teqp 0.22.0
Loading...
Searching...
No Matches
VLE_pure.hpp
Go to the documentation of this file.
1//
2// VLE_pure.hpp
3// teqp
4//
5// Created by Bell, Ian H. (Fed) on 5/3/23.
6//
7#pragma once
8#include <type_traits>
10#include "teqp/derivs.hpp"
12
13namespace teqp{
14
15template<typename Model> using is_AbstractModel = typename std::is_base_of<teqp::cppinterface::AbstractModel, Model>;
16template<typename Model> using is_not_AbstractModel = std::negation<is_AbstractModel<Model>>;
17
18template<typename Model, typename TYPE=double, ADBackends backend = ADBackends::autodiff>
20public:
21 using EigenArray = Eigen::Array<TYPE, 2, 1>;
22 using EigenArray1 = Eigen::Array<TYPE, 1, 1>;
23 using EigenMatrix = Eigen::Array<TYPE, 2, 2>;
24private:
25 const Model& m_model;
26 const TYPE m_T;
27 const Eigen::ArrayXd molefracs;
29 EigenArray y;
30public:
31 std::size_t icall = 0;
32 double Rr, R0;
33
34 IsothermPureVLEResiduals(const Model& model, const TYPE& T, const std::optional<Eigen::ArrayXd>& molefracs_ = std::nullopt) : m_model(model), m_T(T),
35 molefracs( (molefracs_) ? molefracs_.value() : Eigen::ArrayXd::Ones(1,1)) {
37 Rr = m_model.R(molefracs);
38 R0 = m_model.R(molefracs);
39 }
40 else{
41 Rr = m_model.get_R(molefracs);
42 R0 = m_model.get_R(molefracs);
43 }
44 };
45
46 const auto& get_errors() { return y; };
47
48 template<typename Rho>
49 auto get_der(const Rho& rho){
52 return tdx::template get_Ar0n<2, backend>(m_model, m_T, rho, molefracs);
53 }
54 else{
55 return m_model.get_Ar02n(m_T, rho, molefracs);
56 }
57 }
58
59 auto call(const EigenArray& rhovec) {
60 assert(rhovec.size() == 2);
61
62 const EigenArray1 rhovecL = rhovec.head(1);
63 const EigenArray1 rhovecV = rhovec.tail(1);
64 const auto rhomolarL = rhovecL.sum(), rhomolarV = rhovecV.sum();
65
66 //const TYPE R = m_model.R(molefracs);
67 double R0_over_Rr = R0 / Rr;
68
69 auto derL = get_der(rhomolarL);
70 auto pRTL = rhomolarL*(R0_over_Rr + derL[1]); // p/(R*T)
71 auto dpRTLdrhoL = R0_over_Rr + 2*derL[1] + derL[2];
72 auto hatmurL = derL[1] + derL[0] + R0_over_Rr*log(rhomolarL);
73 auto dhatmurLdrho = (2*derL[1] + derL[2])/rhomolarL + R0_over_Rr/rhomolarL;
74
75 auto derV = get_der(rhomolarV);
76 auto pRTV = rhomolarV*(R0_over_Rr + derV[1]); // p/(R*T)
77 auto dpRTVdrhoV = R0_over_Rr + 2*derV[1] + derV[2];
78 auto hatmurV = derV[1] + derV[0] + R0_over_Rr *log(rhomolarV);
79 auto dhatmurVdrho = (2*derV[1] + derV[2])/rhomolarV + R0_over_Rr/rhomolarV;
80
81 y(0) = pRTL - pRTV;
82 J(0, 0) = dpRTLdrhoL;
83 J(0, 1) = -dpRTVdrhoV;
84
85 y(1) = hatmurL - hatmurV;
86 J(1, 0) = dhatmurLdrho;
87 J(1, 1) = -dhatmurVdrho;
88
89 icall++;
90 return y;
91 }
92 auto Jacobian(const EigenArray& /*rhovec*/){
93 return J;
94 }
95 //auto numJacobian(const EigenArray& rhovec) {
96 // EigenArray plus0 = rhovec, plus1 = rhovec;
97 // double dr = 1e-6 * rhovec[0];
98 // plus0[0] += dr; plus1[1] += dr;
99 // EigenMatrix J;
100 // J.col(0) = (call(plus0) - call(rhovec))/dr;
101 // J.col(1) = (call(plus1) - call(rhovec))/dr;
102 // return J;
103 //}
104};
105
106template<typename Residual, typename Scalar=double>
107auto do_pure_VLE_T(Residual &resid, Scalar rhoL, Scalar rhoV, int maxiter) {
108 using EArray = Eigen::Array<Scalar, 2, 1>;
109 auto rhovec = (EArray() << rhoL, rhoV).finished();
110 auto r0 = resid.call(rhovec);
111 auto J = resid.Jacobian(rhovec);
112 for (int iter = 0; iter < maxiter; ++iter){
113 if (iter > 0) {
114 r0 = resid.call(rhovec);
115 J = resid.Jacobian(rhovec);
116 }
117 auto v = J.matrix().colPivHouseholderQr().solve(-r0.matrix()).array().eval();
118 auto rhovecnew = (rhovec + v).eval();
119 //double r00 = static_cast<double>(r0[0]);
120 //double r01 = static_cast<double>(r0[1]);
121
122 // If the solution has stopped improving, stop. The change in rhovec is equal to v in infinite precision, but
123 // not when finite precision is involved, use the minimum non-denormal float as the determination of whether
124 // the values are done changing
125 auto minval = std::numeric_limits<Scalar>::epsilon();
126 //double minvaldbl = static_cast<double>(minval);
127 if (((rhovecnew - rhovec).cwiseAbs() < minval).all()) {
128 break;
129 }
130 if ((r0.cwiseAbs() < minval).all()) {
131 break;
132 }
133 rhovec = rhovecnew;
134 }
135 return rhovec;
136}
137
138inline auto pure_VLE_T(const teqp::cppinterface::AbstractModel& model, double T, double rhoL, double rhoV, int maxiter, const std::optional<Eigen::ArrayXd>& molefracs = std::nullopt) {
139 Eigen::ArrayXd molefracs_{Eigen::ArrayXd::Ones(1,1)};
140 if (molefracs){ molefracs_ = molefracs.value(); }
142 return do_pure_VLE_T(res, rhoL, rhoV, maxiter);
143}
144
145/***
146 * \brief Calculate the derivative of vapor pressure with respect to temperature
147 * \param model The model to operate on
148 * \param T Temperature
149 * \param rhoL Liquid density
150 * \param rhoV Vapor density
151 *
152 * Based upon
153 * \f[
154 * \frac{dp_{\sigma}}{dT} = \frac{h''-h'}{T(v''-v')} = \frac{s''-s'}{v''-v'}
155 * \f]
156 * 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
157 */
158inline auto dpsatdT_pure(const teqp::cppinterface::AbstractModel& model, double T, double rhoL, double rhoV, const std::optional<Eigen::ArrayXd>& molefracs = std::nullopt) {
159
160 auto molefrac = (Eigen::ArrayXd(1) << 1.0).finished();
161 if (molefracs){ molefrac = molefracs.value(); }
162
163 auto R = model.get_R(molefrac);
164
165 auto hrVLERTV = model.get_Ar01(T, rhoV, molefrac) + model.get_Ar10(T, rhoV, molefrac);
166 auto hrVLERTL = model.get_Ar01(T, rhoL, molefrac) + model.get_Ar10(T, rhoL, molefrac);
167 auto deltahr_over_T = R*(hrVLERTV-hrVLERTL);
168 auto dpsatdT = deltahr_over_T/(1/rhoV-1/rhoL); // From Clapeyron; dp/dT = Deltas/Deltav = Deltah/(T*Deltav); Delta=V-L
169 return dpsatdT;
170}
171
172/***
173 \brief Starting at the critical point, trace the VLE down to a temperature of interest
174
175 \note This method only works for well-behaved EOS, notably absent from that category are EOS in the multiparameter category with orthobaric scaling exponent not equal to 0.5 at the critical point. Most other analytical EOS work fine
176
177 The JSON data structure defines the variables that need to be specified.
178
179 In the current implementation, there are a few steps:
180 1. Solve for the true critical point satisfying \f$(\partial p/\partial \rho)_{T}=(\partial^2p/\partial\rho^2)_{T}=0\f$
181 2. Take a small step away from the critical point (this is where the beta=0.5 assumption is invoked)
182 3. Integrate from the near critical temperature to the temperature of interest
183 */
184inline auto pure_trace_VLE(const teqp::cppinterface::AbstractModel& model, const double T, const nlohmann::json &spec){
185 // Start at the true critical point, from the specified guess value
186 nlohmann::json pure_spec;
187 Eigen::ArrayXd z{Eigen::ArrayXd::Ones(1,1)};
188 if (spec.contains("pure_spec")){
189 pure_spec = spec.at("pure_spec");
190 z = Eigen::ArrayXd(pure_spec.at("alternative_length").get<int>()); z.setZero();
191 z(pure_spec.at("alternative_pure_index").get<int>()) = 1;
192 }
193
194 auto [Tc, rhoc] = solve_pure_critical(model, spec.at("Tcguess").get<double>(), spec.at("rhocguess").get<double>(), pure_spec);
195
196 // Small step towards lower temperature close to critical temperature
197 double Tclose = spec.at("Tred").get<double>()*Tc;
198 auto rhoLrhoV = extrapolate_from_critical(model, Tc, rhoc, Tclose, z);
199 auto rhoLrhoVpolished = pure_VLE_T(model, Tclose, rhoLrhoV[0], rhoLrhoV[1], spec.value("NVLE", 10), z);
200 if (rhoLrhoVpolished[0] == rhoLrhoVpolished[1]){
201 throw teqp::IterationError("Converged to trivial solution");
202 }
203
204 // "Integrate" down to temperature of interest
205 int Nstep = spec.at("Nstep");
206 double R = model.R(z);
207 bool with_deriv = spec.at("with_deriv");
208 double dT = -(Tclose-T)/(Nstep-1);
209
210 for (auto T_: Eigen::ArrayXd::LinSpaced(Nstep, Tclose, T)){
211 rhoLrhoVpolished = pure_VLE_T(model, T_, rhoLrhoVpolished[0], rhoLrhoVpolished[1], spec.value("NVLE", 10), z);
212
213 //auto pL = rhoLrhoVpolished[0]*R*T_*(1 + tdx::get_Ar01(model, T_, rhoLrhoVpolished[0], z));
214 //auto pV = rhoLrhoVpolished[1]*R*T_*(1 + tdx::get_Ar01(model, T_, rhoLrhoVpolished[1], z));
215 //std::cout << pL << " " << pV << " " << pL/pV-1 << std::endl;
216 if (with_deriv){
217 // Get drho/dT for both phases
218 auto dpsatdT = dpsatdT_pure(model, T_, rhoLrhoVpolished[0], rhoLrhoVpolished[1], z);
219 auto get_drhodT = [&z, R, dpsatdT](const AbstractModel& model, double T, double rho){
220 auto dpdrho = R*T*(1 + 2*model.get_Ar01(T, rho, z) + model.get_Ar02(T, rho, z));
221 auto dpdT = R*rho*(1 + model.get_Ar01(T, rho, z) - model.get_Ar11(T, rho, z));
222 return -dpdT/dpdrho + dpsatdT/dpdrho;
223 };
224 auto drhodTL = get_drhodT(model, T_, rhoLrhoVpolished[0]);
225 auto drhodTV = get_drhodT(model, T_, rhoLrhoVpolished[1]);
226 // Use the obtained derivative to calculate the step in rho from deltarho = (drhodT)*dT
227 auto DeltarhoL = dT*drhodTL, DeltarhoV = dT*drhodTV;
228 rhoLrhoVpolished[0] += DeltarhoL;
229 rhoLrhoVpolished[1] += DeltarhoV;
230 }
231
232 // Updated values for densities at new T
233 if (!std::isfinite(rhoLrhoVpolished[0])){
234 throw teqp::IterationError("The density is no longer valid; try increasing Nstep");
235 }
236 if (rhoLrhoVpolished[0] == rhoLrhoVpolished[1]){
237 throw teqp::IterationError("Converged to trivial solution; try increasing Nstep");
238 }
239 }
240 return rhoLrhoVpolished;
241}
242
243#define VLE_PURE_FUNCTIONS_TO_WRAP \
244 X(dpsatdT_pure) \
245 X(pure_VLE_T) \
246 X(pure_trace_VLE)
247
248#define X(f) template <typename TemplatedModel, typename ...Params, \
249typename = typename std::enable_if<is_not_AbstractModel<TemplatedModel>::value>::type> \
250inline auto f(const TemplatedModel& model, Params&&... params){ \
251 auto view = teqp::cppinterface::adapter::make_cview(model); \
252 const AbstractModel& am = *view.get(); \
253 return f(am, std::forward<Params>(params)...); \
254}
256#undef X
257#undef VLE_PURE_FUNCTIONS_TO_WRAP
258
259}
#define VLE_PURE_FUNCTIONS_TO_WRAP
Definition VLE_pure.hpp:243
Eigen::Array< TYPE, 1, 1 > EigenArray1
Definition VLE_pure.hpp:22
auto call(const EigenArray &rhovec)
Definition VLE_pure.hpp:59
Eigen::Array< TYPE, 2, 2 > EigenMatrix
Definition VLE_pure.hpp:23
auto get_der(const Rho &rho)
Definition VLE_pure.hpp:49
auto Jacobian(const EigenArray &)
Definition VLE_pure.hpp:92
IsothermPureVLEResiduals(const Model &model, const TYPE &T, const std::optional< Eigen::ArrayXd > &molefracs_=std::nullopt)
Definition VLE_pure.hpp:34
Eigen::Array< TYPE, 2, 1 > EigenArray
Definition VLE_pure.hpp:21
virtual double get_Ar01(const double T, const double rho, const REArrayd &molefrac) const =0
virtual double get_R(const EArrayd &) const =0
virtual double get_Ar10(const double T, const double rho, const REArrayd &molefrac) const =0
double R(const EArrayd &x) const
Definition teqpcpp.hpp:102
std::negation< is_AbstractModel< Model > > is_not_AbstractModel
Definition VLE_pure.hpp:16
auto pure_trace_VLE(const teqp::cppinterface::AbstractModel &model, const double T, const nlohmann::json &spec)
Definition VLE_pure.hpp:184
auto do_pure_VLE_T(Residual &resid, Scalar rhoL, Scalar rhoV, int maxiter)
Definition VLE_pure.hpp:107
auto solve_pure_critical(const Model &model, const Scalar T0, const Scalar rho0, const std::optional< nlohmann::json > &flags=std::nullopt)
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)
IterationFailure IterationError
auto dpsatdT_pure(const teqp::cppinterface::AbstractModel &model, double T, double rhoL, double rhoV, const std::optional< Eigen::ArrayXd > &molefracs=std::nullopt)
Definition VLE_pure.hpp:158
typename std::is_base_of< teqp::cppinterface::AbstractModel, Model > is_AbstractModel
Definition VLE_pure.hpp:15
auto pure_VLE_T(const teqp::cppinterface::AbstractModel &model, double T, double rhoL, double rhoV, int maxiter, const std::optional< Eigen::ArrayXd > &molefracs=std::nullopt)
Definition VLE_pure.hpp:138