32 const Eigen::Index N = rhovecVinit.size();
33 Eigen::MatrixXd J(3 * N, 3 * N); J.setZero();
34 Eigen::VectorXd r(3 * N), x(3 * N);
36 x.head(N) = rhovecVinit;
37 x.segment(N, N) = rhovecL1init;
38 x.tail(N) = rhovecL2init;
40 Eigen::Map<Eigen::ArrayXd> rhovecV (&(x(0)), N);
41 Eigen::Map<Eigen::ArrayXd> rhovecL1(&(x(0+N)), N);
42 Eigen::Map<Eigen::ArrayXd> rhovecL2(&(x(0+2*N)), N);
46 for (
int iter = 0; iter < maxiter; ++iter) {
56 auto zV = rhovecV/rhovecV.sum(), zL1 = rhovecL1 / rhovecL1.sum(), zL2 = rhovecL2 / rhovecL2.sum();
57 double RTL1 = model.
get_R(zL1)*T, RTL2 = model.
get_R(zL2)*T, RTV = model.
get_R(zV)*T;
59 auto rhoL1 = rhovecL1.sum();
60 auto rhoL2 = rhovecL2.sum();
61 auto rhoV = rhovecV.sum();
62 double pL1 = rhoL1 * RTL1 - PsirL1 + (rhovecL1.array() * PsirgradL1.array()).sum();
63 double pL2 = rhoL2 * RTL2 - PsirL2 + (rhovecL2.array() * PsirgradL2.array()).sum();
64 double pV = rhoV * RTV - PsirV + (rhovecV.array() * PsirgradV.array()).sum();
65 auto dpdrhovecL1 = RTL1 + (hessianL1 * rhovecL1.matrix()).array();
66 auto dpdrhovecL2 = RTL2 + (hessianL2 * rhovecL2.matrix()).array();
67 auto dpdrhovecV = RTV + (hessianV * rhovecV.matrix()).array();
70 r.head(N) = PsirgradV + RTV*log(rhovecV) - (PsirgradL1 + RTL1*log(rhovecL1));
71 r.segment(N,N) = PsirgradL1 + RTL1 * log(rhovecL1) - (PsirgradL2 + RTL2 * log(rhovecL2));
77 J.block(0,0,N,N) = HtotV;
78 J.block(0,N,N,N) = -HtotL1;
81 J.block(N, N, N, N) = HtotL1;
82 J.block(N, 2 * N, N, N) = -HtotL2;
84 J.block(2 * N, 0, 1, N) = dpdrhovecV.transpose();
85 J.block(2 * N, N, 1, N) = -dpdrhovecL1.transpose();
86 J.block(2 * N + 1, N, 1, N) = dpdrhovecL1.transpose();
87 J.block(2 * N + 1, 2 * N, 1, N) = -dpdrhovecL2.transpose();
90 Eigen::ArrayXd dx = J.colPivHouseholderQr().solve(-r);
93 auto xtol_threshold = (axtol + relxtol * x.array().cwiseAbs()).eval();
94 if ((dx.array() < xtol_threshold).all()) {
99 auto error_threshold = (atol + reltol * r.array().cwiseAbs()).eval();
100 if ((r.array().cwiseAbs() < error_threshold).all()) {
108 if (((x.array() - dx.array()).cwiseAbs() < std::numeric_limits<double>::min()).all()) {
112 if (iter == maxiter - 1) {
116 Eigen::ArrayXd rhovecVfinal = rhovecV, rhovecL1final = rhovecL1, rhovecL2final = rhovecL2;
117 return std::make_tuple(return_code, rhovecVfinal, rhovecL1final, rhovecL2final);
134 inline auto mix_VLLE_p(
const AbstractModel& model,
double p,
double Tinit,
const EArrayd& rhovecVinit,
const EArrayd& rhovecL1init,
const EArrayd& rhovecL2init,
double atol,
double reltol,
double axtol,
double relxtol,
int maxiter) {
136 const Eigen::Index N = rhovecVinit.size();
137 Eigen::MatrixXd J(3*N+1, 3*N+1); J.setZero();
138 Eigen::VectorXd r(3*N+1), x(3*N+1);
140 x.head(N) = rhovecVinit;
141 x.segment(N, N) = rhovecL1init;
142 x.segment(2*N, N) = rhovecL2init;
143 x(x.size()-1) = Tinit;
145 Eigen::Map<Eigen::ArrayXd> rhovecV (&(x(0)), N);
146 Eigen::Map<Eigen::ArrayXd> rhovecL1(&(x(0+N)), N);
147 Eigen::Map<Eigen::ArrayXd> rhovecL2(&(x(0+2*N)), N);
152 for (
int iter = 0; iter < maxiter; ++iter) {
163 auto zV = rhovecV/rhovecV.sum(), zL1 = rhovecL1 / rhovecL1.sum(), zL2 = rhovecL2 / rhovecL2.sum();
164 double RL1 = model.
get_R(zL1), RL2 = model.
get_R(zL2), RV = model.
get_R(zV);
165 double RTL1 = RL1*T, RTL2 = RL2*T, RTV = RV*T;
167 auto rhoL1 = rhovecL1.sum();
168 auto rhoL2 = rhovecL2.sum();
169 auto rhoV = rhovecV.sum();
170 double pL1 = rhoL1 * RTL1 - PsirL1 + (rhovecL1.array() * PsirgradL1.array()).sum();
171 double pL2 = rhoL2 * RTL2 - PsirL2 + (rhovecL2.array() * PsirgradL2.array()).sum();
172 double pV = rhoV * RTV - PsirV + (rhovecV.array() * PsirgradV.array()).sum();
173 auto dpdrhovecL1 = RTL1 + (hessianL1 * rhovecL1.matrix()).array();
174 auto dpdrhovecL2 = RTL2 + (hessianL2 * rhovecL2.matrix()).array();
175 auto dpdrhovecV = RTV + (hessianV * rhovecV.matrix()).array();
181 auto DELTAVL1_dchempot_dT = (DELTAVL1dmu_dT_res + RV*log(rhovecV) - RL1*log(rhovecL1)).eval();
182 auto DELTAL1L2_dchempot_dT = (DELTAL1L2dmu_dT_res + RL1*log(rhovecL1) - RL2*log(rhovecL2)).eval();
185 r.head(N) = PsirgradV + RTV*log(rhovecV) - (PsirgradL1 + RTL1*log(rhovecL1));
186 r.segment(N,N) = PsirgradL1 + RTL1 * log(rhovecL1) - (PsirgradL2 + RTL2 * log(rhovecL2));
189 r(2*N+1) = pL1 - pL2;
195 J.block(0,0,N,N) = HtotV;
196 J.block(0,N,N,N) = -HtotL1;
198 J.block(0,2*N+2, N, 1) = DELTAVL1_dchempot_dT;
201 J.block(N,N, N, N) = HtotL1;
202 J.block(N,2* N, N, N) = -HtotL2;
203 J.block(N,2*N+2, N, 1) = DELTAL1L2_dchempot_dT;
207 J.block(2*N, 0, 1, N) = dpdrhovecV.transpose();
208 J.block(2*N, N, 1, N) = -dpdrhovecL1.transpose();
210 J.block(2 * N + 1, N, 1, N) = dpdrhovecL1.transpose();
211 J.block(2 * N + 1, 2 * N, 1, N) = -dpdrhovecL2.transpose();
214 J.block(2*N+2, 0, 1, N) = dpdrhovecV.transpose();
219 Eigen::ArrayXd dx = J.colPivHouseholderQr().solve(-r);
223 auto xtol_threshold = (axtol + relxtol * x.array().cwiseAbs()).eval();
224 if ((dx.array() < xtol_threshold).all()) {
229 auto error_threshold = (atol + reltol * r.array().cwiseAbs()).eval();
230 if ((r.array().cwiseAbs() < error_threshold).all()) {
238 if (((x.array() - dx.array()).cwiseAbs() < std::numeric_limits<double>::min()).all()) {
242 if (iter == maxiter - 1) {
247 Eigen::ArrayXd rhovecVfinal = rhovecV, rhovecL1final = rhovecL1, rhovecL2final = rhovecL2;
248 return std::make_tuple(return_code, Tfinal, rhovecVfinal, rhovecL1final, rhovecL2final);
300 inline auto find_VLLE_gen_binary(
const AbstractModel& model,
const std::vector<nlohmann::json>& traces,
const std::string& key,
const std::optional<VLLEFinderOptions> options = std::nullopt) {
301 std::vector<double> x, y;
304 Eigen::ArrayXd rhoL1(2), rhoL2(2), rhoV(2);
305 std::string xkey = (key ==
"T") ?
"T / K" :
"pL / Pa";
306 std::string ykey = (key ==
"T") ?
"pL / Pa" :
"T / K";
309 auto avg_values = [](
const nlohmann::json&j1,
const nlohmann::json &j2,
const std::string& key,
const double w) -> Eigen::ArrayXd{
310 auto v1 = j1.at(key).template get<std::valarray<double>>();
311 auto v2 = j2.at(key).template get<std::valarray<double>>();
312 std::valarray<double> vs = v1*w + v2*(1 - w);
313 return Eigen::Map<Eigen::ArrayXd>(&(vs[0]), vs.size());
316 if (traces.empty()) {
319 else if (traces.size() == 1) {
321 for (
auto& el : traces[0]) {
322 auto rhoV_ = el.at(
"rhoV / mol/m^3").get<std::valarray<double>>();
323 auto y_ = el.at(ykey).get<
double>();
324 x.push_back(rhoV_[0] / rhoV_.sum());
330 auto process_intersection = [&](
auto& trace,
auto& i) {
331 rhoL1 = avg_values(trace[i.j], trace[i.j + 1],
"rhoL / mol/m^3", i.s);
332 rhoL2 = avg_values(trace[i.k], trace[i.k + 1],
"rhoL / mol/m^3", i.t);
333 rhoV = avg_values(trace[i.j], trace[i.j + 1],
"rhoV / mol/m^3", i.s);
336 double T = trace[0].at(
"T / K");
339 auto [code, rhoVfinal, rhoL1final, rhoL2final] =
mix_VLLE_T(model, T, rhoV, rhoL1, rhoL2, 1e-10, 1e-10, 1e-10, 1e-10, opt.max_steps);
341 return nlohmann::json{
342 {
"variables",
"rhoV, rhoL1, rhoL2"},
343 {
"approximate", {rhoV, rhoL1, rhoL2} },
344 {
"polished", {rhoVfinal, rhoL1final, rhoL2final} },
345 {
"polisher_return_code",
static_cast<int>(code)}
348 else if (key ==
"P"){
349 double p = trace[0].at(
"pL / Pa");
352 auto [code, Tfinal, rhoVfinal, rhoL1final, rhoL2final] =
mix_VLLE_p(model, p, i.y, rhoV, rhoL1, rhoL2, 1e-10, 1e-10, 1e-10, 1e-10, opt.max_steps);
354 return nlohmann::json{
355 {
"variables",
"rhoV, rhoL1, rhoL2, T"},
356 {
"approximate", {rhoV, rhoL1, rhoL2, i.y} },
357 {
"polished", {rhoVfinal, rhoL1final, rhoL2final, Tfinal} },
358 {
"polisher_return_code",
static_cast<int>(code)}
365 std::vector<nlohmann::json> solutions;
367 for (
auto& intersection : intersections) {
369 auto soln = process_intersection(traces[0], intersection);
370 auto rhovecL1 = soln.at(
"polished")[1].template get<std::valarray<double>>();
371 auto rhovecL2 = soln.at(
"polished")[2].template get<std::valarray<double>>();
372 auto rhodiff = 100*(rhovecL1.sum() / rhovecL2.sum() - 1);
373 if (std::abs(rhodiff) > opt.rho_trivial_threshold) {
375 solutions.push_back(soln);
385 else if (traces.size() == 2) {
387 std::vector<double> x1, y1, x2, y2;
389 for (
auto& el : traces[0]) {
390 auto rhoV_ = el.at(
"rhoV / mol/m^3").get<std::valarray<double>>();
391 auto y_ = el.at(ykey).get<
double>();
392 x1.push_back(rhoV_[0] / rhoV_.sum());
395 for (
auto& el : traces[1]) {
396 auto rhoV_ = el.at(
"rhoV / mol/m^3").get<std::valarray<double>>();
397 auto y_ = el.at(ykey).get<
double>();
398 x2.push_back(rhoV_[0] / rhoV_.sum());
403 auto process_intersection = [&](
auto& i) {
404 rhoL1 = avg_values(traces[0][i.j], traces[0][i.j + 1],
"rhoL / mol/m^3", i.s);
405 rhoL2 = avg_values(traces[1][i.k], traces[1][i.k + 1],
"rhoL / mol/m^3", i.t);
406 rhoV = avg_values(traces[0][i.j], traces[0][i.j + 1],
"rhoV / mol/m^3", i.s);
409 double T = traces[0][0].at(xkey);
412 auto [code, rhoVfinal, rhoL1final, rhoL2final] =
mix_VLLE_T(model, T, rhoV, rhoL1, rhoL2, 1e-10, 1e-10, 1e-10, 1e-10, opt.max_steps);
414 return nlohmann::json{
415 {
"variables",
"rhoV, rhoL1, rhoL2"},
416 {
"approximate", {rhoV, rhoL1, rhoL2} },
417 {
"polished", {rhoVfinal, rhoL1final, rhoL2final} },
418 {
"polisher_return_code",
static_cast<int>(code)}
421 else if (key ==
"P"){
422 double p = traces[0][0].at(xkey);
425 auto [code, Tfinal, rhoVfinal, rhoL1final, rhoL2final] =
mix_VLLE_p(model, p, i.y, rhoV, rhoL1, rhoL2, 1e-10, 1e-10, 1e-10, 1e-10, opt.max_steps);
427 return nlohmann::json{
428 {
"variables",
"rhoV, rhoL1, rhoL2, T"},
429 {
"approximate", {rhoV, rhoL1, rhoL2, i.y} },
430 {
"polished", {rhoVfinal, rhoL1final, rhoL2final, Tfinal} },
431 {
"polisher_return_code",
static_cast<int>(code)}
438 std::vector<nlohmann::json> solutions;
440 for (
auto& intersection : intersections) {
442 auto soln = process_intersection(intersection);
443 auto rhovecL1 = soln.at(
"polished")[1].template get<std::valarray<double>>();
444 auto rhovecL2 = soln.at(
"polished")[2].template get<std::valarray<double>>();
445 auto rhodiff = 100*(rhovecL1.sum() / rhovecL2.sum() - 1);
446 if (std::abs(rhodiff) > opt.rho_trivial_threshold) {
448 solutions.push_back(soln);
459 throw InvalidArgument(
"No cross intersection between traces implemented yet");
483 auto dot = [](
const EArrayd&a,
const EArrayd &b){
return a.cwiseProduct(b).sum(); };
485 Eigen::MatrixXd LHS(2, 2);
486 Eigen::MatrixXd RHS(2, 1);
496 double RV = model.
R(rhovecV/rhovecV.sum());
497 double RL1 = model.
R(rhovecL1/rhovecL1.sum());
498 double RL2 = model.
R(rhovecL2/rhovecL2.sum());
503 LHS.row(0) = PSIV*(rhovecL1-rhovecV).matrix();
504 LHS.row(1) = PSIV*(rhovecL2-rhovecV).matrix();
505 RHS(0) = dot(dmudTL1-dmudTV, rhovecL1) - (dpdTL1-dpdTV);
506 RHS(1) = dot(dmudTL2-dmudTV, rhovecL2) - (dpdTL2-dpdTV);
508 Eigen::ArrayXd drhovecVdT = LHS.colPivHouseholderQr().solve(RHS);
509 Eigen::VectorXd AV = PSIV*drhovecVdT.matrix();
510 Eigen::ArrayXd drhovecL1dT = PSIL1.colPivHouseholderQr().solve((AV.array() - (dmudTL1-dmudTV)).matrix());
511 Eigen::ArrayXd drhovecL2dT = PSIL2.colPivHouseholderQr().solve((AV.array() - (dmudTL2-dmudTV)).matrix());
513 return std::make_tuple(drhovecVdT, drhovecL1dT, drhovecL2dT);
523 using state_type = std::vector<double>;
524 using namespace boost::numeric::odeint;
526 typedef runge_kutta_cash_karp54< state_type > error_stepper_type;
527 typedef controlled_runge_kutta< error_stepper_type > controlled_stepper_type;
529 auto xprime = [&](
const state_type& x, state_type& dxdt,
const double T)
532 const auto rhovecV_ = Eigen::Map<const Eigen::ArrayXd>(&(x[0]), 2),
533 rhovecL1_ = Eigen::Map<const Eigen::ArrayXd>(&(x[0]) + 2, 2),
534 rhovecL2_ = Eigen::Map<const Eigen::ArrayXd>(&(x[0]) + 4, 2);
537 Eigen::Map<Eigen::ArrayXd>(&(dxdt[0]), 2) = drhovecVdT;
538 Eigen::Map<Eigen::ArrayXd>(&(dxdt[0]) + 2, 2) = drhovecL1dT;
539 Eigen::Map<Eigen::ArrayXd>(&(dxdt[0]) + 4, 2) = drhovecL2dT;
543 double abs_err = options.abs_err, rel_err = options.rel_err, a_x = 1.0, a_dxdt = 1.0;
544 controlled_stepper_type controlled_stepper(default_error_checker< double, range_algebra, default_operations >(abs_err, rel_err, a_x, a_dxdt));
547 double T = Tinit, dT = options.init_dT;
549 Eigen::Map<Eigen::ArrayXd>(&(x0[0]), 2) = rhovecVinit;
550 Eigen::Map<Eigen::ArrayXd>(&(x0[0]) + 2, 2) = rhovecL1init;
551 Eigen::Map<Eigen::ArrayXd>(&(x0[0]) + 4, 2) = rhovecL2init;
553 nlohmann::json data_collector = nlohmann::json::array();
554 for (
auto iter = 0; iter < options.max_step_count; ++iter) {
557 auto res = controlled_step_result::fail;
559 res = controlled_stepper.try_step(xprime, x0, T, dT);
561 catch (
const std::exception &e) {
562 if (options.verbosity > 0) {
563 std::cout << e.what() << std::endl;
568 if (res != controlled_step_result::success) {
578 dT = std::min(dT, options.max_dT);
580 const auto rhovecV = Eigen::Map<const Eigen::ArrayXd>(&(x0[0]), 2),
581 rhovecL1 = Eigen::Map<const Eigen::ArrayXd>(&(x0[0]) + 2, 2),
582 rhovecL2 = Eigen::Map<const Eigen::ArrayXd>(&(x0[0]) + 4, 2);
586 auto [code, rhovecVnew, rhovecL1new, rhovecL2new] =
teqp::VLLE::mix_VLLE_T(model, T, rhovecV, rhovecL1, rhovecL2, 1e-10, 1e-10, 1e-10, 1e-10, options.max_polish_steps);
587 Eigen::Map<Eigen::ArrayXd>(&(x0[0]), 2) = rhovecVnew;
588 Eigen::Map<Eigen::ArrayXd>(&(x0[0]) + 2, 2) = rhovecL1new;
589 Eigen::Map<Eigen::ArrayXd>(&(x0[0]) + 4, 2) = rhovecL2new;
595 double rhomolarV = rhovecV.sum();
596 auto molefracV = rhovecV/rhomolarV;
597 auto pV = rhomolarV*model.
get_R(molefracV)*T*(1.0 + model.
get_Ar01(T, rhomolarV, molefracV));
598 if (!std::isfinite(pV)){
599 if (options.verbosity > 0) {
600 std::cout <<
"Calculated pressure is not finite" << std::endl;
604 if(options.init_dT > 0 && T > options.T_limit){
605 if (options.verbosity > 0) {
606 std::cout <<
"Exceeded maximum temperature of " << options.T_limit <<
" K" << std::endl;
610 if(options.init_dT < 0 && T < options.T_limit){
611 if (options.verbosity > 0) {
612 std::cout <<
"Exceeded minimum temperature of " << options.T_limit <<
" K" << std::endl;
617 if (options.verbosity > 100){
618 std::cout <<
"[T,x0L1,x0L2,x0V]: " << T <<
"," << rhovecL1[0]/rhovecL1.sum() <<
"," << rhovecL2[0]/rhovecL2.sum() <<
"," << rhovecV[0]/rhovecV.sum() << std::endl;
619 std::cout <<
"[crits]: " << critV <<
"," << critL1 <<
"," << critL2 << std::endl;
622 if (options.terminate_composition){
623 auto c0 = (Eigen::ArrayXd(3) << rhovecL1[0]/rhovecL1.sum(), rhovecL2[0]/rhovecL2.sum(), rhovecV[0]/rhovecV.sum()).finished();
624 auto diffs = (Eigen::ArrayXd(3) << c0[0]-c0[1], c0[0]-c0[2], c0[1]-c0[2]).finished();
625 if ((diffs.cwiseAbs() < options.terminate_composition_tol).any()){
629 if (retry_count > options.max_step_retries){
630 if (options.verbosity > 0) {
631 std::cout <<
"Max retries of step exceeded." << std::endl;
636 nlohmann::json entry{
638 {
"rhoL1 / mol/m^3", rhovecL1},
639 {
"rhoL2 / mol/m^3", rhovecL2},
640 {
"rhoV / mol/m^3", rhovecV},
646 data_collector.push_back(entry);
648 return data_collector;