#include "HelmholtzEOSMixtureBackend.h" #include "VLERoutines.h" #include "MatrixMath.h" #include "MixtureDerivatives.h" namespace CoolProp { void SaturationSolvers::saturation_critical(HelmholtzEOSMixtureBackend &HEOS, parameters ykey, long double y){ class inner_resid : public FuncWrapper1D{ public: HelmholtzEOSMixtureBackend *HEOS; long double T, desired_p, rhomolar_liq, calc_p, rhomolar_crit; inner_resid(HelmholtzEOSMixtureBackend *HEOS, long double T, long double desired_p) : HEOS(HEOS), T(T), desired_p(desired_p){}; double call(double rhomolar_liq){ this->rhomolar_liq = rhomolar_liq; HEOS->SatL->update(DmolarT_INPUTS, rhomolar_liq, T); calc_p = HEOS->SatL->p(); std::cout << format("inner p: %0.16Lg; res: %0.16Lg", calc_p, calc_p - desired_p) << std::endl; return calc_p - desired_p; } }; // Define the outer residual to be driven to zero - this is the equality of // Gibbs function for both co-existing phases class outer_resid : public FuncWrapper1D { public: HelmholtzEOSMixtureBackend *HEOS; parameters ykey; long double y; long double r, T, rhomolar_liq, rhomolar_vap, value, p, gL, gV, rhomolar_crit; int other; outer_resid(HelmholtzEOSMixtureBackend &HEOS, CoolProp::parameters ykey, long double y) : HEOS(&HEOS), ykey(ykey), y(y){ rhomolar_crit = HEOS.rhomolar_critical(); }; double call(double rhomolar_vap){ this->y = y; // Calculate the other variable (T->p or p->T) for given vapor density if (ykey == iT){ T = y; HEOS->SatV->update(DmolarT_INPUTS, rhomolar_vap, y); this->p = HEOS->SatV->p(); std::cout << format("outer p: %0.16Lg",this->p) << std::endl; inner_resid inner(HEOS, T, p); std::string errstr2; rhomolar_liq = Brent(inner, rhomolar_crit*1.5, rhomolar_crit*(1+1e-8), LDBL_EPSILON, 1e-10, 100, errstr2); } HEOS->SatL->update(DmolarT_INPUTS, rhomolar_liq, T); HEOS->SatV->update(DmolarT_INPUTS, rhomolar_vap, T); // Calculate the Gibbs functions for liquid and vapor gL = HEOS->SatL->gibbsmolar(); gV = HEOS->SatV->gibbsmolar(); // Residual is difference in Gibbs function r = gL - gV; return this->p; }; }; outer_resid resid(HEOS, iT, y); double rhomolar_crit = HEOS.rhomolar_critical(); std::string errstr; Brent(&resid, rhomolar_crit*(1-1e-8), rhomolar_crit*0.5, DBL_EPSILON, 1e-9, 20, errstr); } void SaturationSolvers::saturation_T_pure_1D_P(HelmholtzEOSMixtureBackend &HEOS, long double T, saturation_T_pure_options &options) { // Define the residual to be driven to zero class solver_resid : public FuncWrapper1D { public: HelmholtzEOSMixtureBackend *HEOS; long double r, T, rhomolar_liq, rhomolar_vap, value, p, gL, gV; int other; solver_resid(HelmholtzEOSMixtureBackend &HEOS, long double T, long double rhomolar_liq_guess, long double rhomolar_vap_guess) : HEOS(&HEOS), T(T), rhomolar_liq(rhomolar_liq_guess), rhomolar_vap(rhomolar_vap_guess){}; double call(double p){ this->p = p; // Recalculate the densities using the current guess values HEOS->SatL->update_TP_guessrho(T, p, rhomolar_liq); HEOS->SatV->update_TP_guessrho(T, p, rhomolar_vap); // Calculate the Gibbs functions for liquid and vapor gL = HEOS->SatL->gibbsmolar(); gV = HEOS->SatV->gibbsmolar(); // Residual is difference in Gibbs function r = gL - gV; return r; }; }; solver_resid resid(HEOS, T, options.rhoL, options.rhoV); if (!ValidNumber(options.p)){throw ValueError(format("options.p is not valid in saturation_T_pure_1D_P for T = %Lg",T));}; if (!ValidNumber(options.rhoL)){throw ValueError(format("options.rhoL is not valid in saturation_T_pure_1D_P for T = %Lg",T));}; if (!ValidNumber(options.rhoV)){throw ValueError(format("options.rhoV is not valid in saturation_T_pure_1D_P for T = %Lg",T));}; std::string errstr; try{ Secant(resid, options.p, options.p*1.1, 1e-10, 100, errstr); } catch(std::exception &){ long double pmax = std::min(options.p*1.03, static_cast(HEOS.p_critical()+1e-6)); long double pmin = std::max(options.p*0.97, static_cast(HEOS.p_triple()-1e-6)); Brent(resid, pmin, pmax, LDBL_EPSILON, 1e-8, 100, errstr); } } void SaturationSolvers::saturation_P_pure_1D_T(HelmholtzEOSMixtureBackend &HEOS, long double p, saturation_PHSU_pure_options &options){ // Define the residual to be driven to zero class solver_resid : public FuncWrapper1D { public: HelmholtzEOSMixtureBackend *HEOS; long double r, p, rhomolar_liq, rhomolar_vap, value, T, gL, gV; int other; solver_resid(HelmholtzEOSMixtureBackend &HEOS, long double p, long double rhomolar_liq_guess, long double rhomolar_vap_guess) : HEOS(&HEOS), p(p), rhomolar_liq(rhomolar_liq_guess), rhomolar_vap(rhomolar_vap_guess){}; double call(double T){ this->T = T; // Recalculate the densities using the current guess values HEOS->SatL->update_TP_guessrho(T, p, rhomolar_liq); HEOS->SatV->update_TP_guessrho(T, p, rhomolar_vap); // Calculate the Gibbs functions for liquid and vapor gL = HEOS->SatL->gibbsmolar(); gV = HEOS->SatV->gibbsmolar(); // Residual is difference in Gibbs function r = gL - gV; return r; }; }; solver_resid resid(HEOS, p, options.rhoL, options.rhoV); if (!ValidNumber(options.T)){throw ValueError("options.T is not valid in saturation_P_pure_1D_T");}; if (!ValidNumber(options.rhoL)){throw ValueError("options.rhoL is not valid in saturation_P_pure_1D_T");}; if (!ValidNumber(options.rhoV)){throw ValueError("options.rhoV is not valid in saturation_P_pure_1D_T");}; std::string errstr; long double Tmax = std::min(options.T + 2, static_cast(HEOS.T_critical()-1e-6)); long double Tmin = std::max(options.T - 2, static_cast(HEOS.Ttriple()+1e-6)); Brent(resid, Tmin, Tmax, LDBL_EPSILON, 1e-11, 100, errstr); } void SaturationSolvers::saturation_PHSU_pure(HelmholtzEOSMixtureBackend &HEOS, long double specified_value, saturation_PHSU_pure_options &options) { /* This function is inspired by the method of Akasaka: R. Akasaka,"A Reliable and Useful Method to Determine the Saturation State from Helmholtz Energy Equations of State", Journal of Thermal Science and Technology v3 n3,2008 Ancillary equations are used to get a sensible starting point */ std::vector negativer(3,_HUGE), v; std::vector > J(3, std::vector(3,_HUGE)); HEOS.calc_reducing_state(); const SimpleState & reduce = HEOS.get_reducing_state(); shared_ptr SatL = HEOS.SatL, SatV = HEOS.SatV; long double T, rhoL, rhoV, pL, pV; long double deltaL=0, deltaV=0, tau=0, error; int iter=0, specified_parameter; // Use the density ancillary function as the starting point for the solver try { if (options.specified_variable == saturation_PHSU_pure_options::IMPOSED_PL || options.specified_variable == saturation_PHSU_pure_options::IMPOSED_PV) { // Invert liquid density ancillary to get temperature // TODO: fit inverse ancillaries too try{ T = HEOS.get_components()[0]->ancillaries.pL.invert(specified_value); } catch(std::exception &e) { throw ValueError("Unable to invert ancillary equation"); } } else { throw ValueError(format("options.specified_variable to saturation_PHSU_pure [%d] is invalid",options.specified_variable)); } if (T > HEOS.T_critical()-1){ T -= 1; } // Evaluate densities from the ancillary equations rhoV = HEOS.get_components()[0]->ancillaries.rhoV.evaluate(T); rhoL = HEOS.get_components()[0]->ancillaries.rhoL.evaluate(T); // Apply a single step of Newton's method to improve guess value for liquid // based on the error between the gas pressure (which is usually very close already) // and the liquid pressure, which can sometimes (especially at low pressure), // be way off, and often times negative SatL->update(DmolarT_INPUTS, rhoL, T); SatV->update(DmolarT_INPUTS, rhoV, T); rhoL += -(SatL->p()-SatV->p())/SatL->first_partial_deriv(iP, iDmolar, iT); // Update the state again with the better guess for the liquid density SatL->update(DmolarT_INPUTS, rhoL, T); SatV->update(DmolarT_INPUTS, rhoV, T); deltaL = rhoL/reduce.rhomolar; deltaV = rhoV/reduce.rhomolar; tau = reduce.T/T; } catch(NotImplementedError &) { throw; } do{ /*if (get_debug_level()>8){ std::cout << format("%s:%d: right before the derivs with deltaL = %g deltaV = %g tau = %g\n",__FILE__,__LINE__,deltaL, deltaV, tau).c_str(); }*/ pL = SatL->p(); pV = SatV->p(); // These derivatives are needed for both cases long double alpharL = SatL->alphar(); long double alpharV = SatV->alphar(); long double dalphar_dtauL = SatL->dalphar_dTau(); long double dalphar_dtauV = SatV->dalphar_dTau(); long double d2alphar_ddelta_dtauL = SatL->d2alphar_dDelta_dTau(); long double d2alphar_ddelta_dtauV = SatV->d2alphar_dDelta_dTau(); long double dalphar_ddeltaL = SatL->dalphar_dDelta(); long double dalphar_ddeltaV = SatV->dalphar_dDelta(); long double d2alphar_ddelta2L = SatL->d2alphar_dDelta2(); long double d2alphar_ddelta2V = SatV->d2alphar_dDelta2(); // -r_1 negativer[0] = -(deltaV*(1+deltaV*dalphar_ddeltaV)-deltaL*(1+deltaL*dalphar_ddeltaL)); // -r_2 negativer[1] = -(deltaV*dalphar_ddeltaV+alpharV+log(deltaV)-deltaL*dalphar_ddeltaL-alpharL-log(deltaL)); switch (options.specified_variable){ case saturation_PHSU_pure_options::IMPOSED_PL: // -r_3 negativer[2] = -(pL/specified_value - 1); break; case saturation_PHSU_pure_options::IMPOSED_PV: // -r_3 negativer[2] = -(pV/specified_value - 1); break; default: throw ValueError(format("options.specified_variable to saturation_PHSU_pure [%d] is invalid",options.specified_variable)); } // dr1_dtau J[0][0] = pow(deltaV,2)*d2alphar_ddelta_dtauV-pow(deltaL,2)*d2alphar_ddelta_dtauL; // dr2_dtau J[1][0] = deltaV*d2alphar_ddelta_dtauV+dalphar_dtauV-deltaL*d2alphar_ddelta_dtauL-dalphar_dtauL; if (options.use_logdelta){ // dr_1/d_log(delta'') J[0][1] = -deltaL-2*pow(deltaL,2)*dalphar_ddeltaL-pow(deltaL,3)*d2alphar_ddelta2L; // dr_2/d_log(delta'') J[1][1] = -pow(deltaL,2)*d2alphar_ddelta2L-2*deltaL*dalphar_ddeltaL-1; } else{ // dr_1/ddelta'' J[0][1] = -1-2*deltaL*dalphar_ddeltaL-pow(deltaL,2)*d2alphar_ddelta2L; // dr_2/ddelta'' J[1][1] = -1/deltaL-2*dalphar_ddeltaL-deltaL*d2alphar_ddelta2L; } if (options.use_logdelta){ // dr_1/d_log(delta'') J[0][2] = deltaV+2*pow(deltaV,2)*dalphar_ddeltaV+pow(deltaV,3)*d2alphar_ddelta2V; // dr_2/d_log(delta'') J[1][2] = 1+2*deltaV*dalphar_ddeltaV+1+pow(deltaV,2)*d2alphar_ddelta2V; } else{ // dr_1/ddelta'' J[0][2] = 1+2*deltaV*dalphar_ddeltaV+pow(deltaV,2)*d2alphar_ddelta2V; // dr_2/ddelta'' J[1][2] = deltaV*d2alphar_ddelta2V+2*dalphar_ddeltaV+1/deltaV; } // Derivatives of the specification equation switch (options.specified_variable){ case saturation_PHSU_pure_options::IMPOSED_PL: // dr_3/dtau J[2][0] = SatL->first_partial_deriv(iP,iTau,iDelta)/specified_value; if (options.use_logdelta){ // dr_3/d(log(delta')) J[2][1] = deltaL*SatL->first_partial_deriv(iP,iDelta,iTau)/specified_value; } else{ // dr_3/ddelta' J[2][1] = SatL->first_partial_deriv(iP,iDelta,iTau)/specified_value; } // dr_3/ddelta'' (liquid pressure not a function of vapor density) J[2][2] = 0; specified_parameter = CoolProp::iP; break; case saturation_PHSU_pure_options::IMPOSED_PV: // dr_3/dtau J[2][0] = SatV->first_partial_deriv(iP,iTau,iDelta)/specified_value; // dr_3/ddelta' (vapor pressure not a function of liquid density) J[2][1] = 0; if (options.use_logdelta){ // dr_3/d(log(delta'') J[2][2] = deltaV*SatV->first_partial_deriv(iP,iDelta,iTau)/specified_value; } else{ // dr_3/ddelta'' J[2][2] = SatV->first_partial_deriv(iP,iDelta,iTau)/specified_value; } specified_parameter = CoolProp::iP; break; default: throw ValueError(format("options.specified_variable to saturation_PHSU_pure [%d] is invalid",options.specified_variable)); } v = linsolve(J, negativer); tau += options.omega*v[0]; if (options.use_logdelta){ deltaL = exp(log(deltaL)+options.omega*v[1]); deltaV = exp(log(deltaV)+options.omega*v[2]); } else{ deltaL += options.omega*v[1]; deltaV += options.omega*v[2]; } rhoL = deltaL*reduce.rhomolar; rhoV = deltaV*reduce.rhomolar; T = reduce.T/tau; SatL->update(DmolarT_INPUTS, rhoL, T); SatV->update(DmolarT_INPUTS, rhoV, T); error = sqrt(pow(negativer[0], 2)+pow(negativer[1], 2)+pow(negativer[2], 2)); iter++; if (T < 0) { throw SolutionError(format("saturation_PHSU_pure solver T < 0")); } if (iter > 25){ // Set values back into the options structure for use in next solver options.rhoL = rhoL; options.rhoV = rhoV; options.T = T; // Error out std::string info = get_parameter_information(specified_parameter, "short"); throw SolutionError(format("saturation_PHSU_pure solver did not converge after 25 iterations for %s=%Lg current error is %Lg", info.c_str(), specified_value, error)); } } while (error > 1e-9); } void SaturationSolvers::saturation_D_pure(HelmholtzEOSMixtureBackend &HEOS, long double rhomolar, saturation_D_pure_options &options) { /* This function is inspired by the method of Akasaka: R. Akasaka,"A Reliable and Useful Method to Determine the Saturation State from Helmholtz Energy Equations of State", Journal of Thermal Science and Technology v3 n3,2008 Ancillary equations are used to get a sensible starting point */ std::vector r(2,_HUGE), v; std::vector > J(2, std::vector(2,_HUGE)); HEOS.calc_reducing_state(); const SimpleState & reduce = HEOS.get_reducing_state(); shared_ptr SatL = HEOS.SatL, SatV = HEOS.SatV; long double T, rhoL,rhoV; long double deltaL=0, deltaV=0, tau=0, error, p_error; int iter=0; // Use the density ancillary function as the starting point for the solver try { if (options.imposed_rho == saturation_D_pure_options::IMPOSED_RHOL) { // Invert liquid density ancillary to get temperature // TODO: fit inverse ancillaries too T = HEOS.get_components()[0]->ancillaries.rhoL.invert(rhomolar); rhoV = HEOS.get_components()[0]->ancillaries.rhoV.evaluate(T); rhoL = rhomolar; } else if (options.imposed_rho == saturation_D_pure_options::IMPOSED_RHOV) { // Invert vapor density ancillary to get temperature // TODO: fit inverse ancillaries too T = HEOS.get_components()[0]->ancillaries.rhoV.invert(rhomolar); rhoL = HEOS.get_components()[0]->ancillaries.rhoL.evaluate(T); rhoV = rhomolar; } else { throw ValueError(format("imposed rho to saturation_D_pure [%d%] is invalid",options.imposed_rho)); } deltaL = rhoL/reduce.rhomolar; deltaV = rhoV/reduce.rhomolar; tau = reduce.T/T; } catch(NotImplementedError &e) { throw e; } do{ /*if (get_debug_level()>8){ std::cout << format("%s:%d: right before the derivs with deltaL = %g deltaV = %g tau = %g\n",__FILE__,__LINE__,deltaL, deltaV, tau).c_str(); }*/ // Calculate once to save on calls to EOS SatL->update(DmolarT_INPUTS, rhoL, T); SatV->update(DmolarT_INPUTS, rhoV, T); long double pL = SatL->p(); long double pV = SatV->p(); // These derivatives are needed for both cases long double dalphar_dtauL = SatL->dalphar_dTau(); long double dalphar_dtauV = SatV->dalphar_dTau(); long double d2alphar_ddelta_dtauL = SatL->d2alphar_dDelta_dTau(); long double d2alphar_ddelta_dtauV = SatV->d2alphar_dDelta_dTau(); long double alpharL = SatL->alphar(); long double alpharV = SatV->alphar(); long double dalphar_ddeltaL = SatL->dalphar_dDelta(); long double dalphar_ddeltaV = SatV->dalphar_dDelta(); // -r_1 r[0] = -(deltaV*(1+deltaV*dalphar_ddeltaV)-deltaL*(1+deltaL*dalphar_ddeltaL)); // -r_2 r[1] = -(deltaV*dalphar_ddeltaV+alpharV+log(deltaV)-deltaL*dalphar_ddeltaL-alpharL-log(deltaL)); // dr1_dtau J[0][0] = pow(deltaV,2)*d2alphar_ddelta_dtauV-pow(deltaL,2)*d2alphar_ddelta_dtauL; // dr2_dtau J[1][0] = deltaV*d2alphar_ddelta_dtauV+dalphar_dtauV-deltaL*d2alphar_ddelta_dtauL-dalphar_dtauL; if (options.imposed_rho == saturation_D_pure_options::IMPOSED_RHOL) { long double d2alphar_ddelta2V = SatV->d2alphar_dDelta2(); if (options.use_logdelta) { J[0][1] = deltaV+2*pow(deltaV,2)*dalphar_ddeltaV+pow(deltaV,3)*d2alphar_ddelta2V; J[1][1] = pow(deltaV,2)*d2alphar_ddelta2V+2*deltaV*dalphar_ddeltaV+1; } else { J[0][1] = 1+2*deltaV*dalphar_ddeltaV+pow(deltaV,2)*d2alphar_ddelta2V; J[1][1] = deltaV*d2alphar_ddelta2V+2*dalphar_ddeltaV+1/deltaV; } } else if (options.imposed_rho == saturation_D_pure_options::IMPOSED_RHOV) { long double d2alphar_ddelta2L = SatL->d2alphar_dDelta2(); if (options.use_logdelta) { J[0][1] = -deltaL-2*pow(deltaL,2)*dalphar_ddeltaL-pow(deltaL,3)*d2alphar_ddelta2L; J[1][1] = -pow(deltaL,2)*d2alphar_ddelta2L-2*deltaL*dalphar_ddeltaL-1; } else { J[0][1] = -1-2*deltaL*dalphar_ddeltaL-pow(deltaL,2)*d2alphar_ddelta2L; J[1][1] = -deltaL*d2alphar_ddelta2L-2*dalphar_ddeltaL-1/deltaL; } } //double DET = J[0][0]*J[1][1]-J[0][1]*J[1][0]; v = linsolve(J, r); tau += options.omega*v[0]; if (options.imposed_rho == saturation_D_pure_options::IMPOSED_RHOL) { if (options.use_logdelta) deltaV = exp(log(deltaV)+options.omega*v[1]); else deltaV += v[1]; } else { if (options.use_logdelta) deltaL = exp(log(deltaL)+options.omega*v[1]); else deltaL += v[1]; } rhoL = deltaL*reduce.rhomolar; rhoV = deltaV*reduce.rhomolar; T = reduce.T/tau; p_error = (pL-pV)/pL; error = sqrt(pow(r[0], 2)+pow(r[1], 2)); iter++; if (T < 0) { throw SolutionError(format("saturation_D_pure solver T < 0")); } if (iter > 200){ throw SolutionError(format("saturation_D_pure solver did not converge after 100 iterations with rho: %g mol/m^3",rhomolar)); } } while (error > 1e-9); long double p_error_limit = 1e-3; if (std::abs(p_error) > p_error_limit){ throw SolutionError(format("saturation_D_pure solver abs error on p [%Lg] > limit [%Lg]", p_error, p_error_limit)); } } void SaturationSolvers::saturation_T_pure(HelmholtzEOSMixtureBackend &HEOS, long double T, saturation_T_pure_options &options) { // Set some imput options SaturationSolvers::saturation_T_pure_Akasaka_options _options; _options.omega = 1.0; _options.use_guesses = false; try{ // Actually call the solver SaturationSolvers::saturation_T_pure_Akasaka(HEOS, T, _options); } catch(std::exception &){ // If there was an error, store values for use in later solvers options.pL = _options.pL; options.pV = _options.pV; options.rhoL = _options.rhoL; options.rhoV = _options.rhoV; options.p = _options.pL; SaturationSolvers::saturation_T_pure_1D_P(HEOS, T, options); } } void SaturationSolvers::saturation_T_pure_Akasaka(HelmholtzEOSMixtureBackend &HEOS, long double T, saturation_T_pure_Akasaka_options &options) { // Start with the method of Akasaka /* This function implements the method of Akasaka R. Akasaka,"A Reliable and Useful Method to Determine the Saturation State from Helmholtz Energy Equations of State", Journal of Thermal Science and Technology v3 n3,2008 Ancillary equations are used to get a sensible starting point */ HEOS.calc_reducing_state(); const SimpleState & reduce = HEOS.get_reducing_state(); long double R_u = HEOS.calc_gas_constant(); shared_ptr SatL = HEOS.SatL, SatV = HEOS.SatV; long double rhoL = _HUGE, rhoV = _HUGE,JL,JV,KL,KV,dJL,dJV,dKL,dKV; long double DELTA, deltaL=0, deltaV=0, error, PL, PV, stepL, stepV; int iter=0; try { if (options.use_guesses) { // Use the guesses provided in the options structure rhoL = options.rhoL; rhoV = options.rhoV; } else { // Use the density ancillary function as the starting point for the solver // If very close to the critical temp, evaluate the ancillaries for a slightly lower temperature if (T > 0.99*HEOS.get_reducing_state().T){ rhoL = HEOS.get_components()[0]->ancillaries.rhoL.evaluate(T-0.1); rhoV = HEOS.get_components()[0]->ancillaries.rhoV.evaluate(T-0.1); } else{ rhoL = HEOS.get_components()[0]->ancillaries.rhoL.evaluate(T); rhoV = HEOS.get_components()[0]->ancillaries.rhoV.evaluate(T); // Apply a single step of Newton's method to improve guess value for liquid // based on the error between the gas pressure (which is usually very close already) // and the liquid pressure, which can sometimes (especially at low pressure), // be way off, and often times negative SatL->update(DmolarT_INPUTS, rhoL, T); SatV->update(DmolarT_INPUTS, rhoV, T); rhoL += -(SatL->p()-SatV->p())/SatL->first_partial_deriv(iP, iDmolar, iT); } } deltaL = rhoL/reduce.rhomolar; deltaV = rhoV/reduce.rhomolar; } catch(NotImplementedError &) { /*double Tc = crit.T; double pc = crit.p.Pa; double w = 6.67228479e-09*Tc*Tc*Tc-7.20464352e-06*Tc*Tc+3.16947758e-03*Tc-2.88760012e-01; double q = -6.08930221451*w -5.42477887222; double pt = exp(q*(Tc/T-1))*pc;*/ //double rhoL = density_Tp_Soave(T, pt, 0), rhoV = density_Tp_Soave(T, pt, 1); //deltaL = rhoL/reduce.rhomolar; //deltaV = rhoV/reduce.rhomolar; //tau = reduce.T/T; } //if (get_debug_level()>5){ // std::cout << format("%s:%d: Akasaka guess values deltaL = %g deltaV = %g tau = %g\n",__FILE__,__LINE__,deltaL, deltaV, tau).c_str(); // } do{ /*if (get_debug_level()>8){ std::cout << format("%s:%d: right before the derivs with deltaL = %g deltaV = %g tau = %g\n",__FILE__,__LINE__,deltaL, deltaV, tau).c_str(); }*/ // Calculate once to save on calls to EOS SatL->update(DmolarT_INPUTS, rhoL, T); SatV->update(DmolarT_INPUTS, rhoV, T); long double alpharL = SatL->alphar(); long double alpharV = SatV->alphar(); long double dalphar_ddeltaL = SatL->dalphar_dDelta(); long double dalphar_ddeltaV = SatV->dalphar_dDelta(); long double d2alphar_ddelta2L = SatL->d2alphar_dDelta2(); long double d2alphar_ddelta2V = SatV->d2alphar_dDelta2(); JL = deltaL * (1 + deltaL*dalphar_ddeltaL); JV = deltaV * (1 + deltaV*dalphar_ddeltaV); KL = deltaL*dalphar_ddeltaL + alpharL + log(deltaL); KV = deltaV*dalphar_ddeltaV + alpharV + log(deltaV); PL = R_u*reduce.rhomolar*T*JL; PV = R_u*reduce.rhomolar*T*JV; // At low pressure, the magnitude of d2alphar_ddelta2L and d2alphar_ddelta2V are enormous, truncation problems arise for all the partials dJL = 1 + 2*deltaL*dalphar_ddeltaL + deltaL*deltaL*d2alphar_ddelta2L; dJV = 1 + 2*deltaV*dalphar_ddeltaV + deltaV*deltaV*d2alphar_ddelta2V; dKL = 2*dalphar_ddeltaL + deltaL*d2alphar_ddelta2L + 1/deltaL; dKV = 2*dalphar_ddeltaV + deltaV*d2alphar_ddelta2V + 1/deltaV; DELTA = dJV*dKL-dJL*dKV; error = sqrt((KL-KV)*(KL-KV)+(JL-JV)*(JL-JV)); // Get the predicted step stepL = options.omega/DELTA*( (KV-KL)*dJV-(JV-JL)*dKV); stepV = options.omega/DELTA*( (KV-KL)*dJL-(JV-JL)*dKL); if (deltaL+stepL > 1 && deltaV+stepV < 1 && deltaV+stepV > 0){ deltaL += stepL; deltaV += stepV; rhoL = deltaL*reduce.rhomolar; rhoV = deltaV*reduce.rhomolar; } else{ throw ValueError(format("rhosatPure_Akasaka densities are crossed")); } iter++; if (iter > 100){ throw SolutionError(format("Akasaka solver did not converge after 100 iterations")); } } while (error > 1e-10 && std::abs(stepL) > 10*DBL_EPSILON*std::abs(stepL) && std::abs(stepV) > 10*DBL_EPSILON*std::abs(stepV)); long double p_error_limit = 1e-3; long double p_error = (PL - PV)/PL; if (std::abs(p_error) > p_error_limit){ options.pL = PL; options.pV = PV; options.rhoL = rhoL; options.rhoV = rhoV; throw SolutionError(format("saturation_T_pure_Akasaka solver abs error on p [%g] > limit [%g]", std::abs(p_error), p_error_limit)); } } void SaturationSolvers::x_and_y_from_K(long double beta, const std::vector &K, const std::vector &z, std::vector &x, std::vector &y) { for (unsigned int i=0; i < K.size(); i++) { double denominator = (1-beta+beta*K[i]); // Common denominator x[i] = z[i]/denominator; y[i] = K[i]*z[i]/denominator; } } void SaturationSolvers::successive_substitution(HelmholtzEOSMixtureBackend &HEOS, const long double beta, long double T, long double p, const std::vector &z, std::vector &K, mixture_VLE_IO &options) { int iter = 1; long double change, f, df, deriv_liq, deriv_vap; std::size_t N = z.size(); std::vector ln_phi_liq, ln_phi_vap; ln_phi_liq.resize(N); ln_phi_vap.resize(N); std::vector &x = HEOS.SatL->get_mole_fractions(), &y = HEOS.SatV->get_mole_fractions(); x_and_y_from_K(beta, K, z, x, y); HEOS.SatL->specify_phase(iphase_liquid); HEOS.SatV->specify_phase(iphase_gas); normalize_vector(x); normalize_vector(y); HEOS.SatL->set_mole_fractions(x); HEOS.SatV->set_mole_fractions(y); HEOS.SatL->calc_reducing_state(); HEOS.SatV->calc_reducing_state(); long double rhomolar_liq = HEOS.SatL->solver_rho_Tp_SRK(T, p, iphase_liquid); // [mol/m^3] long double rhomolar_vap = HEOS.SatV->solver_rho_Tp_SRK(T, p, iphase_gas); // [mol/m^3] HEOS.SatL->update_TP_guessrho(T, p, rhomolar_liq*1.3); HEOS.SatV->update_TP_guessrho(T, p, rhomolar_vap); do { HEOS.SatL->update_TP_guessrho(T, p, HEOS.SatL->rhomolar()); HEOS.SatV->update_TP_guessrho(T, p, HEOS.SatV->rhomolar()); f = 0; df = 0; x_N_dependency_flag xN_flag = XN_INDEPENDENT; for (std::size_t i = 0; i < N; ++i) { ln_phi_liq[i] = MixtureDerivatives::ln_fugacity_coefficient(*(HEOS.SatL.get()), i, xN_flag); ln_phi_vap[i] = MixtureDerivatives::ln_fugacity_coefficient(*(HEOS.SatV.get()), i, xN_flag); if (options.sstype == imposed_p){ deriv_liq = MixtureDerivatives::dln_fugacity_coefficient_dT__constp_n(*(HEOS.SatL.get()), i, xN_flag); deriv_vap = MixtureDerivatives::dln_fugacity_coefficient_dT__constp_n(*(HEOS.SatV.get()), i, xN_flag); } else if (options.sstype == imposed_T){ deriv_liq = MixtureDerivatives::dln_fugacity_coefficient_dp__constT_n(*(HEOS.SatL.get()), i, xN_flag); deriv_vap = MixtureDerivatives::dln_fugacity_coefficient_dp__constT_n(*(HEOS.SatV.get()), i, xN_flag); } else {throw ValueError();} K[i] = exp(ln_phi_liq[i]-ln_phi_vap[i]); f += z[i]*(K[i]-1)/(1-beta+beta*K[i]); double dfdK = K[i]*z[i]/pow(1-beta+beta*K[i],(int)2); df += dfdK*(deriv_liq-deriv_vap); } change = -f/df; if (options.sstype == imposed_p){ T += change; } else if (options.sstype == imposed_T){ p += change; } x_and_y_from_K(beta, K, z, x, y); normalize_vector(x); normalize_vector(y); HEOS.SatL->set_mole_fractions(x); HEOS.SatV->set_mole_fractions(y); iter += 1; if (iter > 50) { throw ValueError(format("saturation_p was unable to reach a solution within 50 iterations")); } } while(std::abs(f) > 1e-12 && iter < options.Nstep_max); HEOS.SatL->update_TP_guessrho(T, p, rhomolar_liq); HEOS.SatV->update_TP_guessrho(T, p, rhomolar_vap); options.p = HEOS.SatL->p(); options.T = HEOS.SatL->T(); options.rhomolar_liq = HEOS.SatL->rhomolar(); options.rhomolar_vap = HEOS.SatV->rhomolar(); options.x = x; options.y = y; } void SaturationSolvers::newton_raphson_saturation::resize(unsigned int N) { this->N = N; x.resize(N); y.resize(N); r.resize(N+1); negative_r.resize(N+1); J.resize(N+1, std::vector(N+1, 0)); } void SaturationSolvers::newton_raphson_saturation::check_Jacobian() { // References to the classes for concision HelmholtzEOSMixtureBackend &rSatL = *(HEOS->SatL.get()), &rSatV = *(HEOS->SatV.get()); // Build the Jacobian and residual vectors build_arrays(); // Make copies of the base long double T0 = T; std::vector r0 = r, x0 = x; STLMatrix J0 = J; long double rhomolar_liq0 = rSatL.rhomolar(); long double rhomolar_vap0 = rSatV.rhomolar(); { // Derivatives with respect to T double dT = 1e-3, T1 = T+dT, T2 = T-dT; this->T = T1; this->rhomolar_liq = rhomolar_liq0; this->rhomolar_vap = rhomolar_vap0; build_arrays(); std::vector r1 = r; this->T = T2; this->rhomolar_liq = rhomolar_liq0; this->rhomolar_vap = rhomolar_vap0; build_arrays(); std::vector r2 = r; std::vector diffn(N+1, _HUGE); for (std::size_t i = 0; i < N+1; ++i){ diffn[i] = (r1[i]-r2[i])/(2*dT); } std::cout << format("For T\n"); std::cout << "numerical: " << vec_to_string(diffn, "%0.11Lg") << std::endl; std::cout << "analytic: " << vec_to_string(get_col(J0, N-1), "%0.11Lg") << std::endl; } { // Derivatives with respect to rho' double drho = 1; this->T = T0; this->rhomolar_liq = rhomolar_liq0+drho; this->rhomolar_vap = rhomolar_vap0; build_arrays(); std::vector rr1 = r; this->T = T0; this->rhomolar_liq = rhomolar_liq0-drho; this->rhomolar_vap = rhomolar_vap0; build_arrays(); std::vector rr2 = r; std::vector difffn(N+1, _HUGE); for (std::size_t i = 0; i < N+1; ++i){ difffn[i] = (rr1[i]-rr2[i])/(2*drho); } std::cout << format("For rho\n"); std::cout << "numerical: " << vec_to_string(difffn, "%0.11Lg") << std::endl; std::cout << "analytic: " << vec_to_string(get_col(J0, N), "%0.11Lg") << std::endl; } for (std::size_t i = 0; i < x.size()-1; ++i) { // Derivatives with respect to x[i] double dx = 1e-5; this->x = x0; this->x[i] += dx; this->x[x.size()-1] -= dx; this->T = T0; this->rhomolar_liq = rhomolar_liq0; this->rhomolar_vap = rhomolar_vap0; build_arrays(); std::vector r1 = this->r; this->x = x0; this->x[i] -= dx; this->x[x.size()-1] += dx; this->T = T0; this->rhomolar_liq = rhomolar_liq0; this->rhomolar_vap = rhomolar_vap0; build_arrays(); std::vector r2 = this->r; std::vector diffn(N+1, _HUGE); for (std::size_t j = 0; j < N+1; ++j){ diffn[j] = (r1[j]-r2[j])/(2*dx); } std::cout << format("For x%d N %d\n", i, N); std::cout << "numerical: " << vec_to_string(diffn, "%0.11Lg") << std::endl; std::cout << "analytic: " << vec_to_string(get_col(J0, i), "%0.11Lg") << std::endl; } } void SaturationSolvers::newton_raphson_saturation::call(HelmholtzEOSMixtureBackend &HEOS, const std::vector &z, std::vector &z_incipient, newton_raphson_saturation_options &IO) { int iter = 0; if (get_debug_level() > 9){std::cout << " NRsat::call: p" << IO.p << " T" << IO.T << " dl" << IO.rhomolar_liq << " dv" << IO.rhomolar_vap << std::endl;} // Reset all the variables and resize pre_call(); resize(z.size()); this->bubble_point = IO.bubble_point; rhomolar_liq = IO.rhomolar_liq; rhomolar_vap = IO.rhomolar_vap; T = IO.T; p = IO.p; if (bubble_point){ // Bubblepoint, vapor (y) is the incipient phase x = z; y = z_incipient; } else{ // Dewpoint, liquid (x) is the incipient phase y = z; x = z_incipient; } // Hold a pointer to the backend this->HEOS = &HEOS; //check_Jacobian(); do { // Build the Jacobian and residual vectors build_arrays(); // Solve for the step; v is the step with the contents // [delta(x_0), delta(x_1), ..., delta(x_{N-2}), delta(spec)] std::vector v = linsolve(J, negative_r); max_rel_change = max_abs_value(v); min_abs_change = min_abs_value(v); if (bubble_point){ for (unsigned int i = 0; i < N-1; ++i){ y[i] += v[i]; } y[N-1] = 1 - std::accumulate(y.begin(), y.end()-1, 0.0); } else{ for (unsigned int i = 0; i < N-1; ++i){ x[i] += v[i]; } x[N-1] = 1 - std::accumulate(x.begin(), x.end()-1, 0.0); } T += v[N-1]; rhomolar_liq += v[N]; //std::cout << format("\t%Lg ", this->error_rms) << T << " " << rhomolar_liq << " " << rhomolar_vap << " " << vec_to_string(v, "%0.10Lg") << " " << vec_to_string(x, "%0.10Lg") << std::endl; iter++; if (iter == IO.Nstep_max){ throw ValueError(format("newton_raphson_saturation::call reached max number of iterations [%d]",IO.Nstep_max)); } } while(this->error_rms > 1e-7 && max_rel_change > 1000*LDBL_EPSILON && min_abs_change > 100*DBL_EPSILON && iter < IO.Nstep_max); IO.Nsteps = iter; IO.p = p; IO.x = x; // Mole fractions in liquid IO.y = y; // Mole fractions in vapor IO.T = T; IO.rhomolar_liq = rhomolar_liq; IO.rhomolar_vap = rhomolar_vap; IO.hmolar_liq = HEOS.SatL.get()->hmolar(); IO.hmolar_vap = HEOS.SatV.get()->hmolar(); IO.smolar_liq = HEOS.SatL.get()->smolar(); IO.smolar_vap = HEOS.SatV.get()->smolar(); } void SaturationSolvers::newton_raphson_saturation::build_arrays() { // References to the classes for concision HelmholtzEOSMixtureBackend &rSatL = *(HEOS->SatL.get()), &rSatV = *(HEOS->SatV.get()); // Step 0: // ------- // Set mole fractions for the incipient phase if (bubble_point){ // Vapor is incipient phase, set its composition rSatV.set_mole_fractions(y); } else{ // Liquid is incipient phase, set its composition rSatL.set_mole_fractions(x); } rSatL.update(DmolarT_INPUTS, rhomolar_liq, T); rSatV.update(DmolarT_INPUTS, rhomolar_vap, T); //std::cout << format("\t\t%Lg %Lg %Lg %s\n", T, rhomolar_liq, rhomolar_vap, vec_to_string(x,"%0.12Lg").c_str()); // For diagnostic purposes calculate the pressures (no derivatives are evaluated) long double p_liq = rSatL.p(); long double p_vap = rSatV.p(); p = 0.5*(p_liq + p_vap); // Step 2: // ------- // Build the residual vector and the Jacobian matrix x_N_dependency_flag xN_flag = XN_DEPENDENT; // For the residuals F_i (equality of fugacities) for (std::size_t i = 0; i < N; ++i) { // Equate the liquid and vapor fugacities long double ln_f_liq = log(MixtureDerivatives::fugacity_i(rSatL, i, xN_flag)); long double ln_f_vap = log(MixtureDerivatives::fugacity_i(rSatV, i, xN_flag)); r[i] = ln_f_liq - ln_f_vap; if (bubble_point){ for (std::size_t j = 0; j < N-1; ++j){ // j from 0 to N-2 J[i][j] = -MixtureDerivatives::dln_fugacity_dxj__constT_rho_xi(rSatV, i, j, xN_flag); } } else{ for (std::size_t j = 0; j < N-1; ++j){ // j from 0 to N-2 J[i][j] = MixtureDerivatives::dln_fugacity_dxj__constT_rho_xi(rSatL, i, j, xN_flag); } } J[i][N-1] = MixtureDerivatives::dln_fugacity_i_dT__constrho_n(rSatL, i, xN_flag) - MixtureDerivatives::dln_fugacity_i_dT__constrho_n(rSatV, i, xN_flag); J[i][N] = MixtureDerivatives::dln_fugacity_i_drho__constT_n(rSatL, i, xN_flag); } // --------------------------------------------------------------- // Derivatives of pL(T,rho',x)-p(T,rho'',y) with respect to inputs // --------------------------------------------------------------- r[N] = p_liq - p_vap; for (std::size_t j = 0; j < N-1; ++j){ // j from 0 to N-2 J[N][j] = MixtureDerivatives::dpdxj__constT_V_xi(rSatL, j, xN_flag); // p'' not a function of x0 } // Fixed composition derivatives J[N][N-1] = rSatL.first_partial_deriv(iP, iT, iDmolar)-rSatV.first_partial_deriv(iP, iT, iDmolar); J[N][N] = rSatL.first_partial_deriv(iP, iDmolar, iT); // Flip all the signs of the entries in the residual vector since we are solving Jv = -r, not Jv=r // Also calculate the rms error of the residual vector at this step error_rms = 0; for (unsigned int i = 0; i < J.size(); ++i) { negative_r[i] = -r[i]; error_rms += r[i]*r[i]; // Sum the squares } error_rms = sqrt(error_rms); // Square-root (The R in RMS) } } /* namespace CoolProp*/