24 double call(
double rhomolar_liq)
override {
27 std::cout <<
format(
"inner p: %0.16Lg; res: %0.16Lg", calc_p, calc_p - desired_p) <<
'\n';
28 return calc_p - desired_p;
44 double call(
double rhomolar_vap)
override {
52 std::cout <<
format(
"outer p: %0.16Lg", p) <<
'\n';
53 inner_resid inner(HEOS,
T, p);
54 rhomolar_liq =
Brent(inner, rhomolar_crit * 1.5, rhomolar_crit * (1 + 1e-8), LDBL_EPSILON, 1e-10, 100);
58 throw ValueError(
"Wrong input for outer_resid");
73 outer_resid resid(HEOS,
iT, y);
77 Brent(&resid, rhomolar_crit * (1 - 1e-8), rhomolar_crit * 0.5,
DBL_EPSILON, 1e-9, 20);
90 : HEOS(&HEOS),
T(
T), rhomolar_liq(rhomolar_liq_guess), rhomolar_vap(rhomolar_vap_guess) {};
91 double call(
double p)
override {
93 HEOS->
SatL->update_TP_guessrho(
T, p, rhomolar_liq);
94 HEOS->
SatV->update_TP_guessrho(
T, p, rhomolar_vap);
104 solver_resid resid(HEOS,
T, options.
rhoL, options.
rhoV);
107 throw ValueError(
format(
"options.p is not valid in saturation_T_pure_1D_P for T = %Lg",
T));
110 throw ValueError(
format(
"options.rhoL is not valid in saturation_T_pure_1D_P for T = %Lg",
T));
113 throw ValueError(
format(
"options.rhoV is not valid in saturation_T_pure_1D_P for T = %Lg",
T));
117 Secant(resid, options.
p, options.
p * 1.1, 1e-10, 100);
121 Brent(resid, pmin, pmax, LDBL_EPSILON, 1e-8, 100);
135 : HEOS(&HEOS), p(p), rhomolar_liq(rhomolar_liq_guess), rhomolar_vap(rhomolar_vap_guess) {};
136 double call(
double T)
override {
138 HEOS->
SatL->update_TP_guessrho(
T, p, rhomolar_liq);
139 HEOS->
SatV->update_TP_guessrho(
T, p, rhomolar_vap);
149 solver_resid resid(HEOS, p, options.
rhoL, options.
rhoV);
152 throw ValueError(
"options.T is not valid in saturation_P_pure_1D_T");
155 throw ValueError(
"options.rhoL is not valid in saturation_P_pure_1D_T");
158 throw ValueError(
"options.rhoV is not valid in saturation_P_pure_1D_T");
163 Brent(resid, Tmin, Tmax, LDBL_EPSILON, 1e-11, 100);
176 std::vector<CoolPropDbl> negativer(3, _HUGE), v;
177 std::vector<std::vector<CoolPropDbl>> J(3, std::vector<CoolPropDbl>(3, _HUGE));
182 shared_ptr<HelmholtzEOSMixtureBackend> SatL = HEOS.
SatL, SatV = HEOS.
SatV;
184 CoolPropDbl T = NAN, rhoL = NAN, rhoV = NAN, pL = NAN, pV = NAN, hL = NAN, sL = NAN, hV = NAN, sV = NAN;
185 CoolPropDbl deltaL = 0, deltaV = 0, tau = 0, error = NAN;
186 int iter = 0, specified_parameter = 0;
197 throw ValueError(
"Unable to invert ancillary equation");
205 throw ValueError(
"Unable to invert ancillary equation for hL");
213 Residual(
CoolPropFluid& component,
double h) : component(&component), h(h) {}
214 double call(
double T)
override {
224 double Tmin = Tmin_satL;
229 shared_ptr<HelmholtzEOSMixtureBackend> HEOS_copy = std::make_shared<HelmholtzEOSMixtureBackend>(HEOS.
get_components());
231 double hTmin = HEOS_copy->hmolar();
233 double hTmax = HEOS_copy->hmolar();
234 T = (Tmax - Tmin) / (hTmax - hTmin) * (HEOS.
hmolar() - hTmin) + Tmin;
242 T = std::max(0.99 * crit.
T, crit.
T - 0.1);
244 CoolPropDbl Tmin = NAN, Tmax = NAN, Tmin_satV = NAN;
250 T = anc.
invert(specified_value - hs_anchor.
smolar, Tmin, Tmax);
253 T = anc.
invert(specified_value - hs_anchor.
smolar, Tmin - 3, Tmax + 3);
257 if (std::abs(specified_value - hs_anchor.
smolar) < std::abs(vmax)) {
260 throw ValueError(
format(
"Unable to invert ancillary equation for sL for value %Lg with Tminval %g and Tmaxval %g ",
261 specified_value - hs_anchor.
smolar, vmin, vmax));
274 Residual(
CoolPropFluid& component,
double s) : component(&component), s(s) {}
275 double call(
double T)
override {
282 Residual resid(component, HEOS.
smolar());
287 double Tmin = Tmin_satL;
294 if (std::abs(specified_value - hs_anchor.
smolar) < std::abs(vmax)) {
295 T = std::max(0.99 * crit.
T, crit.
T - 0.1);
297 shared_ptr<HelmholtzEOSMixtureBackend> HEOS_copy = std::make_shared<HelmholtzEOSMixtureBackend>(HEOS.
get_components());
299 double sTmin = HEOS_copy->smolar();
301 double sTmax = HEOS_copy->smolar();
302 T = (Tmax - Tmin) / (sTmax - sTmin) * (HEOS.
smolar() - sTmin) + Tmin;
322 double rhoL_updated = rhoL - (SatL->p() - SatV->p()) / SatL->first_partial_deriv(
iP,
iDmolar,
iT);
325 if (rhoL_updated > rhoV) {
357 CoolPropDbl d2alphar_ddelta_dtauL = SatL->d2alphar_dDelta_dTau();
358 CoolPropDbl d2alphar_ddelta_dtauV = SatV->d2alphar_dDelta_dTau();
359 CoolPropDbl dalphar_ddeltaL = SatL->dalphar_dDelta();
360 CoolPropDbl dalphar_ddeltaV = SatV->dalphar_dDelta();
361 CoolPropDbl d2alphar_ddelta2L = SatL->d2alphar_dDelta2();
362 CoolPropDbl d2alphar_ddelta2V = SatV->d2alphar_dDelta2();
365 negativer[0] = -(deltaV * (1 + deltaV * dalphar_ddeltaV) - deltaL * (1 + deltaL * dalphar_ddeltaL));
367 negativer[1] = -(deltaV * dalphar_ddeltaV + alpharV + log(deltaV) - deltaL * dalphar_ddeltaL - alpharL - log(deltaL));
369 case saturation_PHSU_pure_options::IMPOSED_PL:
371 negativer[2] = -(pL / specified_value - 1);
373 case saturation_PHSU_pure_options::IMPOSED_PV:
375 negativer[2] = -(pV / specified_value - 1);
377 case saturation_PHSU_pure_options::IMPOSED_HL:
379 negativer[2] = -(hL - specified_value);
381 case saturation_PHSU_pure_options::IMPOSED_HV:
383 negativer[2] = -(hV - specified_value);
385 case saturation_PHSU_pure_options::IMPOSED_SL:
387 negativer[2] = -(sL - specified_value);
389 case saturation_PHSU_pure_options::IMPOSED_SV:
391 negativer[2] = -(sV - specified_value);
398 J[0][0] = pow(deltaV, 2) * d2alphar_ddelta_dtauV - pow(deltaL, 2) * d2alphar_ddelta_dtauL;
400 J[1][0] = deltaV * d2alphar_ddelta_dtauV + dalphar_dtauV - deltaL * d2alphar_ddelta_dtauL - dalphar_dtauL;
404 J[0][1] = -deltaL - 2 * pow(deltaL, 2) * dalphar_ddeltaL - pow(deltaL, 3) * d2alphar_ddelta2L;
406 J[1][1] = -pow(deltaL, 2) * d2alphar_ddelta2L - 2 * deltaL * dalphar_ddeltaL - 1;
409 J[0][1] = -1 - 2 * deltaL * dalphar_ddeltaL - pow(deltaL, 2) * d2alphar_ddelta2L;
411 J[1][1] = -1 / deltaL - 2 * dalphar_ddeltaL - deltaL * d2alphar_ddelta2L;
416 J[0][2] = deltaV + 2 * pow(deltaV, 2) * dalphar_ddeltaV + pow(deltaV, 3) * d2alphar_ddelta2V;
418 J[1][2] = 1 + 2 * deltaV * dalphar_ddeltaV + 1 + pow(deltaV, 2) * d2alphar_ddelta2V;
421 J[0][2] = 1 + 2 * deltaV * dalphar_ddeltaV + pow(deltaV, 2) * d2alphar_ddelta2V;
423 J[1][2] = deltaV * d2alphar_ddelta2V + 2 * dalphar_ddeltaV + 1 / deltaV;
428 case saturation_PHSU_pure_options::IMPOSED_PL:
430 J[2][0] = SatL->first_partial_deriv(
iP,
iTau,
iDelta) / specified_value;
433 J[2][1] = deltaL * SatL->first_partial_deriv(
iP,
iDelta,
iTau) / specified_value;
436 J[2][1] = SatL->first_partial_deriv(
iP,
iDelta,
iTau) / specified_value;
442 case saturation_PHSU_pure_options::IMPOSED_PV:
444 J[2][0] = SatV->first_partial_deriv(
iP,
iTau,
iDelta) / specified_value;
449 J[2][2] = deltaV * SatV->first_partial_deriv(
iP,
iDelta,
iTau) / specified_value;
452 J[2][2] = SatV->first_partial_deriv(
iP,
iDelta,
iTau) / specified_value;
456 case saturation_PHSU_pure_options::IMPOSED_HL:
468 case saturation_PHSU_pure_options::IMPOSED_HV:
480 case saturation_PHSU_pure_options::IMPOSED_SL:
492 case saturation_PHSU_pure_options::IMPOSED_SV:
513 double tau0 = tau, deltaL0 = deltaL, deltaV0 = deltaV;
515 for (
double omega_local = 1.0; omega_local > 0.1; omega_local /= 1.1) {
516 tau = tau0 + omega_local * options.
omega * v[0];
518 deltaL = exp(log(deltaL0) + omega_local * options.
omega * v[1]);
519 deltaV = exp(log(deltaV0) + omega_local * options.
omega * v[2]);
521 deltaL = deltaL0 + omega_local * options.
omega * v[1];
522 deltaV = deltaV0 + omega_local * options.
omega * v[2];
524 if (tau > 1 && deltaL > deltaV) {
536 error = sqrt(pow(negativer[0], 2) + pow(negativer[1], 2) + pow(negativer[2], 2));
552 throw SolutionError(
format(
"saturation_PHSU_pure solver did not converge after 50 iterations for %s=%Lg current error is %Lg",
553 info.c_str(), specified_value, error));
555 }
while (error > 1e-9);
561 SatL->unspecify_phase();
562 SatV->unspecify_phase();
565 negativer[0] = -(deltaV * (1 + deltaV * SatV->dalphar_dDelta()) - deltaL * (1 + deltaL * SatL->dalphar_dDelta()));
566 negativer[1] = -(deltaV * SatV->dalphar_dDelta() + SatV->alphar() + log(deltaV) - deltaL * SatL->dalphar_dDelta() - SatL->alphar() - log(deltaL));
568 case saturation_PHSU_pure_options::IMPOSED_PL:
570 negativer[2] = -(SatL->p() / specified_value - 1);
572 case saturation_PHSU_pure_options::IMPOSED_PV:
574 negativer[2] = -(SatV->p() / specified_value - 1);
576 case saturation_PHSU_pure_options::IMPOSED_HL:
578 negativer[2] = -(SatL->hmolar() - specified_value);
580 case saturation_PHSU_pure_options::IMPOSED_HV:
582 negativer[2] = -(SatV->hmolar() - specified_value);
584 case saturation_PHSU_pure_options::IMPOSED_SL:
586 negativer[2] = -(SatL->smolar() - specified_value);
588 case saturation_PHSU_pure_options::IMPOSED_SV:
590 negativer[2] = -(SatV->smolar() - specified_value);
595 error = sqrt(pow(negativer[0], 2) + pow(negativer[1], 2) + pow(negativer[2], 2));
600 throw SolutionError(
format(
"saturation_PHSU_pure solver was good, but went bad. Current error is %Lg", error));
613 std::vector<CoolPropDbl> r(2, _HUGE), v;
614 std::vector<std::vector<CoolPropDbl>> J(2, std::vector<CoolPropDbl>(2, _HUGE));
618 shared_ptr<HelmholtzEOSMixtureBackend> SatL = HEOS.
SatL, SatV = HEOS.
SatV;
621 CoolPropDbl deltaL = 0, deltaV = 0, tau = 0, error = NAN, p_error = NAN;
626 if (options.
imposed_rho == saturation_D_pure_options::IMPOSED_RHOL) {
632 }
else if (options.
imposed_rho == saturation_D_pure_options::IMPOSED_RHOV) {
664 CoolPropDbl d2alphar_ddelta_dtauL = SatL->d2alphar_dDelta_dTau();
665 CoolPropDbl d2alphar_ddelta_dtauV = SatV->d2alphar_dDelta_dTau();
668 CoolPropDbl dalphar_ddeltaL = SatL->dalphar_dDelta();
669 CoolPropDbl dalphar_ddeltaV = SatV->dalphar_dDelta();
672 r[0] = -(deltaV * (1 + deltaV * dalphar_ddeltaV) - deltaL * (1 + deltaL * dalphar_ddeltaL));
674 r[1] = -(deltaV * dalphar_ddeltaV + alpharV + log(deltaV) - deltaL * dalphar_ddeltaL - alpharL - log(deltaL));
677 J[0][0] = pow(deltaV, 2) * d2alphar_ddelta_dtauV - pow(deltaL, 2) * d2alphar_ddelta_dtauL;
679 J[1][0] = deltaV * d2alphar_ddelta_dtauV + dalphar_dtauV - deltaL * d2alphar_ddelta_dtauL - dalphar_dtauL;
681 if (options.
imposed_rho == saturation_D_pure_options::IMPOSED_RHOL) {
682 CoolPropDbl d2alphar_ddelta2V = SatV->d2alphar_dDelta2();
684 J[0][1] = deltaV + 2 * pow(deltaV, 2) * dalphar_ddeltaV + pow(deltaV, 3) * d2alphar_ddelta2V;
685 J[1][1] = pow(deltaV, 2) * d2alphar_ddelta2V + 2 * deltaV * dalphar_ddeltaV + 1;
687 J[0][1] = 1 + 2 * deltaV * dalphar_ddeltaV + pow(deltaV, 2) * d2alphar_ddelta2V;
688 J[1][1] = deltaV * d2alphar_ddelta2V + 2 * dalphar_ddeltaV + 1 / deltaV;
690 }
else if (options.
imposed_rho == saturation_D_pure_options::IMPOSED_RHOV) {
691 CoolPropDbl d2alphar_ddelta2L = SatL->d2alphar_dDelta2();
693 J[0][1] = -deltaL - 2 * pow(deltaL, 2) * dalphar_ddeltaL - pow(deltaL, 3) * d2alphar_ddelta2L;
694 J[1][1] = -pow(deltaL, 2) * d2alphar_ddelta2L - 2 * deltaL * dalphar_ddeltaL - 1;
696 J[0][1] = -1 - 2 * deltaL * dalphar_ddeltaL - pow(deltaL, 2) * d2alphar_ddelta2L;
697 J[1][1] = -deltaL * d2alphar_ddelta2L - 2 * dalphar_ddeltaL - 1 / deltaL;
705 double omega = options.
omega;
707 if (options.
imposed_rho == saturation_D_pure_options::IMPOSED_RHOL) {
709 deltaV = exp(log(deltaV) + omega * v[1]);
711 if (deltaV + omega * v[1] <= 0) {
712 omega = 0.5 * deltaV / v[1];
714 if (tau + omega * v[0] <= 0) {
715 omega = 0.5 * tau / v[0];
717 deltaV += omega * v[1];
721 deltaL = exp(log(deltaL) + omega * v[1]);
723 if (deltaL + omega * v[1] <= 0) {
724 omega = 0.5 * deltaL / v[1];
726 if (tau + omega * v[0] <= 0) {
727 omega = 0.5 * tau / v[0];
729 deltaL += omega * v[1];
739 p_error = (pL - pV) / pL;
741 error = sqrt(pow(r[0], 2) + pow(r[1], 2));
748 format(
"saturation_D_pure solver did not converge after %d iterations with rho: %g mol/m^3", options.
max_iterations, rhomolar));
750 }
while (error > 1e-9);
752 if (std::abs(p_error) > p_error_limit) {
753 throw SolutionError(
format(
"saturation_D_pure solver abs error on p [%Lg] > limit [%Lg]", p_error, p_error_limit));
759 _options.
omega = 1.0;
769 options.
pL = _options.
pL;
770 options.
pV = _options.
pV;
773 options.
p = _options.
pL;
794 shared_ptr<HelmholtzEOSMixtureBackend> SatL = HEOS.
SatL, SatV = HEOS.
SatV;
796 CoolPropDbl rhoL = _HUGE, rhoV = _HUGE, JL = NAN, JV = NAN, KL = NAN, KV = NAN, dJL = NAN, dJV = NAN, dKL = NAN, dKV = NAN;
797 CoolPropDbl DELTA = NAN, deltaL = 0, deltaV = 0, error = NAN, PL = NAN, PV = NAN, stepL = NAN, stepV = NAN;
866 CoolPropDbl dalphar_ddeltaL = SatL->dalphar_dDelta();
867 CoolPropDbl dalphar_ddeltaV = SatV->dalphar_dDelta();
868 CoolPropDbl d2alphar_ddelta2L = SatL->d2alphar_dDelta2();
869 CoolPropDbl d2alphar_ddelta2V = SatV->d2alphar_dDelta2();
871 JL = deltaL * (1 + deltaL * dalphar_ddeltaL);
872 JV = deltaV * (1 + deltaV * dalphar_ddeltaV);
873 KL = deltaL * dalphar_ddeltaL + alpharL + log(deltaL);
874 KV = deltaV * dalphar_ddeltaV + alpharV + log(deltaV);
880 dJL = 1 + 2 * deltaL * dalphar_ddeltaL + deltaL * deltaL * d2alphar_ddelta2L;
881 dJV = 1 + 2 * deltaV * dalphar_ddeltaV + deltaV * deltaV * d2alphar_ddelta2V;
882 dKL = 2 * dalphar_ddeltaL + deltaL * d2alphar_ddelta2L + 1 / deltaL;
883 dKV = 2 * dalphar_ddeltaV + deltaV * d2alphar_ddelta2V + 1 / deltaV;
885 DELTA = dJV * dKL - dJL * dKV;
887 error = sqrt((KL - KV) * (KL - KV) + (JL - JV) * (JL - JV));
890 stepL = options.
omega / DELTA * ((KV - KL) * dJV - (JV - JL) * dKV);
891 stepV = options.
omega / DELTA * ((KV - KL) * dJL - (JV - JL) * dKL);
897 for (
double omega_local = 1.0; omega_local > 0.1; omega_local /= 1.1) {
898 deltaL = deltaL0 + omega_local * stepL;
899 deltaV = deltaV0 + omega_local * stepV;
901 if (deltaL > 1 && deltaV < 1 && deltaV > 0) {
912 }
while (error > 1e-10 && std::abs(stepL) > 10 *
DBL_EPSILON * std::abs(stepL) && std::abs(stepV) > 10 *
DBL_EPSILON * std::abs(stepV));
916 if (std::abs(p_error) > p_error_limit) {
921 throw SolutionError(
format(
"saturation_T_pure_Akasaka solver abs error on p [%g] > limit [%g]", std::abs(p_error), p_error_limit));
942 shared_ptr<HelmholtzEOSMixtureBackend> SatL = HEOS.
SatL, SatV = HEOS.
SatV;
944 CoolPropDbl rhoL = _HUGE, rhoV = _HUGE, error = 999, DeltavL = NAN, DeltavV = NAN, pL = NAN, pV = NAN, p = NAN, last_error = NAN;
945 int iter = 0, small_step_count = 0,
946 backwards_step_count = 0;
970 if ((rhoL < crit.rhomolar * 0.8 || rhoL > tripleL.
rhomolar * 1.2 || rhoV > crit.
rhomolar * 1.2 || rhoV < tripleV.
rhomolar * 0.8)
975 "[Maxwell] ancillaries correction T: %0.16Lg rhoL: %0.16Lg rhoV: %0.16Lg rhoc: %g rhoLtrip: %g rhoVtrip: %g\n",
T, rhoL,
982 SatL->update_DmolarT_direct(rhoL,
T);
985 rhoV = SatL->p() / (SatL->gas_constant() *
T);
987 rhoV = p / (SatL->gas_constant() *
T);
990 SatV->update_DmolarT_direct(rhoV,
T);
992 SatL->update_DmolarT_direct(rhoL,
T);
993 SatV->update_DmolarT_direct(rhoV,
T);
996 std::cout <<
format(
"[Maxwell] ancillaries T: %0.16Lg rhoL: %0.16Lg rhoV: %0.16Lg pL: %g pV: %g\n",
T, rhoL, rhoV, SatL->p(),
1003 if (std::abs((SatL->p() - p) / p) > 0.1) {
1004 for (
int iii = 0; iii < 6; ++iii) {
1009 CoolPropDbl deltarhoLHalley = -(2 * f * dpdrho) / (2 *
POW2(dpdrho) - f * d2pdrho2);
1010 rhoL += deltarhoLHalley;
1011 if (std::abs(deltarhoLHalley / rhoL) <
DBL_EPSILON) {
1014 SatL->update_DmolarT_direct(rhoL,
T);
1018 SatL->update_DmolarT_direct(rhoL,
T);
1019 SatV->update_DmolarT_direct(rhoV,
T);
1024 if (std::abs((SatV->p() - p) / p) > 0.1) {
1026 rhoV = SatV->solver_rho_Tp(
T, p, rhoV);
1044 SatL->update_DmolarT_direct(rhoL,
T);
1045 SatV->update_DmolarT_direct(rhoV,
T);
1047 std::cout <<
format(
"[Maxwell] starting T: %0.16Lg rhoL: %Lg rhoV: %Lg pL: %Lg pV: %g\n",
T, rhoL, rhoV, SatL->p(), SatV->p());
1052 CoolPropDbl vL = 1 / SatL->rhomolar(), vV = 1 / SatV->rhomolar();
1060 CoolPropDbl helmholtzL = (SatL->calc_alpha0() + SatL->calc_alphar()) * RT;
1061 CoolPropDbl helmholtzV = (SatV->calc_alpha0() + SatV->calc_alphar()) * RT;
1064 CoolPropDbl pM = (helmholtzL - helmholtzV) / (vV - vL);
1068 CoolPropDbl B = alphaL * (pL - pV - alphaV * (vL - vV));
1072 CoolPropDbl sqrt_arg = std::abs(B * B / (4 * A * A) - C / A);
1076 if (std::abs(sqrt_arg) > 1e-10) {
1077 DeltavL = -0.5 * B / A +
sign((alphaL - alphaV) / alphaV) * sqrt(sqrt_arg);
1081 DeltavL = -0.5 * B / A +
sign((alphaL - alphaV) / alphaV) * sqrt(sqrt_arg * powerfactor) / sqrt(powerfactor);
1083 DeltavV = (pL - pV + alphaL * DeltavL) / alphaV;
1086 rhoL = 1 / (vL + DeltavL);
1087 rhoV = 1 / (vV + DeltavV);
1088 if (B * B / (4 * A * A) - C / A < -10 *
DBL_EPSILON) {
1094 SatL->update_DmolarT_direct(rhoL,
T);
1095 SatV->update_DmolarT_direct(rhoV,
T);
1098 error = std::abs((SatL->p() - SatV->p()) / SatL->p());
1101 std::cout <<
format(
"[Maxwell] rhoL: %0.16Lg rhoV: %0.16Lg error: %Lg dvL/vL: %Lg dvV/vV: %Lg pL: %Lg pV: %Lg\n", rhoL, rhoV, error,
1102 DeltavL / vL, DeltavV / vV, pL, pV);
1107 if (std::abs(DeltavL * rhoL) < 1e-13 || std::abs(DeltavV * rhoV) < 1e-13) {
1112 if (std::abs(error) > std::abs(last_error)) {
1113 backwards_step_count++;
1119 throw SolutionError(
format(
"Maxwell solver did not converge after 30 iterations; rhoL: %0.16Lg rhoV: %0.16Lg error: %Lg dvL/vL: %Lg "
1120 "dvV/vV: %Lg pL: %Lg pV: %Lg\n",
1121 rhoL, rhoV, error, DeltavL / vL, DeltavV / vV, pL, pV));
1123 }
while ((SatL->p() < 0) || (error > 1e-10 && small_step_count < 4 && backwards_step_count < 6));
1125 std::cout <<
format(
"[Maxwell] pL: %g pV: %g\n", SatL->p(), SatV->p());
1130 std::vector<CoolPropDbl>& x, std::vector<CoolPropDbl>& y) {
1131 for (
unsigned int i = 0; i < K.size(); i++) {
1132 double denominator = (1 - beta + beta * K[i]);
1133 x[i] = z[i] / denominator;
1134 y[i] = K[i] * z[i] / denominator;
1139 const std::vector<CoolPropDbl>& z, std::vector<CoolPropDbl>& K,
mixture_VLE_IO& options) {
1141 CoolPropDbl change = NAN, f = NAN, df = NAN, deriv_liq = NAN, deriv_vap = NAN;
1142 std::size_t N = z.size();
1143 std::vector<CoolPropDbl> ln_phi_liq, ln_phi_vap;
1144 ln_phi_liq.resize(N);
1145 ln_phi_vap.resize(N);
1147 std::vector<CoolPropDbl>&x = HEOS.
SatL->get_mole_fractions_ref(), &y = HEOS.
SatV->get_mole_fractions_ref();
1156 HEOS.
SatL->set_mole_fractions(x);
1157 HEOS.
SatV->set_mole_fractions(y);
1158 HEOS.
SatL->calc_reducing_state();
1159 HEOS.
SatV->calc_reducing_state();
1165 double summer_c = 0, v_SRK = 1 / rhomolar_liq;
1166 const std::vector<CoolPropFluid>& components = HEOS.
get_components();
1167 for (std::size_t i = 0; i < components.size(); ++i) {
1174 summer_c += z[i] * (0.40768 * R * Tc / pc * (0.29441 - pc / (rhomolarc * R * Tc)));
1176 rhomolar_liq = 1 / (v_SRK - summer_c);
1177 HEOS.
SatL->update_TP_guessrho(
T, p, rhomolar_liq);
1178 HEOS.
SatV->update_TP_guessrho(
T, p, rhomolar_vap);
1181 HEOS.
SatL->update_TP_guessrho(
T, p, HEOS.
SatL->rhomolar());
1182 HEOS.
SatV->update_TP_guessrho(
T, p, HEOS.
SatV->rhomolar());
1188 for (std::size_t i = 0; i < N; ++i) {
1202 K[i] = exp(ln_phi_liq[i] - ln_phi_vap[i]);
1204 f += z[i] * (K[i] - 1) / (1 - beta + beta * K[i]);
1206 double dfdK = K[i] * z[i] / pow(1 - beta + beta * K[i], (
int)2);
1207 df += dfdK * (deriv_liq - deriv_vap);
1210 if (std::abs(df) <= 1e-14) {
1211 if (std::abs(f) <= 1e-12)
1215 throw ValueError(
format(
"df very small (df = %g) in successive_substitution but f is not converged (f = %g > 1e-12).", df, f));
1225 if (std::abs(change) > 0.05 * p) {
1228 p += omega * change;
1234 HEOS.
SatL->set_mole_fractions(x);
1235 HEOS.
SatV->set_mole_fractions(y);
1239 throw ValueError(
format(
"saturation_p was unable to reach a solution within 50 iterations"));
1241 }
while (std::abs(f) > 1e-12 && iter < options.
Nstep_max);
1243 HEOS.
SatL->update_TP_guessrho(
T, p, HEOS.
SatL->rhomolar());
1244 HEOS.
SatV->update_TP_guessrho(
T, p, HEOS.
SatV->rhomolar());
1246 options.
p = HEOS.
SatL->p();
1247 options.
T = HEOS.
SatL->T();
1261 J.resize(
N + 1,
N + 1);
1279 std::vector<CoolPropDbl> x0 = x;
1280 Eigen::VectorXd r0 = r;
1281 Eigen::MatrixXd J0 = J;
1287 double dT = 1e-3, T1 =
T + dT, T2 =
T - dT;
1289 this->rhomolar_liq = rhomolar_liq0;
1290 this->rhomolar_vap = rhomolar_vap0;
1292 Eigen::VectorXd r1 = r;
1294 this->rhomolar_liq = rhomolar_liq0;
1295 this->rhomolar_vap = rhomolar_vap0;
1297 Eigen::VectorXd r2 = r;
1299 Eigen::VectorXd diffn = (r1 - r2) / (2 * dT);
1300 std::cout <<
format(
"For T\n");
1308 this->rhomolar_liq = rhomolar_liq0 + drho;
1309 this->rhomolar_vap = rhomolar_vap0;
1311 Eigen::VectorXd rr1 = r;
1313 this->rhomolar_liq = rhomolar_liq0 - drho;
1314 this->rhomolar_vap = rhomolar_vap0;
1316 Eigen::VectorXd rr2 = r;
1318 Eigen::VectorXd diffn = (rr1 - rr2) / (2 * drho);
1319 std::cout <<
format(
"For rho\n");
1323 for (std::size_t i = 0; i < x.size() - 1; ++i) {
1328 this->x[x.size() - 1] -= dx;
1330 this->rhomolar_liq = rhomolar_liq0;
1331 this->rhomolar_vap = rhomolar_vap0;
1333 Eigen::VectorXd r1 = this->r;
1337 this->x[x.size() - 1] += dx;
1339 this->rhomolar_liq = rhomolar_liq0;
1340 this->rhomolar_vap = rhomolar_vap0;
1342 Eigen::VectorXd r2 = this->r;
1344 Eigen::VectorXd diffn = (r1 - r2) / (2 * dx);
1345 std::cout <<
format(
"For x%d N %d\n", i, N);
1392 Eigen::VectorXd v = J.colPivHouseholderQr().solve(-r);
1395 for (
unsigned int i = 0; i < N - 1; ++i) {
1396 err_rel[i] = v[i] / y[i];
1399 y[N - 1] = 1 - std::accumulate(y.begin(), y.end() - 1, 0.0);
1401 for (
unsigned int i = 0; i < N - 1; ++i) {
1402 err_rel[i] = v[i] / x[i];
1405 x[N - 1] = 1 - std::accumulate(x.begin(), x.end() - 1, 0.0);
1409 err_rel[N - 1] = v[N - 1] /
T;
1412 err_rel[N - 1] = v[N - 1] / p;
1415 err_rel[N - 1] = v[N - 1] /
T;
1416 rhomolar_liq += v[N];
1417 err_rel[N] = v[N] / rhomolar_liq;
1419 throw ValueError(
"invalid imposed_variable");
1425 min_rel_change = err_rel.cwiseAbs().minCoeff();
1431 }
while (this->error_rms > 1e-7 && min_rel_change > 1000 *
DBL_EPSILON && iter < IO.
Nstep_max);
1440 const std::vector<CoolPropFluid>& fluidsL = HEOS.
SatL->get_components();
1441 const std::vector<CoolPropFluid>& fluidsV = HEOS.
SatV->get_components();
1442 if (!fluidsL.empty() && !fluidsV.empty()) {
1464 rSatV.set_mole_fractions(y);
1473 rSatV.update_TP_guessrho(
T, p, rhomolar_vap);
1474 rhomolar_vap = rSatV.rhomolar();
1476 throw ValueError(
"imposed variable not set for NR VLE");
1482 p = 0.5 * (p_liq + p_vap);
1492 for (std::size_t i = 0; i < N; ++i) {
1496 r(i) = ln_f_liq - ln_f_vap;
1498 for (std::size_t j = 0; j < N - 1; ++j) {
1512 r(N) = p_liq - p_vap;
1513 for (std::size_t j = 0; j < N - 1; ++j) {
1523 for (std::size_t i = 0; i < N; ++i) {
1527 r(i) = ln_f_liq - ln_f_vap;
1529 for (std::size_t j = 0; j < N - 1; ++j) {
1543 for (std::size_t i = 0; i < N; ++i) {
1547 r(i) = ln_f_liq - ln_f_vap;
1549 for (std::size_t j = 0; j < N - 1; ++j) {
1563 error_rms = r.norm();
1568 for (std::size_t i = 0; i < N; ++i) {
1576 dTsat_dPsat = -dQ_dPsat / dQ_dTsat;
1577 dPsat_dTsat = -dQ_dTsat / dQ_dPsat;
1603 r.resize(2 * N - 1);
1604 J.resize(2 * N - 1, 2 * N - 1);
1605 err_rel.resize(2 * N - 1);
1619 throw ValueError(
"newton_raphson_twophase::call produced a non-finite residual");
1624 Eigen::VectorXd v = J.colPivHouseholderQr().solve(-r);
1632 const double step_safety = 0.8;
1637 double dx_last = 0.0, dy_last = 0.0;
1638 for (std::size_t i = 0; i < N - 1; ++i) {
1639 const double dx = v[i], dy = v[i + (N - 1)];
1642 if (x[i] + dx <= 0.0) {
1643 tau = std::min(tau, step_safety * (-x[i] / dx));
1644 }
else if (x[i] + dx >= 1.0) {
1645 tau = std::min(tau, step_safety * ((1.0 - x[i]) / dx));
1647 if (y[i] + dy <= 0.0) {
1648 tau = std::min(tau, step_safety * (-y[i] / dy));
1649 }
else if (y[i] + dy >= 1.0) {
1650 tau = std::min(tau, step_safety * ((1.0 - y[i]) / dy));
1654 const double x_last = x[N - 1], y_last = y[N - 1];
1655 if (x_last + dx_last <= 0.0) {
1656 tau = std::min(tau, step_safety * (-x_last / dx_last));
1657 }
else if (x_last + dx_last >= 1.0) {
1658 tau = std::min(tau, step_safety * ((1.0 - x_last) / dx_last));
1660 if (y_last + dy_last <= 0.0) {
1661 tau = std::min(tau, step_safety * (-y_last / dy_last));
1662 }
else if (y_last + dy_last >= 1.0) {
1663 tau = std::min(tau, step_safety * ((1.0 - y_last) / dy_last));
1666 for (
unsigned int i = 0; i < N - 1; ++i) {
1667 err_rel[i] = tau * v[i] / x[i];
1669 err_rel[i + (N - 1)] = tau * v[i + (N - 1)] / y[i];
1670 y[i] += tau * v[i + (N - 1)];
1672 x[N - 1] = 1 - std::accumulate(x.begin(), x.end() - 1, 0.0);
1673 y[N - 1] = 1 - std::accumulate(y.begin(), y.end() - 1, 0.0);
1676 T += tau * v[2 * N - 2];
1677 err_rel[2 * N - 2] = tau * v[2 * N - 2] /
T;
1679 p += tau * v[2 * N - 2];
1680 err_rel[2 * N - 2] = tau * v[2 * N - 2] / p;
1682 throw ValueError(
"invalid imposed_variable");
1687 min_rel_change = err_rel.cwiseAbs().maxCoeff();
1693 }
while (this->error_rms > 1e-9 && min_rel_change > 1000 *
DBL_EPSILON && iter < IO.
Nstep_max);
1699 if (!
ValidNumber(error_rms) || error_rms > 1e-7) {
1700 throw ValueError(
format(
"newton_raphson_twophase::call did not converge (error_rms = %g)",
static_cast<double>(error_rms)));
1731 rSatV.set_mole_fractions(y);
1738 rSatV.update_TP_guessrho(
T, p, rhomolar_vap);
1739 rhomolar_vap = rSatV.rhomolar();
1744 p = 0.5 * (p_liq + p_vap);
1753 for (std::size_t i = 0; i < N; ++i) {
1757 r[i] = ln_f_liq - ln_f_vap;
1761 r[i + N] = (z[i] - x[i]) / (y[i] - x[i]) - beta;
1766 for (std::size_t i = 0; i < N; ++i) {
1767 for (std::size_t j = 0; j < N - 1; ++j) {
1784 for (std::size_t i = 0; i < N - 1; ++i) {
1785 std::size_t k = i + N;
1786 J(k, i) = (z[i] - y[i]) / pow(y[i] - x[i], 2);
1787 J(k, i + (N - 1)) = -(z[i] - x[i]) / pow(y[i] - x[i], 2);
1790 error_rms = r.norm();
1796 const std::vector<double>&z, &lnK;
1800 double call(
double beta)
override {
1815static CoolPropDbl rachford_rice_beta_bisect(
const std::vector<CoolPropDbl>& z,
const std::vector<CoolPropDbl>& K) {
1817 for (
int it = 0; it < 100; ++it) {
1820 for (std::size_t i = 0; i < z.size(); ++i)
1821 g += z[i] * (K[i] - 1.0) / (1.0 - beta + beta * K[i]);
1827 return 0.5 * (a + b);
1832 int num_steps,
double tol) {
1837 const std::size_t N = z.size();
1838 std::vector<CoolPropDbl> lnK(N, 0.0), K(N);
1839 for (
int ss = 0; ss < num_steps; ++ss) {
1843 const CoolPropDbl rho_liq_prev = rhomolar_liq, rho_vap_prev = rhomolar_vap;
1844 HEOS.
SatL->set_mole_fractions(x);
1845 HEOS.
SatL->update_TP_guessrho(HEOS.
T(), HEOS.
p(), rhomolar_liq);
1846 HEOS.
SatV->set_mole_fractions(y);
1847 HEOS.
SatV->update_TP_guessrho(HEOS.
T(), HEOS.
p(), rhomolar_vap);
1848 rhomolar_liq = HEOS.
SatL->rhomolar();
1849 rhomolar_vap = HEOS.
SatV->rhomolar();
1853 for (std::size_t i = 0; i < N; ++i) {
1854 CoolPropDbl lnK_new = log(HEOS.
SatL->fugacity_coefficient(i) / HEOS.
SatV->fugacity_coefficient(i));
1859 max_lnK_change = std::max(max_lnK_change, std::abs(lnK_new - lnK[i]));
1866 g0 += z[i] * (K[i] - 1.0);
1867 g1 += z[i] * (1.0 - 1.0 / K[i]);
1873 rhomolar_liq = rho_liq_prev;
1874 rhomolar_vap = rho_vap_prev;
1884 beta = rachford_rice_beta_bisect(z, K);
1889 if (ss > 0 && max_lnK_change < tol)
break;
1904 const std::size_t N = z.size();
1905 std::vector<CoolPropDbl> K(N), lnK(N);
1907 for (std::size_t i = 0; i < N; ++i) {
1908 lnK[i] = Wilson_lnK_factor(HEOS,
T, p, i);
1912 g0 += z[i] * (K[i] - 1.0);
1913 g1 += z[i] * (1.0 - 1.0 / K[i]);
1921 bool bracketed = (g0 > 0 && g1 < 0);
1922 if (require_bracket && !bracketed)
return false;
1924 CoolPropDbl beta = bracketed ? rachford_rice_beta_bisect(z, K) : 0.5;
1934 HEOS.
SatL->set_mole_fractions(x);
1935 rhomolar_liq = HEOS.
SatL->solver_rho_Tp_global(
T, p, HEOS.
SatL->calc_rhomolar_max_bound());
1936 HEOS.
SatV->set_mole_fractions(y);
1937 rhomolar_vap = HEOS.
SatV->solver_rho_Tp_global(
T, p, HEOS.
SatV->calc_rhomolar_max_bound());
1938 if (!
ValidNumber(rhomolar_liq) || rhomolar_liq <= 0 || !
ValidNumber(rhomolar_vap) || rhomolar_vap <= 0)
return false;
1948 lnK.resize(z.size());
1950 double g0 = 0, g1 = 0, beta = -1;
1952 for (
int i = 0; i < static_cast<int>(z.size()); ++i) {
1954 if (m_T < 0 && m_p < 0) {
1956 lnK[i] = SaturationSolvers::Wilson_lnK_factor(HEOS, HEOS.T(), HEOS.p(), i);
1959 lnK[i] = SaturationSolvers::Wilson_lnK_factor(HEOS, m_T, m_p, i);
1962 g0 += z[i] * (K[i] - 1);
1963 g1 += z[i] * (1 - 1 / K[i]);
1972 }
else if (g1 > 0) {
1984 std::cout <<
format(
"1) T: %g p: %g beta: %g\n", HEOS.T(), HEOS.p(), beta);
1992 HEOS.SatL->set_mole_fractions(x);
1993 HEOS.SatL->calc_reducing_state();
1994 HEOS.SatV->set_mole_fractions(y);
1995 HEOS.SatV->calc_reducing_state();
1998 std::cout <<
format(
"2) SS1: i beta K x y rho' rho''\n");
2000 for (
int step_count = 0; step_count < num_steps; ++step_count) {
2002 HEOS.SatL->set_mole_fractions(x);
2003 HEOS.SatV->set_mole_fractions(y);
2004 HEOS.SatL->calc_reducing_state();
2005 HEOS.SatV->calc_reducing_state();
2007 this->rho_TP_global();
2010 double g0 = 0, g1 = 0;
2011 for (std::size_t i = 0; i < z.size(); ++i) {
2012 lnK[i] = log(HEOS.SatL->fugacity_coefficient(i) / HEOS.SatV->fugacity_coefficient(i));
2014 g0 += z[i] * (K[i] - 1);
2015 g1 += z[i] * (1 - 1 / K[i]);
2020 }
else if (g1 > 0) {
2033 vec_to_string(y,
"%0.6f").c_str(), rhomolar_liq, rhomolar_vap);
2048 if (use_michelsen) {
2049 check_stability_michelsen();
2051 check_stability_legacy();
2066 bool warm_ok =
false;
2077 warm_ok =
ValidNumber(r) && r > 0 && r < 2.0 * rho_warm && r > 0.5 * rho_warm;
2095 const std::size_t N = z.size();
2096 CoolPropDbl the_T = (m_T > 0 && m_p > 0) ? m_T : HEOS.T();
2097 CoolPropDbl the_p = (m_T > 0 && m_p > 0) ? m_p : HEOS.p();
2100 bool any_uncertain =
false;
2103 HEOS.SatL->set_mole_fractions(z);
2106 rho_b = HEOS.SatL->solver_rho_Tp_global(the_T, the_p, HEOS.SatL->calc_rhomolar_max_bound());
2111 rho_b = HEOS.SatL->solver_rho_Tp(the_T, the_p);
2112 HEOS.SatL->unspecify_phase();
2114 HEOS.SatL->update_DmolarT_direct(rho_b, the_T);
2116 std::vector<CoolPropDbl> ln_f_z(N);
2117 for (std::size_t i = 0; i < N; ++i) {
2119 ln_f_z[i] = std::log(z[i]) + std::log(HEOS.SatL->fugacity_coefficient(i));
2126 std::vector<CoolPropDbl> yV(N), xL(N);
2127 for (std::size_t i = 0; i < N; ++i) {
2128 double Ki = std::exp(SaturationSolvers::Wilson_lnK_factor(HEOS, the_T, the_p, i));
2134 std::vector<std::vector<CoolPropDbl>> trials = {yV, xL};
2135 for (std::size_t t = 0; t < trials.size(); ++t) {
2136 auto& Y = trials[t];
2144 const int max_ss_loops = 4;
2145 const double cntol = 1e-7;
2146 bool ss_decided =
false;
2148 for (
int loop = 0; loop < max_ss_loops && !ss_decided; ++loop) {
2149 std::array<double, 2> esq_pair = {0, 0};
2150 std::vector<CoolPropDbl> err(N);
2152 for (
int kk = 0; kk < 2 && !ss_decided; ++kk) {
2155 for (std::size_t i = 0; i < N; ++i)
2157 std::vector<CoolPropDbl> y_norm(N);
2158 for (std::size_t i = 0; i < N; ++i)
2159 y_norm[i] = Y[i] / sumY;
2162 HEOS.SatV->set_mole_fractions(y_norm);
2164 solve_trial_rho_warm(*HEOS.SatV, the_T, the_p, rho_warm);
2174 for (std::size_t i = 0; i < N; ++i) {
2175 double ln_phi_y = std::log(HEOS.SatV->fugacity_coefficient(i));
2176 double ln_Y_new = ln_f_z[i] - ln_phi_y;
2177 double ln_Y_old = std::log(std::max(Y[i], 1e-300));
2178 double diff = ln_Y_new - ln_Y_old;
2180 esq += Y[i] * diff * diff;
2181 gmax = std::max(gmax, std::abs(diff));
2183 double s_i = ln_Y_old + ln_phi_y - ln_f_z[i];
2184 tm += Y[i] * (s_i - 1.0);
2186 Y[i] = std::exp(ln_Y_new);
2193 for (std::size_t i = 0; i < N; ++i)
2195 for (std::size_t i = 0; i < N; ++i)
2196 y_norm[i] = Y[i] / sY;
2215 for (std::size_t i = 0; i < N; ++i) {
2216 double zysq = std::sqrt(Y[i] * z[i]);
2217 distance_sq += Y[i] + z[i] - 2.0 * zysq;
2218 curvature -= (Y[i] - zysq) * err[i];
2220 if (distance_sq < 0) distance_sq = -distance_sq;
2221 if (std::sqrt(distance_sq) < 0.1 && curvature > 0 && tm / curvature > 0.8) {
2229 if (ss_decided)
break;
2232 if (esq_pair[0] > 0) {
2233 double ratio = std::sqrt(esq_pair[1] / esq_pair[0]);
2236 if (!
ValidNumber(ratio) || ratio < 0 || ratio >= 0.95) ratio = 0.95;
2237 double factor = ratio / (1.0 - ratio);
2238 for (std::size_t i = 0; i < N; ++i) {
2239 double ln_Y = std::log(std::max(Y[i], 1e-300));
2240 ln_Y += factor * err[i];
2241 Y[i] = std::exp(ln_Y);
2249 bool trial_unstable =
false;
2250 bool trial_ok = minimize_tpd(Y, ln_f_z, the_T, the_p, trial_unstable);
2251 if (!trial_ok) any_uncertain =
true;
2253 if (trial_unstable) {
2256 for (std::size_t i = 0; i < N; ++i)
2258 std::vector<CoolPropDbl> y_norm(N);
2259 for (std::size_t i = 0; i < N; ++i)
2260 y_norm[i] = Y[i] / sY;
2275 _uncertain = any_uncertain;
2291 const std::size_t N = Y.size();
2292 const double cntol = 1e-7;
2293 const int max_iter = 20;
2294 is_unstable =
false;
2297 std::vector<CoolPropDbl> alpha(N), alpha_old(N);
2298 for (std::size_t i = 0; i < N; ++i)
2299 alpha[i] = 2.0 * std::sqrt(std::max(Y[i], 1e-300));
2301 double trust_radius = 0.25;
2302 double diagonal_shift = 0.0;
2305 for (
int iter = 0; iter < max_iter; ++iter) {
2307 for (std::size_t i = 0; i < N; ++i) {
2308 Y[i] = (0.5 * alpha[i]) * (0.5 * alpha[i]);
2309 alpha_old[i] = alpha[i];
2314 for (std::size_t i = 0; i < N; ++i)
2316 std::vector<CoolPropDbl> y_norm(N);
2317 for (std::size_t i = 0; i < N; ++i)
2318 y_norm[i] = Y[i] / sumY;
2320 HEOS.SatV->set_mole_fractions(y_norm);
2322 solve_trial_rho_warm(*HEOS.SatV, the_T, the_p, rho_warm);
2331 std::vector<CoolPropDbl> scaled_grad(N), grad(N), half_alpha(N);
2332 double max_gradient = 0, obj_old = 1.0;
2334 for (std::size_t i = 0; i < N; ++i) {
2335 half_alpha[i] = alpha[i] * 0.5;
2337 double ln_Y = std::log(std::max(Y[i], 1e-300));
2338 double ln_phi = std::log(HEOS.SatV->fugacity_coefficient(i));
2339 scaled_grad[i] = ln_Y + ln_phi - ln_f_z[i];
2343 grad[i] = -scaled_grad[i] * half_alpha[i];
2344 max_gradient = std::max(max_gradient, std::abs(grad[i]));
2345 obj_old += Y[i] * (scaled_grad[i] - 1.0);
2349 if (max_gradient < cntol) {
2350 is_unstable = (obj_old < -cntol);
2355 Eigen::MatrixXd
H(N, N);
2356 for (std::size_t i = 0; i < N; ++i) {
2357 double ahi = half_alpha[i] / sumY;
2358 for (std::size_t j = 0; j <= i; ++j) {
2360 double term = ahi * half_alpha[j] * dln_phi_dnj;
2364 H(i, i) += 1.0 + 0.5 * scaled_grad[i];
2369 Eigen::VectorXd g_vec(N), delta_alpha(N);
2370 for (std::size_t i = 0; i < N; ++i)
2374 const int max_inner = 20;
2375 bool step_accepted =
false;
2376 for (
int inner = 0; inner < max_inner; ++inner) {
2377 Eigen::MatrixXd H_shifted =
H;
2378 H_shifted.diagonal().array() += diagonal_shift;
2380 delta_alpha = H_shifted.colPivHouseholderQr().solve(g_vec);
2383 double step_norm_sq = 0;
2384 for (std::size_t i = 0; i < N; ++i) {
2385 double da = delta_alpha(i);
2386 if (da + alpha_old[i] <= 0) da = -0.9 * alpha_old[i];
2387 delta_alpha(i) = da;
2388 alpha[i] = alpha_old[i] + da;
2389 Y[i] = (0.5 * alpha[i]) * (0.5 * alpha[i]);
2390 step_norm_sq += da * da;
2392 double step_size = std::sqrt(step_norm_sq);
2395 if (step_size > trust_radius && diagonal_shift == 0) {
2397 diagonal_shift = step_size / trust_radius - 1.0;
2399 for (std::size_t i = 0; i < N; ++i) {
2400 alpha[i] = alpha_old[i];
2401 Y[i] = (0.5 * alpha[i]) * (0.5 * alpha[i]);
2408 for (std::size_t i = 0; i < N; ++i)
2410 for (std::size_t i = 0; i < N; ++i)
2411 y_norm[i] = Y[i] / sumY;
2413 HEOS.SatV->set_mole_fractions(y_norm);
2415 solve_trial_rho_warm(*HEOS.SatV, the_T, the_p, rho_warm);
2418 trust_radius = step_size / 3.0;
2420 for (std::size_t i = 0; i < N; ++i) {
2421 alpha[i] = alpha_old[i];
2422 Y[i] = (0.5 * alpha[i]) * (0.5 * alpha[i]);
2427 double obj_new = 1.0;
2428 for (std::size_t i = 0; i < N; ++i) {
2429 double ln_Y = std::log(std::max(Y[i], 1e-300));
2430 double ln_phi = std::log(HEOS.SatV->fugacity_coefficient(i));
2431 obj_new += Y[i] * (ln_Y + ln_phi - ln_f_z[i] - 1.0);
2435 if (obj_new < -cntol) {
2441 if (obj_new > obj_old + 1e-12) {
2443 trust_radius = step_size / 3.0;
2445 for (std::size_t i = 0; i < N; ++i) {
2446 alpha[i] = alpha_old[i];
2447 Y[i] = (0.5 * alpha[i]) * (0.5 * alpha[i]);
2453 Eigen::VectorXd Hd =
H * delta_alpha;
2454 double predicted = 0.5 * delta_alpha.dot(Hd) - delta_alpha.dot(g_vec);
2455 double actual = obj_new - obj_old;
2456 double ratio = (predicted != 0) ? actual / predicted : 1.0;
2459 trust_radius = step_size / 2.0;
2460 }
else if (ratio > 0.75 && diagonal_shift > 0) {
2461 trust_radius = step_size * 2.0;
2463 trust_radius = step_size;
2466 step_accepted =
true;
2470 if (!step_accepted) {
2479 std::vector<double> tpdL, tpdH;
2482 double the_T = (m_T > 0 && m_p > 0) ? m_T : HEOS.T();
2483 double the_p = (m_T > 0 && m_p > 0) ? m_p : HEOS.p();
2489 HEOS.SatL->set_mole_fractions(z);
2490 HEOS.SatV->set_mole_fractions(z);
2491 HEOS.SatL->calc_reducing_state();
2492 HEOS.SatV->calc_reducing_state();
2495 double rhoL = HEOS.SatL->solver_rho_Tp_global(the_T, the_p, HEOS.SatL->calc_rhomolar_max_bound());
2496 double rhoV = HEOS.SatV->solver_rho_Tp_global(the_T, the_p, HEOS.SatV->calc_rhomolar_max_bound());
2497 HEOS.SatL->update_DmolarT_direct(rhoL, the_T);
2498 HEOS.SatV->update_DmolarT_direct(rhoV, the_T);
2502 this->tpd_liq = HEOS.SatL->tangent_plane_distance(the_T, the_p, x, rhomolar_liq);
2503 this->tpd_vap = HEOS.SatV->tangent_plane_distance(the_T, the_p, y, rhomolar_vap);
2505 this->DELTAG_nRT = (1 - beta) * tpd_liq + beta * (tpd_vap);
2507 std::cout <<
format(
"3) tpd': %g tpd'': %g DELTAG/nRT: %g\n", tpd_liq, tpd_vap, DELTAG_nRT);
2511 if (this->tpd_liq < -DBL_EPSILON || this->tpd_vap < -DBL_EPSILON || this->DELTAG_nRT < -
DBL_EPSILON) {
2513 std::cout <<
format(
"3) PHASE SPLIT beta in (eps,1-eps) \n");
2523 CoolPropDbl rho_bulk = HEOS.solver_rho_Tp_global(the_T, the_p, HEOS.calc_rhomolar_max_bound());
2524 HEOS.update_DmolarT_direct(rho_bulk, the_T);
2527 std::vector<double> fugacity_coefficient0(z.size()), fugacity0(z.size());
2528 for (std::size_t i = 0; i < z.size(); ++i) {
2534 xL.resize(z.size());
2535 xH.resize(z.size());
2536 for (std::size_t i = 0; i < z.size(); ++i) {
2537 xL[i] = z[i] * K0[i];
2538 xH[i] = z[i] / K0[i];
2545 std::cout <<
format(
"3) SS2: i x' x'' rho' rho'' tpd' tpd''\n");
2551 double diffbulkL = 0, diffbulkH = 0;
2552 for (
int step_count = 0; step_count < 100; ++step_count) {
2555 HEOS.SatL->set_mole_fractions(xH);
2556 HEOS.SatV->set_mole_fractions(xL);
2557 HEOS.SatL->calc_reducing_state();
2558 HEOS.SatV->calc_reducing_state();
2563 double tpd_L = 0, tpd_H = 0;
2564 for (std::size_t i = 0; i < xL.size(); ++i) {
2568 tpdL.push_back(tpd_L);
2569 tpdH.push_back(tpd_H);
2572 diffbulkL = 0, diffbulkH = 0;
2573 for (std::size_t i = 0; i < z.size(); ++i) {
2574 xL[i] = z[i] * fugacity_coefficient0[i] / HEOS.SatV->fugacity_coefficient(i);
2575 diffbulkL += std::abs(xL[i] - z[i]);
2576 xH[i] = z[i] * fugacity_coefficient0[i] / HEOS.SatL->fugacity_coefficient(i);
2577 diffbulkH += std::abs(xH[i] - z[i]);
2583 rhomolar_liq, rhomolar_vap, tpd_L, tpd_H);
2587 if (diffbulkL < 1e-2 || diffbulkH < 1e-2) {
2593 if (tpd_L < -1e-12 || tpd_H < -1e-12) {
2598 if (diffbulkH > 0.25 || diffbulkL > 0.25) {
2607 double the_T = (m_T > 0 && m_p > 0) ? m_T : HEOS.T();
2608 double the_p = (m_T > 0 && m_p > 0) ? m_p : HEOS.p();
2611 double rhoL = HEOS.SatL->solver_rho_Tp_global(the_T, the_p, HEOS.SatL->calc_rhomolar_max_bound());
2612 double rhoV = HEOS.SatV->solver_rho_Tp_global(the_T, the_p, HEOS.SatV->calc_rhomolar_max_bound());
2613 HEOS.SatL->update_DmolarT_direct(rhoL, the_T);
2614 HEOS.SatV->update_DmolarT_direct(rhoV, the_T);
2616 rhomolar_liq = HEOS.SatL->rhomolar();
2617 rhomolar_vap = HEOS.SatV->rhomolar();
2623 if (m_T > 0 && m_p > 0) {
2624 HEOS.SatL->update_TP_guessrho(m_T, m_p, rhomolar_liq);
2625 HEOS.SatV->update_TP_guessrho(m_T, m_p, rhomolar_vap);
2627 HEOS.SatL->update_TP_guessrho(HEOS.T(), HEOS.p(), rhomolar_liq);
2628 HEOS.SatV->update_TP_guessrho(HEOS.T(), HEOS.p(), rhomolar_vap);
2630 rhomolar_liq = HEOS.SatL->rhomolar();
2631 rhomolar_vap = HEOS.SatV->rhomolar();
2637 if (m_T > 0 && m_p > 0) {
2638 rhomolar_liq = HEOS.SatL->solver_rho_Tp_SRK(m_T, m_p,
iphase_liquid);
2639 rhomolar_vap = HEOS.SatV->solver_rho_Tp_SRK(m_T, m_p,
iphase_gas);
2641 rhomolar_liq = HEOS.SatL->solver_rho_Tp_SRK(HEOS.T(), HEOS.p(),
iphase_liquid);
2642 rhomolar_vap = HEOS.SatV->solver_rho_Tp_SRK(HEOS.T(), HEOS.p(),
iphase_gas);
2646 if (HEOS.backend_name().find(
"Helmholtz") == 0) {
2649 double summer_c = 0, v_SRK = 1 / rhomolar_liq;
2650 for (std::size_t i = 0; i < z.size(); ++i) {
2655 summer_c += z[i] * (0.40768 * R * Tc / pc * (0.29441 - pc / (rhomolarc * R * Tc)));
2657 rhomolar_liq = 1 / (v_SRK - summer_c);
2678 const std::size_t N = IO.x.size();
2684 IO.nonconvergence =
false;
2691 std::vector<CoolPropDbl> lnK(N);
2692 for (std::size_t i = 0; i < N; ++i) {
2693 double ratio = IO.y[i] / std::max(IO.x[i], 1e-300);
2694 lnK[i] = std::log(std::max(ratio, 1e-300));
2703 for (std::size_t i = 0; i < N; ++i) {
2705 IO.nonconvergence =
true;
2706 throw SolutionError(
format(
"PTflash_twophase::solve_michelsen got a non-finite seed at T = %g K, p = %g Pa",
static_cast<double>(IO.T),
2707 static_cast<double>(IO.p)));
2712 auto solve_rachford_rice = [&]() {
2717 if (!
ValidNumber(beta) || beta < 0.0 || beta > 1.0) beta = 0.5;
2718 for (
int rr_iter = 0; rr_iter < 50; ++rr_iter) {
2720 for (std::size_t i = 0; i < N; ++i) {
2730 r += IO.z[i] * term / denom;
2731 dr -= IO.z[i] * term * term / (denom * denom);
2737 if (std::abs(r) < 1e-11)
break;
2743 if (!
ValidNumber(beta_new) || beta_new <= beta_min || beta_new >= beta_max) beta_new = 0.5 * (beta_min + beta_max);
2744 if (std::abs(beta_new - beta) < 1e-11)
break;
2747 for (std::size_t i = 0; i < N; ++i) {
2751 IO.x[i] = IO.z[i] / (1.0 + beta * (Ki - 1.0));
2752 IO.y[i] = Ki * IO.x[i];
2759 auto evaluate_phases = [&]() ->
bool {
2760 HEOS.SatL->set_mole_fractions(IO.x);
2762 IO.rhomolar_liq = solve_trial_rho_warm(*HEOS.SatL, IO.T, IO.p, rho_warm_L);
2766 HEOS.SatV->set_mole_fractions(IO.y);
2768 IO.rhomolar_vap = solve_trial_rho_warm(*HEOS.SatV, IO.T, IO.p, rho_warm_V);
2778 const int max_ss_loops = 4;
2779 const double ss_tol = 1e-7;
2780 bool ss_converged =
false;
2782 for (
int loop = 0; loop < max_ss_loops && !ss_converged; ++loop) {
2783 std::array<double, 2> esq_pair = {0, 0};
2784 std::vector<CoolPropDbl> err(N);
2786 for (
int kk = 0; kk < 2 && !ss_converged; ++kk) {
2787 solve_rachford_rice();
2788 if (!evaluate_phases()) {
2789 throw SolutionError(
"PT flash lost a phase density solve during successive substitution");
2793 for (std::size_t i = 0; i < N; ++i) {
2794 double lnK_new = std::log(HEOS.SatL->fugacity_coefficient(i)) - std::log(HEOS.SatV->fugacity_coefficient(i));
2795 double diff = lnK_new - lnK[i];
2797 esq += IO.z[i] * diff * diff;
2798 gmax = std::max(gmax, std::abs(diff));
2802 if (gmax < ss_tol) {
2803 ss_converged =
true;
2807 if (ss_converged)
break;
2810 if (esq_pair[0] > 0) {
2811 double ratio = std::sqrt(esq_pair[1] / esq_pair[0]);
2814 if (!
ValidNumber(ratio) || ratio < 0 || ratio >= 0.95) ratio = 0.95;
2815 double factor = ratio / (1.0 - ratio);
2816 for (std::size_t i = 0; i < N; ++i) {
2817 lnK[i] += factor * err[i];
2823 solve_rachford_rice();
2824 if (!evaluate_phases()) {
2825 throw SolutionError(
"PT flash lost a phase density solve after successive substitution");
2834 auto compute_gibbs = [&]() ->
double {
2836 for (std::size_t i = 0; i < N; ++i) {
2837 if (IO.x[i] > 0) G += (1.0 - beta) * IO.x[i] * (std::log(IO.x[i]) + std::log(HEOS.SatL->fugacity_coefficient(i)));
2838 if (IO.y[i] > 0) G += beta * IO.y[i] * (std::log(IO.y[i]) + std::log(HEOS.SatV->fugacity_coefficient(i)));
2843 double G_old = compute_gibbs();
2848 const double gibbs_tol = 1e-9;
2849 const int max_gibbs_iter = 50;
2850 const int max_restart = 2;
2851 const int max_inner = 40;
2852 bool converged =
false;
2853 double last_max_g = 1e300;
2855 for (
int restart = 0; restart < max_restart && !converged; ++restart) {
2858 double trust_radius = (restart == 0) ? 1.0 : 0.2;
2860 for (
int gibbs_iter = 0; gibbs_iter < max_gibbs_iter && !converged; ++gibbs_iter) {
2870 HEOS.SatL->set_mole_fractions(IO.x);
2871 HEOS.SatL->update_DmolarT_direct(IO.rhomolar_liq, IO.T);
2872 HEOS.SatV->set_mole_fractions(IO.y);
2873 HEOS.SatV->update_DmolarT_direct(IO.rhomolar_vap, IO.T);
2881 Eigen::VectorXd g(N), dia(N);
2882 Eigen::MatrixXd
H(N, N);
2884 Eigen::MatrixXd DL(N, N), DV(N, N);
2885 for (std::size_t i = 0; i < N; ++i) {
2886 for (std::size_t j = 0; j < N; ++j) {
2891 for (std::size_t i = 0; i < N; ++i) {
2893 for (std::size_t k = 0; k < N; ++k) {
2894 sum_x_DL += IO.x[k] * DL(i, k);
2895 sum_y_DV += IO.y[k] * DV(i, k);
2897 for (std::size_t j = 0; j < N; ++j) {
2900 H(i, j) = V_frac * dln_phi_L_dnj + L_frac * dln_phi_V_dnj - 1.0;
2902 H(i, i) += (V_frac / IO.x[i]) + (L_frac / IO.y[i]);
2903 CoolPropDbl l_act = std::log(IO.x[i]) + std::log(HEOS.SatL->fugacity_coefficient(i));
2904 CoolPropDbl v_act = std::log(IO.y[i]) + std::log(HEOS.SatV->fugacity_coefficient(i));
2905 g(i) = V_frac * L_frac * (v_act - l_act);
2906 max_g = std::max(max_g, std::abs(v_act - l_act));
2911 CoolPropDbl dia_i = std::sqrt(IO.z[i] / std::max(IO.x[i] * IO.y[i], 1e-300));
2912 dia(i) = (dia_i > 1e-300) ? dia_i : 1.0;
2915 if (max_g < gibbs_tol) {
2921 Eigen::VectorXd gs(N);
2922 Eigen::MatrixXd Hs(N, N);
2923 for (std::size_t i = 0; i < N; ++i) {
2924 gs(i) = g(i) / dia(i);
2925 for (std::size_t j = 0; j < N; ++j) {
2926 Hs(i, j) =
H(i, j) / (dia(i) * dia(j));
2934 double diagonal_shift = 0.0;
2935 bool step_ok =
false;
2936 for (
int inner = 0; inner < max_inner; ++inner) {
2937 Eigen::MatrixXd Hl = Hs;
2938 Hl.diagonal().array() += diagonal_shift;
2941 Eigen::SelfAdjointEigenSolver<Eigen::MatrixXd> es(Hl, Eigen::EigenvaluesOnly);
2942 double min_eig = es.eigenvalues().minCoeff();
2944 if (min_eig < 1e-8) {
2945 diagonal_shift += (1e-8 - min_eig);
2949 Eigen::VectorXd ds = Hl.ldlt().solve(-gs);
2950 double step_norm = ds.norm();
2953 if (step_norm > trust_radius) {
2954 diagonal_shift = (diagonal_shift > 0.0) ? diagonal_shift * 3.0 : (step_norm / trust_radius - 1.0) * std::max(min_eig, 1e-3);
2960 Eigen::VectorXd delta_v(N);
2961 for (std::size_t i = 0; i < N; ++i) {
2962 delta_v(i) = ds(i) / dia(i);
2965 for (std::size_t i = 0; i < N; ++i) {
2967 if (delta_v(i) > 0 && v_old + delta_v(i) > IO.z[i]) pos_scale = std::min(pos_scale, 0.99 * (IO.z[i] - v_old) / delta_v(i));
2968 if (delta_v(i) < 0 && v_old + delta_v(i) < 0) pos_scale = std::min(pos_scale, 0.99 * (-v_old) / delta_v(i));
2972 std::vector<CoolPropDbl> v_new(N), l_new(N);
2973 for (std::size_t i = 0; i < N; ++i) {
2974 v_new[i] = beta * IO.y[i] + pos_scale * delta_v(i);
2975 l_new[i] = IO.z[i] - v_new[i];
2979 std::vector<CoolPropDbl> x_trial(N), y_trial(N);
2980 for (std::size_t i = 0; i < N; ++i) {
2981 y_trial[i] = v_new[i] / V_new;
2982 x_trial[i] = l_new[i] / L_new;
2985 bool eval_ok =
false;
2987 HEOS.SatL->set_mole_fractions(x_trial);
2988 CoolPropDbl rL = HEOS.SatL->solver_rho_Tp_global(IO.T, IO.p, HEOS.SatL->calc_rhomolar_max_bound());
2989 HEOS.SatV->set_mole_fractions(y_trial);
2990 CoolPropDbl rV = HEOS.SatV->solver_rho_Tp_global(IO.T, IO.p, HEOS.SatV->calc_rhomolar_max_bound());
2992 HEOS.SatL->update_DmolarT_direct(rL, IO.T);
2993 HEOS.SatV->update_DmolarT_direct(rV, IO.T);
2996 double beta_save = beta;
2999 CoolPropDbl rL_save = IO.rhomolar_liq, rV_save = IO.rhomolar_vap;
3003 IO.rhomolar_liq = rL;
3004 IO.rhomolar_vap = rV;
3005 double G_new = compute_gibbs();
3007 if (G_new < G_old + 1e-12) {
3012 if (step_norm > 0.8 * trust_radius) trust_radius = std::min(2.0 * trust_radius, 1e3);
3021 IO.rhomolar_liq = rL_save;
3022 IO.rhomolar_vap = rV_save;
3030 trust_radius = 0.5 * step_norm;
3031 if (trust_radius < 1e-10)
break;
3032 diagonal_shift = 0.0;
3034 if (!step_ok)
break;
3044 HEOS.SatL->set_mole_fractions(IO.x);
3045 HEOS.SatL->update_DmolarT_direct(IO.rhomolar_liq, IO.T);
3046 HEOS.SatV->set_mole_fractions(IO.y);
3047 HEOS.SatV->update_DmolarT_direct(IO.rhomolar_vap, IO.T);
3048 double final_max_g = 0;
3049 for (std::size_t i = 0; i < N; ++i) {
3050 double l_act = std::log(IO.x[i]) + std::log(HEOS.SatL->fugacity_coefficient(i));
3051 double v_act = std::log(IO.y[i]) + std::log(HEOS.SatV->fugacity_coefficient(i));
3052 final_max_g = std::max(final_max_g, std::abs(v_act - l_act));
3054 last_max_g = final_max_g;
3055 converged = (final_max_g < gibbs_tol);
3068 for (std::size_t i = 0; i < N; ++i)
3069 spread = std::max(spread, std::abs(IO.x[i] - IO.y[i]));
3070 const bool genuine =
ValidNumber(last_max_g) && last_max_g <= 1e-5
3072 && beta > 1e-8 && beta < 1.0 - 1e-8;
3074 IO.nonconvergence =
true;
3075 throw SolutionError(
format(
"PTflash_twophase::solve_michelsen failed to converge: max|ln f_V - ln f_L| = %g at T = %g K, p = %g Pa",
3076 last_max_g,
static_cast<double>(IO.T),
static_cast<double>(IO.p)));
3082 const std::size_t N = IO.x.size();
3084 double min_rel_change = NAN;
3096 Eigen::VectorXd v = J.colPivHouseholderQr().solve(-r);
3098 for (
unsigned int i = 0; i < N - 1; ++i) {
3099 err_rel[i] = v[i] / IO.x[i];
3101 err_rel[i + (N - 1)] = v[i + (N - 1)] / IO.y[i];
3102 IO.y[i] += v[i + (N - 1)];
3104 IO.x[N - 1] = 1 - std::accumulate(IO.x.begin(), IO.x.end() - 1, 0.0);
3105 IO.y[N - 1] = 1 - std::accumulate(IO.y.begin(), IO.y.end() - 1, 0.0);
3109 min_rel_change = err_rel.cwiseAbs().minCoeff();
3112 if (iter == IO.Nstep_max) {
3113 throw ValueError(
format(
"PTflash_twophase::call reached max number of iterations [%d]", IO.Nstep_max));
3115 }
while (this->error_rms > 1e-9 && min_rel_change > 1000 *
DBL_EPSILON && iter < IO.Nstep_max);
3126 std::size_t i_best = 0;
3128 for (std::size_t i = 0; i < N; ++i) {
3130 if (sep > sep_best) {
3136 IO.beta = (IO.z[i_best] - IO.x[i_best]) / (IO.y[i_best] - IO.x[i_best]);
3145 const std::size_t N = IO.x.size();
3147 r.resize(2 * N - 2);
3148 J.resize(2 * N - 2, 2 * N - 2);
3149 err_rel.resize(2 * N - 2);
3151 HEOS.SatL->set_mole_fractions(IO.x);
3152 HEOS.SatL->update_TP_guessrho(IO.T, IO.p, IO.rhomolar_liq);
3154 HEOS.SatV->set_mole_fractions(IO.y);
3155 HEOS.SatV->update_TP_guessrho(IO.T, IO.p, IO.rhomolar_vap);
3161 for (std::size_t k = 0; k < N; ++k) {
3162 r(k) = log(HEOS.SatL->fugacity(k) / HEOS.SatV->fugacity(k));
3163 for (std::size_t j = 0; j < N - 1; ++j) {
3174 for (std::size_t i = 0; i < N - 2; ++i) {
3175 std::size_t k = i + N;
3176 r(k) = (IO.z[i] - IO.x[i]) / (IO.y[i] - IO.x[i]) - (IO.z[N - 1] - IO.x[N - 1]) / (IO.y[N - 1] - IO.x[N - 1]);
3177 for (std::size_t j = 0; j < N - 2; ++j) {
3178 J(k, j) = (IO.z[j] - IO.x[j]) /
POW2(IO.y[j] - IO.x[j]);
3179 J(k, j + N - 1) = -(IO.z[j] - IO.x[j]) /
POW2(IO.y[j] - IO.x[j]);
3181 std::size_t j = N - 2;
3182 J(k, j) = -(IO.z[j] - IO.x[j]) /
POW2(IO.y[j] - IO.x[j]);
3183 J(k, j + N - 1) = +(IO.z[j] - IO.x[j]) /
POW2(IO.y[j] - IO.x[j]);
3185 this->error_rms = r.norm();
3189#if defined(ENABLE_CATCH)
3190# include <catch2/catch_all.hpp>
3192TEST_CASE(
"Check the PT flash calculation for two-phase inputs",
"[PTflash_twophase]") {
3194 AS->set_mole_fractions(std::vector<double>(2, 0.5));
3200 o.
x = AS->mole_fractions_liquid();
3201 o.
y = AS->mole_fractions_vapor();
3202 o.
z = AS->get_mole_fractions();
3209 solver.build_arrays();
3210 double err = solver.r.norm();
3211 REQUIRE(std::abs(err) < 1e-10);
3214 std::vector<double> x0 = o.
x;
3216 o.
x[1] = 1 - o.
x[0];
3219 double diffx0 = o.
x[0] - x0[0];
3220 REQUIRE(std::abs(diffx0) < 1e-10);
3227TEST_CASE(
"Legacy PT flash recovers the vapor quality (not pinned at 0.5)",
"[PTflash_twophase]") {
3233 const std::string fluids =
"Methane&Ethane";
3234 const std::vector<double> z = {0.6, 0.4};
3235 const double Qtarget = 0.25;
3238 struct StabilityAlgoGuard
3242 StabilityAlgoGuard(
const StabilityAlgoGuard&) =
delete;
3243 StabilityAlgoGuard& operator=(
const StabilityAlgoGuard&) =
delete;
3244 StabilityAlgoGuard(StabilityAlgoGuard&&) =
delete;
3245 StabilityAlgoGuard& operator=(StabilityAlgoGuard&&) =
delete;
3246 ~StabilityAlgoGuard() {
3252 double p_ref, T_ref;
3255 AS->set_mole_fractions(z);
3261 auto flash_Q = [&](
int algo) {
3264 AS->set_mole_fractions(z);
3270 const double Q_michelsen = flash_Q(1);
3271 const double Q_legacy = flash_Q(0);
3274 CHECK(Q_michelsen == Catch::Approx(Qtarget).margin(0.02));
3275 CHECK(Q_legacy == Catch::Approx(Qtarget).margin(0.02));
3278 CHECK(Q_legacy == Catch::Approx(Q_michelsen).margin(0.01));
3279 CHECK(std::abs(Q_legacy - 0.5) > 0.1);