Files
CoolProp/src/Backends/Helmholtz/Fluids/Ancillaries.cpp

420 lines
14 KiB
C++

#include "Ancillaries.h"
#include "DataStructures.h"
#include "AbstractState.h"
#if defined(ENABLE_CATCH)
#include "crossplatform_shared_ptr.h"
#include "catch.hpp"
#endif
namespace CoolProp{
SaturationAncillaryFunction::SaturationAncillaryFunction(rapidjson::Value &json_code)
{
std::string type = cpjson::get_string(json_code,"type");
if (!type.compare("rational_polynomial"))
{
num_coeffs = vec_to_eigen(cpjson::get_double_array(json_code["A"]));
den_coeffs = vec_to_eigen(cpjson::get_double_array(json_code["B"]));
max_abs_error = cpjson::get_double(json_code,"max_abs_error");
try{
Tmin = cpjson::get_double(json_code,"Tmin");
Tmax = cpjson::get_double(json_code,"Tmax");
}
catch (std::exception &e){
std::cout << "Missing Tmin, Tmax" << std::endl;
Tmin = _HUGE;
Tmax = _HUGE;
}
}
else
{
n = cpjson::get_double_array(json_code["n"]);
t = cpjson::get_double_array(json_code["t"]);
Tmin = cpjson::get_double(json_code,"Tmin");
Tmax = cpjson::get_double(json_code,"Tmax");
reducing_value = cpjson::get_double(json_code,"reducing_value");
using_tau_r = cpjson::get_bool(json_code,"using_tau_r");
T_r = cpjson::get_double(json_code,"T_r");
}
if (!type.compare("rational_polynomial"))
this->type = TYPE_RATIONAL_POLYNOMIAL;
else if (!type.compare("rhoLnoexp"))
this->type = TYPE_NOT_EXPONENTIAL;
else
this->type = TYPE_EXPONENTIAL;
this->N = n.size();
s = n;
};
double SaturationAncillaryFunction::evaluate(double T)
{
if (type == TYPE_NOT_SET)
{
throw ValueError(format("type not set"));
}
else if (type == TYPE_RATIONAL_POLYNOMIAL)
{
Polynomial2D poly;
return poly.evaluate(num_coeffs, T)/poly.evaluate(den_coeffs, T);
}
else
{
double THETA = 1-T/T_r;
for (std::size_t i = 0; i < N; ++i)
{
s[i] = n[i]*pow(THETA, t[i]);
}
double summer = std::accumulate(s.begin(), s.end(), 0.0);
if (type == TYPE_NOT_EXPONENTIAL)
{
return reducing_value*(1+summer);
}
else
{
double tau_r_value;
if (using_tau_r)
tau_r_value = T_r/T;
else
tau_r_value = 1.0;
return reducing_value*exp(tau_r_value*summer);
}
}
}
double SaturationAncillaryFunction::invert(double value, double min_bound, double max_bound)
{
// Invert the ancillary curve to get the temperature as a function of the output variable
// Define the residual to be driven to zero
class solver_resid : public FuncWrapper1D
{
public:
int other;
SaturationAncillaryFunction *anc;
long double T, value, r, current_value;
solver_resid(SaturationAncillaryFunction *anc, long double value) : anc(anc), value(value){};
double call(double T){
this->T = T;
current_value = anc->evaluate(T);
r = current_value - value;
return r;
};
};
solver_resid resid(this, value);
std::string errstring;
if (min_bound < 0){ min_bound = Tmin-0.01;}
if (max_bound < 0){ max_bound = Tmax;}
try{
// Safe to expand the domain a little bit to lower temperature, absolutely cannot exceed Tmax
// because then you get (negative number)^(double) which is undefined.
return Brent(resid,min_bound,max_bound,DBL_EPSILON,1e-10,100,errstring);
}
catch(std::exception &e){
return Secant(resid,max_bound, -0.01, 1e-12, 100, errstring);
}
}
void MeltingLineVariables::set_limits(void)
{
if (type == MELTING_LINE_SIMON_TYPE){
// Fill in the min and max pressures for each part
for (std::size_t i = 0; i < simon.parts.size(); ++i){
MeltingLinePiecewiseSimonSegment &part = simon.parts[i];
part.p_min = part.p_0 + part.a*(pow(part.T_min/part.T_0,part.c)-1);
part.p_max = part.p_0 + part.a*(pow(part.T_max/part.T_0,part.c)-1);
}
pmin = simon.parts.front().p_min;
pmax = simon.parts.back().p_max;
Tmin = simon.parts.front().T_min;
Tmax = simon.parts.back().T_max;
}
else if (type == MELTING_LINE_POLYNOMIAL_IN_TR_TYPE){
// Fill in the min and max pressures for each part
for (std::size_t i = 0; i < polynomial_in_Tr.parts.size(); ++i){
MeltingLinePiecewisePolynomialInTrSegment &part = polynomial_in_Tr.parts[i];
part.p_min = part.evaluate(part.T_min);
part.p_max = part.evaluate(part.T_max);
}
Tmin = polynomial_in_Tr.parts.front().T_min;
pmin = polynomial_in_Tr.parts.front().p_min;
Tmax = polynomial_in_Tr.parts.back().T_max;
pmax = polynomial_in_Tr.parts.back().p_max;
}
else if (type == MELTING_LINE_POLYNOMIAL_IN_THETA_TYPE){
// Fill in the min and max pressures for each part
for (std::size_t i = 0; i < polynomial_in_Theta.parts.size(); ++i){
MeltingLinePiecewisePolynomialInThetaSegment &part = polynomial_in_Theta.parts[i];
part.p_min = part.evaluate(part.T_min);
part.p_max = part.evaluate(part.T_max);
}
Tmin = polynomial_in_Theta.parts.front().T_min;
pmin = polynomial_in_Theta.parts.front().p_min;
Tmax = polynomial_in_Theta.parts.back().T_max;
pmax = polynomial_in_Theta.parts.back().p_max;
}
else{
throw ValueError("only Simon supported now");
}
}
long double MeltingLineVariables::evaluate(int OF, int GIVEN, long double value)
{
if (type == MELTING_LINE_NOT_SET){throw ValueError("Melting line curve not set");}
if (OF == iP_max){ return pmax;}
else if (OF == iP_min){ return pmin;}
else if (OF == iT_max){ return Tmax;}
else if (OF == iT_min){ return Tmin;}
else if (OF == iP && GIVEN == iT){
long double T = value;
if (type == MELTING_LINE_SIMON_TYPE){
// Need to find the right segment
for (std::size_t i = 0; i < simon.parts.size(); ++i){
MeltingLinePiecewiseSimonSegment &part = simon.parts[i];
if (is_in_closed_range(part.T_min, part.T_max, T)){
return part.p_0 + part.a*(pow(T/part.T_0,part.c)-1);
}
}
throw ValueError("unable to calculate melting line (p,T) for Simon curve");
}
else if (type == MELTING_LINE_POLYNOMIAL_IN_TR_TYPE){
// Need to find the right segment
for (std::size_t i = 0; i < polynomial_in_Tr.parts.size(); ++i){
MeltingLinePiecewisePolynomialInTrSegment &part = polynomial_in_Tr.parts[i];
if (is_in_closed_range(part.T_min, part.T_max, T)){
return part.evaluate(T);
}
}
throw ValueError("unable to calculate melting line (p,T) for polynomial_in_Tr curve");
}
else if (type == MELTING_LINE_POLYNOMIAL_IN_THETA_TYPE){
// Need to find the right segment
for (std::size_t i = 0; i < polynomial_in_Theta.parts.size(); ++i){
MeltingLinePiecewisePolynomialInThetaSegment &part = polynomial_in_Theta.parts[i];
if (is_in_closed_range(part.T_min, part.T_max, T)){
return part.evaluate(T);
}
}
throw ValueError("unable to calculate melting line (p,T) for polynomial_in_Theta curve");
}
else{
throw ValueError(format("Invalid melting line type [%d]",type));
}
}
else{
if (type == MELTING_LINE_SIMON_TYPE){
// Need to find the right segment
for (std::size_t i = 0; i < simon.parts.size(); ++i){
MeltingLinePiecewiseSimonSegment &part = simon.parts[i];
// p = part.p_0 + part.a*(pow(T/part.T_0,part.c)-1);
long double T = pow((value-part.p_0)/part.a+1,1/part.c)*part.T_0;
if (T >= part.T_0 && T <= part.T_max){
return T;
}
}
throw ValueError(format("unable to calculate melting line T(p) for Simon curve for p=%Lg; bounds are %Lg,%Lg Pa", value, pmin, pmax));
}
else if (type == MELTING_LINE_POLYNOMIAL_IN_TR_TYPE)
{
class solver_resid : public FuncWrapper1D
{
public:
MeltingLinePiecewisePolynomialInTrSegment *part;
long double r, given_p, calc_p, T;
solver_resid(MeltingLinePiecewisePolynomialInTrSegment *part, long double p) : part(part), given_p(p){};
double call(double T){
this->T = T;
calc_p = part->evaluate(T);
// Difference between the two is to be driven to zero
r = given_p - calc_p;
return r;
};
};
// Need to find the right segment
for (std::size_t i = 0; i < polynomial_in_Tr.parts.size(); ++i){
MeltingLinePiecewisePolynomialInTrSegment &part = polynomial_in_Tr.parts[i];
if (is_in_closed_range(part.p_min, part.p_max, value)){
std::string errstr;
solver_resid resid(&part, value);
double T = Brent(resid, part.T_min, part.T_max, DBL_EPSILON, 1e-12, 100, errstr);
return T;
}
}
throw ValueError(format("unable to calculate melting line T(p) for polynomial_in_Theta curve for p=%Lg; bounds are %Lg,%Lg Pa", value, pmin, pmax));
}
else if (type == MELTING_LINE_POLYNOMIAL_IN_THETA_TYPE)
{
class solver_resid : public FuncWrapper1D
{
public:
MeltingLinePiecewisePolynomialInThetaSegment *part;
long double r, given_p, calc_p, T;
solver_resid(MeltingLinePiecewisePolynomialInThetaSegment *part, long double p) : part(part), given_p(p){};
double call(double T){
this->T = T;
calc_p = part->evaluate(T);
// Difference between the two is to be driven to zero
r = given_p - calc_p;
return r;
};
};
// Need to find the right segment
for (std::size_t i = 0; i < polynomial_in_Theta.parts.size(); ++i){
MeltingLinePiecewisePolynomialInThetaSegment &part = polynomial_in_Theta.parts[i];
if (is_in_closed_range(part.p_min, part.p_max, value)){
std::string errstr;
solver_resid resid(&part, value);
double T = Brent(resid, part.T_min, part.T_max, DBL_EPSILON, 1e-12, 100, errstr);
return T;
}
}
throw ValueError(format("unable to calculate melting line T(p) for polynomial_in_Theta curve for p=%Lg; bounds are %Lg,%Lg Pa", value, pmin, pmax));
}
else{
throw ValueError(format("Invalid melting line type T(p) [%d]",type));
}
}
}
}; /* namespace CoolProp */
#if defined(ENABLE_CATCH)
TEST_CASE("Water melting line", "[melting]")
{
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS","water"));
int iT = CoolProp::iT, iP = CoolProp::iP;
SECTION("Ice Ih-liquid")
{
double actual = AS->melting_line(iT, iP, 138.268e6);
double expected = 260.0;
CAPTURE(actual);
CAPTURE(expected);
CHECK(std::abs(actual-expected) < 0.01);
}
SECTION("Ice III-liquid")
{
double actual = AS->melting_line(iT, iP, 268.685e6);
double expected = 254;
CAPTURE(actual);
CAPTURE(expected);
CHECK(std::abs(actual-expected) < 0.01);
}
SECTION("Ice V-liquid")
{
double actual = AS->melting_line(iT, iP, 479.640e6);
double expected = 265;
CAPTURE(actual);
CAPTURE(expected);
CHECK(std::abs(actual-expected) < 0.01);
}
SECTION("Ice VI-liquid")
{
double actual = AS->melting_line(iT, iP, 1356.76e6);
double expected = 320;
CAPTURE(actual);
CAPTURE(expected);
CHECK(std::abs(actual-expected) < 1);
}
}
TEST_CASE("Tests for values from melting lines", "[melting]")
{
int iT = CoolProp::iT, iP = CoolProp::iP;
std::vector<std::string> fluids = strsplit(CoolProp::get_global_param_string("fluids_list"),',');
for (std::size_t i = 0; i < fluids.size(); ++i) {
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS",fluids[i]));
// Water has its own better tests; skip fluids without melting line
if (!AS->has_melting_line() || !fluids[i].compare("Water")){continue;}
double pmax = AS->melting_line(CoolProp::iP_max, iT, 0);
double pmin = AS->melting_line(CoolProp::iP_min, iT, 0);
double Tmax = AS->melting_line(CoolProp::iT_max, iT, 0);
double Tmin = AS->melting_line(CoolProp::iT_min, iT, 0);
// See https://groups.google.com/forum/?fromgroups#!topic/catch-forum/mRBKqtTrITU
std::ostringstream ss0;
ss0 << "Check melting line limits for fluid " << fluids[i];
SECTION(ss0.str(),"")
{
CAPTURE(Tmin);
CAPTURE(Tmax);
CAPTURE(pmin);
CAPTURE(pmax);
CHECK(Tmax > Tmin);
CHECK(pmax > pmin);
CHECK(pmin > 0);
}
for (double p = 0.1*(pmax-pmin) + pmin; p < pmax; p += 0.2*(pmax-pmin)){
// See https://groups.google.com/forum/?fromgroups#!topic/catch-forum/mRBKqtTrITU
std::ostringstream ss1;
ss1 << "Melting line for " << fluids[i] << " at p=" << p;
SECTION(ss1.str(),"")
{
double actual_T = AS->melting_line(iT, iP, p);
CAPTURE(Tmin);
CAPTURE(Tmax);
CAPTURE(actual_T);
CHECK(actual_T > Tmin);
CHECK(actual_T < Tmax);
}
}
// See https://groups.google.com/forum/?fromgroups#!topic/catch-forum/mRBKqtTrITU
std::ostringstream ss2;
ss2 << "Ensure melting line valid for " << fluids[i] << " @ EOS pmax";
SECTION(ss2.str(),"")
{
double actual_T;
double EOS_pmax = AS->pmax();
CAPTURE(EOS_pmax);
CHECK_NOTHROW(actual_T = AS->melting_line(iT, iP, EOS_pmax));
CAPTURE(actual_T);
}
}
}
TEST_CASE("Test that hs_anchor enthalpy/entropy agrees with EOS", "[ancillaries]")
{
std::vector<std::string> fluids = strsplit(CoolProp::get_global_param_string("fluids_list"),',');
for (std::size_t i = 0; i < fluids.size(); ++i)
{
shared_ptr<CoolProp::AbstractState> AS(CoolProp::AbstractState::factory("HEOS",fluids[i]));
CoolProp::SimpleState hs_anchor = AS->get_state("hs_anchor");
// See https://groups.google.com/forum/?fromgroups#!topic/catch-forum/mRBKqtTrITU
std::ostringstream ss1;
ss1 << "Check hs_anchor for " << fluids[i];
SECTION(ss1.str(),"")
{
std::string note = "The enthalpy and entropy are hardcoded in the fluid JSON files. They MUST agree with the values calculated by the EOS";
AS->update(CoolProp::DmolarT_INPUTS, hs_anchor.rhomolar, hs_anchor.T);
CAPTURE(hs_anchor.hmolar);
CAPTURE(hs_anchor.smolar);
double EOS_hmolar = AS->hmolar();
double EOS_smolar = AS->smolar();
CHECK( std::abs(EOS_hmolar - hs_anchor.hmolar) < 1e-3);
CHECK( std::abs(EOS_smolar - hs_anchor.smolar) < 1e-3);
}
}
}
#endif