diff --git a/include/teqp/algorithms/VLE.hpp b/include/teqp/algorithms/VLE.hpp index 3a17d69c6e90fc2ba4f71db5ddabd146c651442f..141347122c1f245be1e110125f15fde498108031 100644 --- a/include/teqp/algorithms/VLE.hpp +++ b/include/teqp/algorithms/VLE.hpp @@ -63,18 +63,18 @@ public: using EigenMatrix = Eigen::Array<TYPE, 2, 2>; private: const Model& m_model; - TYPE m_T; + const TYPE m_T; + const Eigen::ArrayXd molefracs; EigenMatrix J; EigenArray y; - public: std::size_t icall = 0; double Rr, R0; - IsothermPureVLEResiduals(const Model& model, TYPE T) : m_model(model), m_T(T) { - std::valarray<double> molefrac = { 1.0 }; - Rr = m_model.R(molefrac); - R0 = m_model.R(molefrac); + IsothermPureVLEResiduals(const Model& model, const TYPE& T, const std::optional<Eigen::ArrayXd>& molefracs_ = std::nullopt) : m_model(model), m_T(T), + molefracs( (molefracs_) ? molefracs_.value() : Eigen::ArrayXd::Ones(1,1)) { + Rr = m_model.R(molefracs); + R0 = m_model.R(molefracs); }; const auto& get_errors() { return y; }; @@ -85,9 +85,8 @@ public: const EigenArray1 rhovecL = rhovec.head(1); const EigenArray1 rhovecV = rhovec.tail(1); const auto rhomolarL = rhovecL.sum(), rhomolarV = rhovecV.sum(); - const auto molefracs = (EigenArray1() << 1.0).finished(); - using tdx = TDXDerivatives<Model,TYPE,EigenArray1>; + using tdx = TDXDerivatives<Model,TYPE,Eigen::ArrayXd>; const TYPE &T = m_T; //const TYPE R = m_model.R(molefracs); @@ -163,11 +162,48 @@ auto do_pure_VLE_T(Residual &resid, Scalar rhoL, Scalar rhoV, int maxiter) { } template<typename Model, typename Scalar, ADBackends backend = ADBackends::autodiff> -auto pure_VLE_T(const Model& model, Scalar T, Scalar rhoL, Scalar rhoV, int maxiter) { - auto res = IsothermPureVLEResiduals<Model, Scalar, backend>(model, T); +auto pure_VLE_T(const Model& model, Scalar T, Scalar rhoL, Scalar rhoV, int maxiter, const std::optional<Eigen::ArrayXd>& molefracs = std::nullopt) { + Eigen::ArrayXd molefracs_{Eigen::ArrayXd::Ones(1,1)}; + if (molefracs){ molefracs_ = molefracs.value(); } + auto res = IsothermPureVLEResiduals<Model, Scalar, backend>(model, T, molefracs_); return do_pure_VLE_T(res, rhoL, rhoV, maxiter); } +template<typename Model> +auto pure_trace_VLE(const Model& model, const double T, const nlohmann::json &spec){ + // Start at the true critical point, from the specified guess value + nlohmann::json pure_spec; + Eigen::ArrayXd z{Eigen::ArrayXd::Ones(1,1)}; + if (spec.contains("pure_spec")){ + pure_spec = spec.at("pure_spec"); + z = Eigen::ArrayXd(pure_spec.at("alternative_length").get<int>()); z.setZero(); + z(pure_spec.at("alternative_pure_index").get<int>()) = 1; + } + + auto [Tc, rhoc] = solve_pure_critical(model, spec.at("Tcguess").get<double>(), spec.at("rhocguess").get<double>(), pure_spec); + + // Small step towards lower temperature close to critical temperature + double Tclose = spec.at("Tred").get<double>()*Tc; + auto rhoLrhoV = extrapolate_from_critical(model, Tc, rhoc, Tclose, z); + auto rhoLrhoVpolished = pure_VLE_T(model, Tclose, rhoLrhoV[0], rhoLrhoV[1], spec.value("NVLE", 10), z); + if (rhoLrhoVpolished[0] == rhoLrhoVpolished[1]){ + throw teqp::IterationError("Converged to trivial solution"); + } + + // Integrate down to temperature of interest + int Nstep = spec.at("Nstep"); + for (auto T_: Eigen::ArrayXd::LinSpaced(Nstep, Tclose, T)){ + rhoLrhoVpolished = pure_VLE_T(model, T_, rhoLrhoVpolished[0], rhoLrhoVpolished[1], spec.value("NVLE", 10), z); + if (!std::isfinite(rhoLrhoVpolished[0])){ + throw teqp::IterationError("The density is no longer valid; try increasing Nstep"); + } + if (rhoLrhoVpolished[0] == rhoLrhoVpolished[1]){ + throw teqp::IterationError("Converged to trivial solution; try increasing Nstep"); + } + } + return rhoLrhoVpolished; +} + /*** * \brief Calculate the derivative of vapor pressure with respect to temperature * \param model The model to operate on @@ -182,9 +218,10 @@ auto pure_VLE_T(const Model& model, Scalar T, Scalar rhoL, Scalar rhoV, int maxi * where the \f$h''-h'\f$ is given by the difference in residual enthalpy \f$h''-h' = h^r''-h^r'\f$ because the ideal-gas parts cancel */ template<typename Model, typename Scalar, ADBackends backend = ADBackends::autodiff> -auto dpsatdT_pure(const Model& model, Scalar T, Scalar rhoL, Scalar rhoV) { +auto dpsatdT_pure(const Model& model, Scalar T, Scalar rhoL, Scalar rhoV, const std::optional<Eigen::ArrayXd>& molefracs = std::nullopt) { auto molefrac = (Eigen::ArrayXd(1) << 1.0).finished(); + if (molefracs){ molefrac = molefracs.value(); } using tdx = TDXDerivatives<decltype(model), double, decltype(molefrac)>; using iso = IsochoricDerivatives<decltype(model), double, decltype(molefrac)>; @@ -939,7 +976,8 @@ auto get_dpsat_dTsat_isopleth(const Model& model, const Scalar& T, const VecType } /*** -* \brief Trace an isotherm with parametric tracing + * \brief Trace an isotherm with parametric tracing + * \ note If options.revision is 2, the data will be returned in the "data" field, otherwise the data will be returned as root array */ template<typename Model, typename Scalar, typename VecType> auto trace_VLE_isotherm_binary(const Model &model, Scalar T, VecType rhovecL0, VecType rhovecV0, const std::optional<TVLEOptions>& options = std::nullopt) @@ -1167,7 +1205,22 @@ auto trace_VLE_isotherm_binary(const Model &model, Scalar T, VecType rhovecL0, V store_point(); // last_drhodt is updated; } - return JSONdata; + if (opt.revision == 1){ + return JSONdata; + } + else if (opt.revision == 2){ + nlohmann::json meta{ + {"termination_reason", termination_reason} + }; + return nlohmann::json{ + {"meta", meta}, + {"data", JSONdata} + }; + } + else + { + throw teqp::InvalidArgument("revision is not valid"); + } } /*** diff --git a/include/teqp/algorithms/VLE_types.hpp b/include/teqp/algorithms/VLE_types.hpp index 90bf24026e9db526734db47638f5c2e6afec2b05..d9dd1666f165c66bb71655e8a0b3102a2805d6fa 100644 --- a/include/teqp/algorithms/VLE_types.hpp +++ b/include/teqp/algorithms/VLE_types.hpp @@ -4,7 +4,7 @@ namespace teqp{ struct TVLEOptions { double init_dt = 1e-5, abs_err = 1e-8, rel_err = 1e-8, max_dt = 100000, init_c = 1.0, p_termination = 1e15, crit_termination = 1e-12; - int max_steps = 1000, integration_order = 5; + int max_steps = 1000, integration_order = 5, revision = 1; bool polish = true; bool calc_criticality = false; bool terminate_unstable = false; diff --git a/include/teqp/algorithms/critical_pure.hpp b/include/teqp/algorithms/critical_pure.hpp index df6afb6876329e5dbe7bb213e1f81f17c86b1798..04f9055fc1f8ec0712d5842d6bfae4aaf383eeef 100644 --- a/include/teqp/algorithms/critical_pure.hpp +++ b/include/teqp/algorithms/critical_pure.hpp @@ -77,20 +77,25 @@ namespace teqp { } template<typename Model, typename Scalar, ADBackends backend = ADBackends::autodiff> - auto solve_pure_critical(const Model& model, const Scalar T0, const Scalar rho0, const nlohmann::json& flags = {}) { + auto solve_pure_critical(const Model& model, const Scalar T0, const Scalar rho0, const std::optional<nlohmann::json>& flags = std::nullopt) { auto x = (Eigen::ArrayXd(2) << T0, rho0).finished(); - int maxsteps = (flags.contains("maxsteps")) ? flags.at("maxsteps").get<int>() : 10; + int maxsteps = 10; std::optional<std::size_t> alternative_pure_index; - if (flags.contains("alternative_pure_index")){ - auto i = flags.at("alternative_pure_index").get<int>(); - if (i < 0){ throw teqp::InvalidArgument("alternative_pure_index cannot be less than 0"); } - alternative_pure_index = i; - } std::optional<std::size_t> alternative_length; - if (flags.contains("alternative_length")){ - auto i = flags.at("alternative_length").get<int>(); - if (i < 2){ throw teqp::InvalidArgument("alternative_length cannot be less than 2"); } - alternative_length = i; + if (flags){ + if (flags.value().contains("maxsteps")){ + maxsteps = flags.value().at("maxsteps"); + } + if (flags.value().contains("alternative_pure_index")){ + auto i = flags.value().at("alternative_pure_index").get<int>(); + if (i < 0){ throw teqp::InvalidArgument("alternative_pure_index cannot be less than 0"); } + alternative_pure_index = i; + } + if (flags.value().contains("alternative_length")){ + auto i = flags.value().at("alternative_length").get<int>(); + if (i < 2){ throw teqp::InvalidArgument("alternative_length cannot be less than 2"); } + alternative_length = i; + } } // A convenience method to make linear system solving more concise with Eigen datatypes // All arguments are converted to matrices, the solve is done, and an array is returned @@ -106,19 +111,29 @@ namespace teqp { } template<typename Model, typename Scalar> - Eigen::ArrayXd extrapolate_from_critical(const Model& model, const Scalar& Tc, const Scalar& rhoc, const Scalar& T) { + Scalar get_Brho_critical_extrap(const Model& model, const Scalar& Tc, const Scalar& rhoc, const std::optional<Eigen::ArrayXd>& z = std::nullopt) { using tdx = TDXDerivatives<Model, Scalar>; - auto z = (Eigen::ArrayXd(1) << 1.0).finished(); - auto R = model.R(z); - auto ders = tdx::template get_Ar0n<4>(model, Tc, rhoc, z); + auto z_ = (Eigen::ArrayXd(1) << 1.0).finished(); + if (z){ + z_ = z.value(); + } + auto R = model.R(z_); + auto ders = tdx::template get_Ar0n<4>(model, Tc, rhoc, z_); //auto dpdrho = R*Tc*(1 + 2 * ders[1] + ders[2]); // Should be zero //auto d2pdrho2 = R*Tc/rhoc*(2 * ders[1] + 4 * ders[2] + ders[3]); // Should be zero auto d3pdrho3 = R * Tc / (rhoc * rhoc) * (6 * ders[2] + 6 * ders[3] + ders[4]); - auto Ar11 = tdx::template get_Ar11(model, Tc, rhoc, z); - auto Ar12 = tdx::template get_Ar12(model, Tc, rhoc, z); + auto Ar11 = tdx::template get_Ar11(model, Tc, rhoc, z_); + auto Ar12 = tdx::template get_Ar12(model, Tc, rhoc, z_); auto d2pdrhodT = R * (1 + 2 * ders[1] + ders[2] - 2 * Ar11 - Ar12); auto Brho = sqrt(6 * d2pdrhodT * Tc / d3pdrho3); + return Brho; + } + + template<typename Model, typename Scalar> +Eigen::Array<double, 2, 1> extrapolate_from_critical(const Model& model, const Scalar& Tc, const Scalar& rhoc, const Scalar& T, const std::optional<Eigen::ArrayXd>& z = std::nullopt) { + + auto Brho = get_Brho_critical_extrap(model, Tc, rhoc, z); auto drhohat_dT = Brho / Tc; auto dT = T - Tc; diff --git a/include/teqp/exceptions.hpp b/include/teqp/exceptions.hpp index a17fc2ac7c2addeecb092678e0ababa3d60db1ee..90080a39376a2a7a5a5ed12cb969225fd108ded2 100644 --- a/include/teqp/exceptions.hpp +++ b/include/teqp/exceptions.hpp @@ -36,5 +36,6 @@ namespace teqp { public: IterationFailure(const std::string& msg) : teqpException(100, msg) {}; }; + using IterationError = IterationFailure; -}; // namespace teqp \ No newline at end of file +}; // namespace teqp