-
Notifications
You must be signed in to change notification settings - Fork 12
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #146 from usnistgov/pureparamoptim
Pure Parameter Optimization
- Loading branch information
Showing
7 changed files
with
559 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
{ | ||
"cells": [ | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "0bace5e5", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"import teqp\n", | ||
"import numpy as np\n", | ||
"import matplotlib.pyplot as plt\n", | ||
"import CoolProp.CoolProp as CP\n", | ||
"import scipy.optimize\n", | ||
"display(teqp.__version__)\n", | ||
"\n", | ||
"nonpolar = {\n", | ||
" \"kind\": \"SAFT-VR-Mie\",\n", | ||
" \"model\": {\n", | ||
" \"coeffs\": [\n", | ||
" {\n", | ||
" \"name\": \"R32\",\n", | ||
" \"BibTeXKey\": \"Bell\",\n", | ||
" \"m\": 1.2476268271391935,\n", | ||
" \"sigma_m\": 3.6080717234117107e-10,\n", | ||
" \"epsilon_over_k\": 172.53065054286867,\n", | ||
" \"lambda_r\": 14.634722358167384,\n", | ||
" \"lambda_a\": 6\n", | ||
" }\n", | ||
" ]\n", | ||
" }\n", | ||
"}\n", | ||
"template = { \n", | ||
" 'kind': 'genericSAFT', \n", | ||
" 'model': {\n", | ||
" 'nonpolar': nonpolar\n", | ||
" }\n", | ||
"}\n", | ||
"\n", | ||
"# NOTE: '/' inside field names MUST be escaped as ~1; see https://datatracker.ietf.org/doc/html/rfc6901#section-3\n", | ||
"pointers = [\n", | ||
" '/model/nonpolar/model/coeffs/0/m',\n", | ||
" '/model/nonpolar/model/coeffs/0/sigma_m',\n", | ||
" '/model/nonpolar/model/coeffs/0/epsilon_over_k',\n", | ||
" '/model/nonpolar/model/coeffs/0/lambda_r',\n", | ||
" '/model/nonpolar/model/coeffs/0/lambda_a'\n", | ||
"]\n", | ||
"x0 = [1.5, 3e-10, 150, 19, 5.7]\n", | ||
"bounds = [(1,5), (2e-10,5e-10), (100,400), (12,50), (5.1, 6.0)]" | ||
] | ||
}, | ||
{ | ||
"cell_type": "code", | ||
"execution_count": null, | ||
"id": "f5463428", | ||
"metadata": {}, | ||
"outputs": [], | ||
"source": [ | ||
"FLD = 'Methane'\n", | ||
"ppo = teqp.PureParameterOptimizer(template, pointers)\n", | ||
"\n", | ||
"Ts = np.linspace(100, 170, 10)\n", | ||
"\n", | ||
"# Generate some artificial pseudo-experimental data from \n", | ||
"# the reference EOS as implemented in CoolProp\n", | ||
"for T in Ts:\n", | ||
" pt = teqp.SatRhoLPWPoint()\n", | ||
" rhoL, rhoV = [CP.PropsSI('Dmolar','Q',Q,'T',T,FLD) for Q in [0,1]]\n", | ||
" p = CP.PropsSI('P','Q',0,'T',T,FLD)\n", | ||
" w = CP.PropsSI('speed_of_sound','Q',0,'T',T,FLD)\n", | ||
" pt.T = T\n", | ||
" # Measurands (here, pseudo-experimental values)\n", | ||
" pt.p_exp = p\n", | ||
" pt.rhoL_exp = rhoL\n", | ||
" pt.w_exp = w\n", | ||
" \n", | ||
" # Additional parameters\n", | ||
" pt.rhoL_guess = rhoL\n", | ||
" pt.rhoV_guess = rhoV\n", | ||
" pt.R = 8.31446261815324\n", | ||
" pt.M = CP.PropsSI('molemass',FLD)\n", | ||
" AS = CP.AbstractState('HEOS',FLD)\n", | ||
" AS.update(CP.DmolarT_INPUTS, 1e-10, T)\n", | ||
" pt.Ao20 = AS.tau()**2*AS.d2alpha0_dTau2() # -cv0/R\n", | ||
" \n", | ||
" # Weights (multiplied by 100 to put on a percentage basis)\n", | ||
" pt.weight_p = 1*100\n", | ||
" pt.weight_rho = 1*100\n", | ||
" pt.weight_w = 0.25*100\n", | ||
" ppo.add_one_contribution(pt)\n", | ||
"\n", | ||
"def cost_function(x):\n", | ||
"# return ppo.cost_function_threaded(x, 10) # This is an option if you have lots of threads\n", | ||
" return ppo.cost_function(x)\n", | ||
"\n", | ||
"r = scipy.optimize.differential_evolution(cost_function, bounds=bounds, disp=True, maxiter=10000, popsize=8)\n", | ||
"print(r)\n", | ||
"x = r.x\n", | ||
"model = teqp.make_model(ppo.build_JSON(x))\n", | ||
"\n", | ||
"Tc, rhoc = model.solve_pure_critical(400, 5000)\n", | ||
"print(Tc, rhoc)\n", | ||
"anc = teqp.build_ancillaries(model, Tc, rhoc, 0.5*Tc)\n", | ||
"\n", | ||
"def _get_SOS(model, T, rho, z, *, R, M, Ao20):\n", | ||
" \"\"\" Helper function to calculate speed of sound \"\"\"\n", | ||
" Ar0n = model.get_Ar02n(T, rho, z)\n", | ||
" Ar01 = Ar0n[1]; Ar02 = Ar0n[2]\n", | ||
" Ar11 = model.get_Ar11(T, rho, z)\n", | ||
" Ar20 = model.get_Ar20(T, rho, z)\n", | ||
"\n", | ||
" # M*w^2/(R*T) where w is the speed of sound\n", | ||
" # from the definition w = sqrt(dp/drho|s)\n", | ||
" Mw2RT = 1 + 2*Ar01 + Ar02 - (1 + Ar01 - Ar11)**2/(Ao20 + Ar20)\n", | ||
" if Mw2RT < 0:\n", | ||
" return 1e6\n", | ||
" w = (Mw2RT*R*T/M)**0.5\n", | ||
" return w\n", | ||
"\n", | ||
"TcREF = CP.PropsSI('Tcrit', FLD)\n", | ||
"Tsverify = np.linspace(100, TcREF*0.99999, 1000)\n", | ||
"RHOL, RHOV, PPP, WWW = [],[],[],[]\n", | ||
"z = np.array([1.0])\n", | ||
"for T in Tsverify:\n", | ||
" rhoL, rhoV = model.pure_VLE_T(T, anc.rhoL(T)*1.01, anc.rhoV(T)*0.9, 10)\n", | ||
" pL = rhoL*model.get_R(z)*T*(1+model.get_Ar01(T, rhoL, z))\n", | ||
" pV = rhoV*model.get_R(z)*T*(1+model.get_Ar01(T, rhoV, z))\n", | ||
" RHOL.append(rhoL)\n", | ||
" RHOV.append(rhoV)\n", | ||
" PPP.append(pL)\n", | ||
" AS = CP.AbstractState('HEOS',FLD)\n", | ||
" AS.update(CP.DmolarT_INPUTS, 1e-10, T)\n", | ||
" Ao20 = AS.d2alpha0_dTau2()*AS.tau()**2\n", | ||
" WWW.append(_get_SOS(model, T, rhoL, z, R=8.314462618,M=CP.PropsSI('molemass',FLD),Ao20=Ao20))\n", | ||
" \n", | ||
"# Plot the T-rho for VLE\n", | ||
"line, = plt.plot(np.array(RHOL), Tsverify)\n", | ||
"plt.plot(np.array(RHOV), Tsverify, color=line.get_color())\n", | ||
"for Q in [0, 1]:\n", | ||
" D = CP.PropsSI('Dmolar','T',Tsverify,'Q',Q,FLD)\n", | ||
" plt.plot(D, Tsverify, lw=2, color='k')\n", | ||
"plt.gca().set(xlabel=r'$\\rho$ / mol/m$^3$', ylabel='$T$ / K')\n", | ||
"\n", | ||
"# And a deviation plot, much closer to the critical point\n", | ||
"# than the fitting region\n", | ||
"fig, (ax1, ax2, ax3) = plt.subplots(3,1, figsize=(10,5), sharex=True)\n", | ||
"\n", | ||
"ax1.plot(Tsverify, (np.array(PPP)/CP.PropsSI('P','T',Tsverify,'Q',0,FLD)-1)*100)\n", | ||
"ax1.set(ylabel=r'$(p_{fit}/p_{\\rm pexp}-1)\\times 100$')\n", | ||
"\n", | ||
"ax2.plot(Tsverify, (np.array(RHOL)/CP.PropsSI('Dmolar','T',Tsverify,'Q',0,FLD)-1)*100)\n", | ||
"ax2.set(ylabel=r'$(\\rho_{fit}/\\rho_{\\rm pexp}-1)\\times 100$')\n", | ||
"\n", | ||
"ax3.plot(Tsverify, (np.array(WWW)/CP.PropsSI('speed_of_sound','T',Tsverify,'Q',0,FLD)-1)*100)\n", | ||
"ax3.set(ylabel=r'$(w_{fit}/w_{\\rm pexp}-1)\\times 100$', xlabel='$T$ / K')" | ||
] | ||
} | ||
], | ||
"metadata": { | ||
"kernelspec": { | ||
"display_name": "Python 3 (ipykernel)", | ||
"language": "python", | ||
"name": "python3" | ||
}, | ||
"language_info": { | ||
"codemirror_mode": { | ||
"name": "ipython", | ||
"version": 3 | ||
}, | ||
"file_extension": ".py", | ||
"mimetype": "text/x-python", | ||
"name": "python", | ||
"nbconvert_exporter": "python", | ||
"pygments_lexer": "ipython3", | ||
"version": "3.10.6" | ||
} | ||
}, | ||
"nbformat": 4, | ||
"nbformat_minor": 5 | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,196 @@ | ||
#pragma once | ||
|
||
#include "nlohmann/json.hpp" | ||
#include "teqp/cpp/teqpcpp.hpp" | ||
#include "teqp/exceptions.hpp" | ||
#include "teqp/math/pow_templates.hpp" | ||
|
||
#include <boost/asio/thread_pool.hpp> | ||
#include <boost/asio/post.hpp> | ||
|
||
#include <tuple> | ||
|
||
namespace teqp::algorithms::pure_param_optimization { | ||
|
||
struct SatRhoLPoint{ | ||
double T, rhoL_exp, rhoL_guess, rhoV_guess, weight=1.0; | ||
auto check_fields() const{} | ||
template<typename Model> | ||
auto calculate_contribution(const Model& model) const{ | ||
auto rhoLrhoV = model->pure_VLE_T(T, rhoL_guess, rhoV_guess, 10); | ||
return std::abs(rhoLrhoV[0]-rhoL_exp)*weight; | ||
} | ||
}; | ||
|
||
#define stdstringify(s) std::string(#s) | ||
|
||
#define SatRhoLPPoint_optionalfields X(T) X(p_exp) X(rhoL_exp) X(rhoL_guess) X(rhoV_guess) | ||
struct SatRhoLPPoint{ | ||
#define X(field) std::optional<double> field; | ||
SatRhoLPPoint_optionalfields | ||
#undef X | ||
double weight_rho=1.0, weight_p=1.0, R=8.31446261815324; | ||
Eigen::ArrayXd z = (Eigen::ArrayXd(1) << 1.0).finished(); | ||
|
||
auto check_fields() const{ | ||
#define X(field) if (!field){ throw teqp::InvalidArgument("A field [" + stdstringify(field) + "] has not been initialized"); } | ||
SatRhoLPPoint_optionalfields | ||
#undef X | ||
} | ||
|
||
template<typename Model> | ||
auto calculate_contribution(const Model& model) const{ | ||
auto rhoLrhoV = model->pure_VLE_T(T.value(), rhoL_guess.value(), rhoV_guess.value(), 10); | ||
auto rhoL = rhoLrhoV[0]; | ||
auto p = rhoL*R*T.value()*(1+model->get_Ar01(T.value(), rhoL, z)); | ||
// std::cout << p << "," << p_exp << "," << (p_exp-p)/p_exp << std::endl; | ||
return std::abs(rhoL-rhoL_exp.value())/rhoL_exp.value()*weight_rho + std::abs(p-p_exp.value())/p_exp.value()*weight_p; | ||
} | ||
}; | ||
|
||
#define SatRhoLPWPoint_optionalfields X(T) X(p_exp) X(rhoL_exp) X(w_exp) X(rhoL_guess) X(rhoV_guess) X(Ao20) X(M) X(R) | ||
struct SatRhoLPWPoint{ | ||
|
||
#define X(field) std::optional<double> field; | ||
SatRhoLPWPoint_optionalfields | ||
#undef X | ||
double weight_rho=1.0, weight_p=1.0, weight_w=1.0; | ||
Eigen::ArrayXd z = (Eigen::ArrayXd(1) << 1.0).finished(); | ||
|
||
auto check_fields() const{ | ||
#define X(field) if (!field){ throw teqp::InvalidArgument("A field [" + stdstringify(field) + "] has not been initialized"); } | ||
SatRhoLPWPoint_optionalfields | ||
#undef X | ||
} | ||
|
||
template<typename Model> | ||
auto calculate_contribution(const Model& model) const{ | ||
|
||
auto rhoLrhoV = model->pure_VLE_T(T.value(), rhoL_guess.value(), rhoV_guess.value(), 10); | ||
|
||
// First part, density | ||
auto rhoL = rhoLrhoV[0]; | ||
|
||
auto Ar0n = model->get_Ar02n(T.value(), rhoL, z); | ||
double Ar01 = Ar0n[1], Ar02 = Ar0n[2]; | ||
auto Ar11 = model->get_Ar11(T.value(), rhoL, z); | ||
auto Ar20 = model->get_Ar20(T.value(), rhoL, z); | ||
|
||
// Second part, presure | ||
auto p = rhoL*R.value()*T.value()*(1+Ar01); | ||
|
||
// Third part, speed of sound | ||
// | ||
// M*w^2/(R*T) where w is the speed of sound | ||
// from the definition w = sqrt(dp/drho|s) | ||
double Mw2RT = 1 + 2*Ar01 + Ar02 - POW2(1.0 + Ar01 - Ar11)/(Ao20.value() + Ar20); | ||
double w = sqrt(Mw2RT*R.value()*T.value()/M.value()); | ||
|
||
// std::cout << p << "," << p_exp << "," << (p_exp-p)/p_exp << std::endl; | ||
double cost_rhoL = std::abs(rhoL-rhoL_exp.value())/rhoL_exp.value()*weight_rho; | ||
double cost_p = std::abs(p-p_exp.value())/p_exp.value()*weight_p; | ||
double cost_w = std::abs(w-w_exp.value())/w_exp.value()*weight_w; | ||
return cost_rhoL + cost_p + cost_w; | ||
} | ||
}; | ||
using PureOptimizationContribution = std::variant<SatRhoLPoint, SatRhoLPPoint, SatRhoLPWPoint>; | ||
|
||
class PureParameterOptimizer{ | ||
private: | ||
auto make_pointers(const std::vector<std::variant<std::string, std::vector<std::string>>>& pointerstrs){ | ||
std::vector<std::vector<nlohmann::json::json_pointer>> pointers_; | ||
for (auto & pointer: pointerstrs){ | ||
if (std::holds_alternative<std::string>(pointer)){ | ||
const std::string& s= std::get<std::string>(pointer); | ||
pointers_.emplace_back(1, nlohmann::json::json_pointer(s)); | ||
} | ||
else{ | ||
std::vector<nlohmann::json::json_pointer> buffer; | ||
for (const auto& s : std::get<std::vector<std::string>>(pointer)){ | ||
buffer.emplace_back(s); | ||
} | ||
pointers_.push_back(buffer); | ||
} | ||
} | ||
return pointers_; | ||
} | ||
public: | ||
const nlohmann::json jbase; | ||
std::vector<std::vector<nlohmann::json::json_pointer>> pointers; | ||
std::vector<PureOptimizationContribution> contributions; | ||
|
||
PureParameterOptimizer(const nlohmann::json jbase, const std::vector<std::variant<std::string, std::vector<std::string>>>& pointerstrs) : jbase(jbase), pointers(make_pointers(pointerstrs)){} | ||
|
||
void add_one_contribution(const PureOptimizationContribution& cont){ | ||
std::visit([](const auto&c){c.check_fields();}, cont); | ||
contributions.push_back(cont); | ||
} | ||
|
||
auto prepare_helper_models(const auto& model) const { | ||
return std::vector<double>{1, 3}; | ||
} | ||
|
||
template<typename T> | ||
nlohmann::json build_JSON(const T& x) const{ | ||
|
||
if (x.size() != pointers.size()){ | ||
throw teqp::InvalidArgument("sizes don't match"); | ||
}; | ||
nlohmann::json j = jbase; | ||
for (auto i = 0; i < x.size(); ++i){ | ||
for (const auto& ptr : pointers[i]){ | ||
j.at(ptr) = x[i]; | ||
} | ||
} | ||
return j; | ||
} | ||
|
||
template<typename T> | ||
auto prepare(const T& x) const { | ||
auto j = build_JSON(x); | ||
auto model = teqp::cppinterface::make_model(j); | ||
auto helpers = prepare_helper_models(model); | ||
return std::make_tuple(std::move(model), helpers); | ||
} | ||
|
||
template<typename T> | ||
auto cost_function(const T& x) const{ | ||
auto [model, helpers] = prepare(x); | ||
double cost = 0.0; | ||
for (const auto& contrib : contributions){ | ||
cost += std::visit([&model](const auto& c){ return c.calculate_contribution(model); }, contrib); | ||
} | ||
if (!std::isfinite(cost)){ | ||
return 1e30; | ||
} | ||
return cost; | ||
} | ||
|
||
template<typename T> | ||
auto cost_function_threaded(const T& x, std::size_t Nthreads) { | ||
boost::asio::thread_pool pool{Nthreads}; // Nthreads in the pool | ||
const auto [model, helpers] = prepare(x); | ||
std::valarray<double> buffer(contributions.size()); | ||
std::size_t i = 0; | ||
for (const auto& contrib : contributions){ | ||
auto& dest = buffer[i]; | ||
auto payload = [&model, &dest, contrib] (){ | ||
dest = std::visit([&model](const auto& c){ return c.calculate_contribution(model); }, contrib); | ||
if (!std::isfinite(dest)){ dest = 1e30; } | ||
}; | ||
boost::asio::post(pool, payload); | ||
i++; | ||
} | ||
pool.join(); | ||
double summer = 0.0; | ||
for (auto i = 0; i < contributions.size(); ++i){ | ||
// std::cout << buffer[i] << std::endl; | ||
summer += buffer[i]; | ||
} | ||
// std::cout << summer << std::endl; | ||
return summer; | ||
} | ||
|
||
}; | ||
|
||
} |
Oops, something went wrong.