mirror of
https://github.com/CoolProp/CoolProp.git
synced 2026-02-09 21:35:28 -05:00
379 lines
16 KiB
C++
379 lines
16 KiB
C++
#include "VLERoutines.h"
|
|
#include "FlashRoutines.h"
|
|
#include "HelmholtzEOSMixtureBackend.h"
|
|
|
|
namespace CoolProp{
|
|
|
|
void FlashRoutines::PT_flash(HelmholtzEOSMixtureBackend &HEOS)
|
|
{
|
|
// Find the phase, while updating all internal variables possible
|
|
HEOS.T_phase_determination_pure_or_pseudopure(iP, HEOS._p);
|
|
|
|
if (!HEOS.isHomogeneousPhase())
|
|
{
|
|
throw ValueError("twophase not implemented yet");
|
|
}
|
|
else
|
|
{
|
|
// Find density
|
|
HEOS._rhomolar = HEOS.solver_rho_Tp(HEOS._T, HEOS._p);
|
|
}
|
|
}
|
|
|
|
void FlashRoutines::QT_flash(HelmholtzEOSMixtureBackend &HEOS)
|
|
{
|
|
if (HEOS.is_pure_or_pseudopure)
|
|
{
|
|
if (!(HEOS.components[0]->pEOS->pseudo_pure))
|
|
{
|
|
// Set some imput options
|
|
SaturationSolvers::saturation_T_pure_options options;
|
|
options.omega = 1.0;
|
|
options.use_guesses = false;
|
|
// Actually call the solver
|
|
SaturationSolvers::saturation_T_pure(&HEOS, HEOS._T, options);
|
|
// Load the outputs
|
|
HEOS._p = HEOS._Q*HEOS.SatV->p() + (1- HEOS._Q)*HEOS.SatL->p();
|
|
HEOS._rhomolar = 1/(HEOS._Q/HEOS.SatV->rhomolar() + (1 - HEOS._Q)/HEOS.SatL->rhomolar());
|
|
}
|
|
else{
|
|
// Pseudo-pure fluid
|
|
long double rhoLanc, rhoVanc, rhoLsat, rhoVsat;
|
|
long double psatLanc = HEOS.components[0]->ancillaries.pL.evaluate(HEOS._T); // These ancillaries are used explicitly
|
|
long double psatVanc = HEOS.components[0]->ancillaries.pV.evaluate(HEOS._T); // These ancillaries are used explicitly
|
|
try{
|
|
rhoLanc = HEOS.components[0]->ancillaries.rhoL.evaluate(HEOS._T);
|
|
rhoVanc = HEOS.components[0]->ancillaries.rhoV.evaluate(HEOS._T);
|
|
|
|
if (!ValidNumber(rhoLanc) || !ValidNumber(rhoVanc))
|
|
{
|
|
throw ValueError("pseudo-pure failed");
|
|
}
|
|
|
|
rhoLsat = HEOS.solver_rho_Tp(HEOS._T, psatLanc, rhoLanc);
|
|
rhoVsat = HEOS.solver_rho_Tp(HEOS._T, psatVanc, rhoLanc);
|
|
if (!ValidNumber(rhoLsat) || !ValidNumber(rhoVsat) ||
|
|
fabs(rhoLsat/rhoLanc-1) > 0.1 || fabs(rhoVanc/rhoVsat-1) > 0.1)
|
|
{
|
|
throw ValueError("pseudo-pure failed");
|
|
}
|
|
}
|
|
catch (std::exception &){
|
|
// Near the critical point, the behavior is not very nice, so we will just use the ancillary near the critical point
|
|
rhoLsat = rhoLanc;
|
|
rhoVsat = rhoVanc;
|
|
}
|
|
HEOS._p = HEOS._Q*psatVanc + (1-HEOS._Q)*psatLanc;
|
|
HEOS._rhomolar = 1/(HEOS._Q/rhoVsat + (1-HEOS._Q)/rhoLsat);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set some imput options
|
|
SaturationSolvers::mixture_VLE_IO options;
|
|
options.sstype = SaturationSolvers::imposed_T;
|
|
options.Nstep_max = 5;
|
|
|
|
// Get an extremely rough guess by interpolation of ln(p) v. T curve where the limits are mole-fraction-weighted
|
|
long double pguess = SaturationSolvers::saturation_preconditioner(&HEOS, HEOS._T, SaturationSolvers::imposed_T, HEOS.mole_fractions);
|
|
|
|
// Use Wilson iteration to obtain updated guess for pressure
|
|
pguess = SaturationSolvers::saturation_Wilson(&HEOS, HEOS._Q, HEOS._T, SaturationSolvers::imposed_T, HEOS.mole_fractions, pguess);
|
|
|
|
// Actually call the successive substitution solver
|
|
SaturationSolvers::successive_substitution(&HEOS, HEOS._Q, HEOS._T, pguess, HEOS.mole_fractions, HEOS.K, options);
|
|
}
|
|
}
|
|
void FlashRoutines::PQ_flash(HelmholtzEOSMixtureBackend &HEOS)
|
|
{
|
|
if (HEOS.is_pure_or_pseudopure)
|
|
{
|
|
double pc = HEOS.components[0]->pEOS->reduce.p;
|
|
double Tc = HEOS.components[0]->pEOS->reduce.T;
|
|
double Tt = HEOS.components[0]->pEOS->Ttriple;
|
|
double pt = HEOS.components[0]->pEOS->ptriple;
|
|
double Tsat_guess = 1/(1/Tc-(1/Tt-1/Tc)/log(pc/pt)*log(HEOS._p/pc));
|
|
|
|
if (HEOS.components[0]->pEOS->pseudo_pure){
|
|
// It is a psedo-pure mixture
|
|
throw NotImplementedError("PQ_flash not implemented for pseudo-pure fluids");
|
|
}
|
|
else{
|
|
// It is a pure fluid
|
|
|
|
// Set some imput options
|
|
SaturationSolvers::saturation_PHSU_pure_options options;
|
|
// Specified variable is pressure
|
|
options.specified_variable = SaturationSolvers::saturation_PHSU_pure_options::IMPOSED_PV;
|
|
// Use logarithm of delta as independent variables
|
|
options.use_logdelta = false;
|
|
// Actually call the solver
|
|
SaturationSolvers::saturation_PHSU_pure(&HEOS, HEOS._p, options);
|
|
|
|
// Load the outputs
|
|
HEOS._p = HEOS._Q*HEOS.SatV->p() + (1 - HEOS._Q)*HEOS.SatL->p();
|
|
HEOS._rhomolar = 1/(HEOS._Q/HEOS.SatV->rhomolar() + (1 - HEOS._Q)/HEOS.SatL->rhomolar());
|
|
HEOS._T = HEOS.SatL->T();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Set some imput options
|
|
SaturationSolvers::mixture_VLE_IO io;
|
|
io.sstype = SaturationSolvers::imposed_p;
|
|
io.Nstep_max = 20;
|
|
|
|
// Get an extremely rough guess by interpolation of ln(p) v. T curve where the limits are mole-fraction-weighted
|
|
long double Tguess = SaturationSolvers::saturation_preconditioner(&HEOS, HEOS._p, SaturationSolvers::imposed_p, HEOS.mole_fractions);
|
|
|
|
// Use Wilson iteration to obtain updated guess for temperature
|
|
Tguess = SaturationSolvers::saturation_Wilson(&HEOS, HEOS._Q, HEOS._p, SaturationSolvers::imposed_p, HEOS.mole_fractions, Tguess);
|
|
|
|
// Actually call the successive substitution solver
|
|
SaturationSolvers::successive_substitution(&HEOS, HEOS._Q, Tguess, HEOS._p, HEOS.mole_fractions, HEOS.K, io);
|
|
|
|
PhaseEnvelope::PhaseEnvelope_GV ENV_GV;
|
|
ENV_GV.build(&HEOS, HEOS.mole_fractions, HEOS.K, io);
|
|
}
|
|
}
|
|
// D given and one of P,H,S,U
|
|
void FlashRoutines::PHSU_D_flash(HelmholtzEOSMixtureBackend &HEOS, int other)
|
|
{
|
|
// Define the residual to be driven to zero
|
|
class solver_resid : public FuncWrapper1D
|
|
{
|
|
public:
|
|
|
|
HelmholtzEOSMixtureBackend *HEOS;
|
|
long double r, eos, rhomolar, value, T;
|
|
int other;
|
|
|
|
solver_resid(HelmholtzEOSMixtureBackend *HEOS, long double rhomolar, long double value, int other) : HEOS(HEOS), rhomolar(rhomolar), value(value), other(other){};
|
|
double call(double T){
|
|
this->T = T;
|
|
switch(other)
|
|
{
|
|
case iP:
|
|
eos = HEOS->calc_pressure_nocache(T, rhomolar); break;
|
|
case iSmolar:
|
|
eos = HEOS->calc_smolar_nocache(T, rhomolar); break;
|
|
case iHmolar:
|
|
eos = HEOS->calc_hmolar_nocache(T, rhomolar); break;
|
|
case iUmolar:
|
|
eos = HEOS->calc_umolar_nocache(T, rhomolar); break;
|
|
default:
|
|
throw ValueError(format("Input not supported"));
|
|
}
|
|
|
|
r = eos - value;
|
|
return r;
|
|
};
|
|
};
|
|
|
|
std::string errstring;
|
|
|
|
if (HEOS.imposed_phase_index > -1)
|
|
{
|
|
// Use the phase defined by the imposed phase
|
|
HEOS._phase = HEOS.imposed_phase_index;
|
|
}
|
|
else
|
|
{
|
|
if (HEOS.is_pure_or_pseudopure)
|
|
{
|
|
CoolPropFluid * component = HEOS.components[0];
|
|
|
|
shared_ptr<HelmholtzEOSMixtureBackend> Sat;
|
|
long double rhoLtriple = component->triple_liquid.rhomolar;
|
|
long double rhoVtriple = component->triple_vapor.rhomolar;
|
|
// Check if in the "normal" region
|
|
if (HEOS._rhomolar >= rhoVtriple && HEOS._rhomolar <= rhoLtriple)
|
|
{
|
|
long double yL, yV, value, y_solid;
|
|
long double TLtriple = component->triple_liquid.T; ///TODO: separate TL and TV for ppure
|
|
long double TVtriple = component->triple_vapor.T;
|
|
|
|
// First check if solid (below the line connecting the triple point values) - this is an error for now
|
|
switch (other)
|
|
{
|
|
case iSmolar:
|
|
yL = HEOS.calc_smolar_nocache(TLtriple, rhoLtriple); yV = HEOS.calc_smolar_nocache(TVtriple, rhoVtriple); value = HEOS._smolar; break;
|
|
case iHmolar:
|
|
yL = HEOS.calc_hmolar_nocache(TLtriple, rhoLtriple); yV = HEOS.calc_hmolar_nocache(TVtriple, rhoVtriple); value = HEOS._hmolar; break;
|
|
case iUmolar:
|
|
yL = HEOS.calc_umolar_nocache(TLtriple, rhoLtriple); yV = HEOS.calc_umolar_nocache(TVtriple, rhoVtriple); value = HEOS._umolar; break;
|
|
case iP:
|
|
yL = HEOS.calc_pressure_nocache(TLtriple, rhoLtriple); yV = HEOS.calc_pressure_nocache(TVtriple, rhoVtriple); value = HEOS._p; break;
|
|
default:
|
|
throw ValueError(format("Input is invalid"));
|
|
}
|
|
y_solid = (yV-yL)/(1/rhoVtriple-1/rhoLtriple)*(1/HEOS._rhomolar-1/rhoLtriple) + yL;
|
|
|
|
if (value < y_solid){ throw ValueError(format("Other input [%d:%g] is solid", other, value));}
|
|
|
|
// Check if other is above the saturation value.
|
|
SaturationSolvers::saturation_D_pure_options options;
|
|
options.omega = 1;
|
|
options.use_logdelta = false;
|
|
if (HEOS._rhomolar > HEOS._crit.rhomolar)
|
|
{
|
|
options.imposed_rho = SaturationSolvers::saturation_D_pure_options::IMPOSED_RHOL;
|
|
SaturationSolvers::saturation_D_pure(&HEOS, HEOS._rhomolar, options);
|
|
// SatL and SatV have the saturation values
|
|
Sat = HEOS.SatL;
|
|
}
|
|
else
|
|
{
|
|
options.imposed_rho = SaturationSolvers::saturation_D_pure_options::IMPOSED_RHOV;
|
|
SaturationSolvers::saturation_D_pure(&HEOS, HEOS._rhomolar, options);
|
|
// SatL and SatV have the saturation values
|
|
Sat = HEOS.SatV;
|
|
}
|
|
|
|
// If it is above, it is not two-phase and either liquid, vapor or supercritical
|
|
if (value > Sat->keyed_output(other))
|
|
{
|
|
solver_resid resid(&HEOS, HEOS._rhomolar, value, other);
|
|
|
|
HEOS._T = Brent(resid, Sat->keyed_output(iT), HEOS.Tmax(), DBL_EPSILON, 1e-12, 100, errstring);
|
|
HEOS._Q = 10000;
|
|
HEOS.calc_pressure();
|
|
}
|
|
else
|
|
{
|
|
throw NotImplementedError("Two-phase for PHSU_D_flash not supported yet");
|
|
}
|
|
|
|
}
|
|
// Check if vapor/solid region below triple point vapor density
|
|
else if (HEOS._rhomolar < component->triple_vapor.rhomolar)
|
|
{
|
|
long double y, value;
|
|
long double TVtriple = component->triple_vapor.T; //TODO: separate TL and TV for ppure
|
|
|
|
// If value is above the value calculated from X(Ttriple, _rhomolar), it is vapor
|
|
switch (other)
|
|
{
|
|
case iSmolar:
|
|
y = HEOS.calc_smolar_nocache(TVtriple, HEOS._rhomolar); value = HEOS._smolar; break;
|
|
case iHmolar:
|
|
y = HEOS.calc_hmolar_nocache(TVtriple, HEOS._rhomolar); value = HEOS._hmolar; break;
|
|
case iUmolar:
|
|
y = HEOS.calc_umolar_nocache(TVtriple, HEOS._rhomolar); value = HEOS._umolar; break;
|
|
case iP:
|
|
y = HEOS.calc_pressure_nocache(TVtriple, HEOS._rhomolar); value = HEOS._p; break;
|
|
default:
|
|
throw ValueError(format("Input is invalid"));
|
|
}
|
|
if (value > y)
|
|
{
|
|
solver_resid resid(&HEOS, HEOS._rhomolar, value, other);
|
|
|
|
HEOS._T = Brent(resid, TVtriple, HEOS.Tmax(), DBL_EPSILON, 1e-12, 100, errstring);
|
|
HEOS._Q = 10000;
|
|
HEOS.calc_pressure();
|
|
}
|
|
else
|
|
{
|
|
throw ValueError(format("D < DLtriple %g %g", value, y));
|
|
}
|
|
|
|
}
|
|
// Check in the liquid/solid region above the triple point density
|
|
else
|
|
{
|
|
long double y, value;
|
|
long double TLtriple = component->pEOS->Ttriple;
|
|
|
|
// If value is above the value calculated from X(Ttriple, _rhomolar), it is vapor
|
|
switch (other)
|
|
{
|
|
case iSmolar:
|
|
y = HEOS.calc_smolar_nocache(TLtriple, HEOS._rhomolar); value = HEOS._smolar; break;
|
|
case iHmolar:
|
|
y = HEOS.calc_hmolar_nocache(TLtriple, HEOS._rhomolar); value = HEOS._hmolar; break;
|
|
case iUmolar:
|
|
y = HEOS.calc_umolar_nocache(TLtriple, HEOS._rhomolar); value = HEOS._umolar; break;
|
|
case iP:
|
|
y = HEOS.calc_pressure_nocache(TLtriple, HEOS._rhomolar); value = HEOS._p; break;
|
|
default:
|
|
throw ValueError(format("Input is invalid"));
|
|
}
|
|
if (value > y)
|
|
{
|
|
solver_resid resid(&HEOS, HEOS._rhomolar, value, other);
|
|
|
|
HEOS._T = Brent(resid, TLtriple, HEOS.Tmax(), DBL_EPSILON, 1e-12, 100, errstring);
|
|
HEOS._Q = 10000;
|
|
HEOS.calc_pressure();
|
|
}
|
|
else
|
|
{
|
|
throw ValueError(format("D < DLtriple %g %g", value, y));
|
|
}
|
|
}
|
|
}
|
|
else
|
|
throw NotImplementedError("PHSU_D_flash not ready for mixtures");
|
|
}
|
|
}
|
|
void FlashRoutines::HSU_P_flash(HelmholtzEOSMixtureBackend &HEOS, int other)
|
|
{
|
|
throw NotImplementedError("HSU_P_flash Not implemented yet");
|
|
}
|
|
void FlashRoutines::DHSU_T_flash(HelmholtzEOSMixtureBackend &HEOS, int other)
|
|
{
|
|
if (HEOS.imposed_phase_index > -1)
|
|
{
|
|
// Use the phase defined by the imposed phase
|
|
HEOS._phase = HEOS.imposed_phase_index;
|
|
}
|
|
else
|
|
{
|
|
if (HEOS.is_pure_or_pseudopure)
|
|
{
|
|
// Find the phase, while updating all internal variables possible
|
|
switch (other)
|
|
{
|
|
case iDmolar:
|
|
HEOS.T_phase_determination_pure_or_pseudopure(iDmolar, HEOS._rhomolar); break;
|
|
case iSmolar:
|
|
HEOS.T_phase_determination_pure_or_pseudopure(iSmolar, HEOS._smolar); break;
|
|
case iHmolar:
|
|
HEOS.T_phase_determination_pure_or_pseudopure(iHmolar, HEOS._hmolar); break;
|
|
case iUmolar:
|
|
HEOS.T_phase_determination_pure_or_pseudopure(iUmolar, HEOS._umolar); break;
|
|
default:
|
|
throw ValueError(format("Input is invalid"));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
HEOS._phase = iphase_gas;
|
|
throw NotImplementedError("DHSU_T_flash does not support mixtures (yet)");
|
|
// Find the phase, while updating all internal variables possible
|
|
}
|
|
}
|
|
|
|
if (HEOS.isHomogeneousPhase() && !ValidNumber(HEOS._p))
|
|
{
|
|
switch (other)
|
|
{
|
|
case iDmolar:
|
|
break;
|
|
case iHmolar:
|
|
HEOS._rhomolar = HEOS.solver_for_rho_given_T_oneof_HSU(HEOS._T, HEOS._hmolar, iHmolar); break;
|
|
case iSmolar:
|
|
HEOS._rhomolar = HEOS.solver_for_rho_given_T_oneof_HSU(HEOS._T, HEOS._smolar, iSmolar); break;
|
|
case iUmolar:
|
|
HEOS._rhomolar = HEOS.solver_for_rho_given_T_oneof_HSU(HEOS._T, HEOS._umolar, iUmolar); break;
|
|
default:
|
|
break;
|
|
}
|
|
HEOS.calc_pressure();
|
|
HEOS._Q = -1;
|
|
}
|
|
}
|
|
|
|
} /* namespace CoolProp */
|