teqp 0.22.0
Loading...
Searching...
No Matches
phase_equil.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <optional>
4#include "teqp/exceptions.hpp"
6
7#include <Eigen/Dense>
8
9using namespace teqp::cppinterface;
10
12
14 double rho;
15 double R;
16
17 // Calculated parameters
18 double Psir;
19 Eigen::ArrayXd gradient_Psir;
20 Eigen::ArrayXXd Hessian_Psir;
21 double d_Psir_dT;
22 Eigen::ArrayXd d_gradient_Psir_dT;
23
24 double p(const double T, const Eigen::ArrayXd& rhovec) const{
25 return rho*R*T - Psir + (rhovec*gradient_Psir).sum();
26 }
27 double dpdT(const double T, const Eigen::ArrayXd& rhovec) const{
28 return rho*R - d_Psir_dT + (rhovec*d_gradient_Psir_dT).sum();
29 }
30 Eigen::ArrayXd dpdrhovec(const double T, const Eigen::ArrayXd& rhovec) const{
31 return (R*T + (rhovec.matrix().transpose()*Hessian_Psir.matrix()).array()).eval();
32 }
33};
34
36 double rho;
37 double R;
38
39 // Calculated parameters
40 double Psiig; // --]
41 double d_Psiig_dT; // ] All obtained in one call, needed for all h,s,u
42 double d2_Psiig_dT2; // --]
43 double d2_Psir_dT2; // Needed for entropy, not needed for any standard specifications
44 Eigen::ArrayXd gradient_Psiig; // This is only not used by s, needed for h and u
45 Eigen::ArrayXd d_gradient_Psiig_dT; // Needed for h, s, u
46
47 double s(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
48 return -1/rho*(d_Psiig_dT + resid.d_Psir_dT);
49 }
50 double dsdT(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
51 return -1/rho*(d2_Psiig_dT2 + d2_Psir_dT2);
52 }
53 Eigen::ArrayXd dsdrhovec(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
54 return -1/rho*(d_gradient_Psiig_dT + resid.d_gradient_Psir_dT) + 1/rho/rho*(d_Psiig_dT + resid.d_Psir_dT);
55 }
56
57 double a(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
58 return (Psiig + resid.Psir)/rho;
59 }
60 double dadT(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
61 return 1/rho*(d_Psiig_dT + resid.d_Psir_dT);
62 }
63 Eigen::ArrayXd dadrhovec(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
64 return 1/rho*(gradient_Psiig + resid.gradient_Psir) - 1/rho/rho*(Psiig + resid.Psir);
65 }
66
67 double u(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
68 return a(T, rhovec, resid) + T*s(T, rhovec, resid);
69 }
70 double dudT(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
71 return dadT(T, rhovec, resid) + s(T, rhovec, resid) + T*dsdT(T, rhovec, resid);
72 }
73 Eigen::ArrayXd dudrhovec(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
74 return dadrhovec(T, rhovec, resid) + T*dsdrhovec(T, rhovec, resid);
75 }
76
77 double h(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
78 return u(T, rhovec, resid) + resid.p(T, rhovec)/rho;
79 }
80 double dhdT(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
81 return dudT(T, rhovec, resid) + resid.dpdT(T, rhovec)/rho;
82 }
83 Eigen::ArrayXd dhdrhovec(const double T, const Eigen::ArrayXd& rhovec, const RequiredPhaseDerivatives& resid) const{
84 return dudrhovec(T, rhovec, resid) + resid.dpdrhovec(T, rhovec)/rho - resid.p(T, rhovec)/rho/rho;
85 }
86};
87
90 double* ptr_p_phase0 = nullptr;
91 double* ptr_dpdT_phase0 = nullptr;
92 Eigen::ArrayXd* ptr_dpdrho_phase0 = nullptr;
93 std::vector<RequiredPhaseDerivatives>* derivatives = nullptr;
94 std::vector<CaloricPhaseDerivatives>* caloricderivatives = nullptr;
95};
96
98 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const = 0;
99 virtual ~AbstractSpecification() = default;
100};
101/***
102 \brief Specification equation for temperature
103 */
105private:
106 const double m_Tspec;
107public:
108 TSpecification(double T) : m_Tspec(T) {};
109
110 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
111 double r = x[0] - m_Tspec;
112 Eigen::ArrayXd J(x.size()); J.setZero();
113 J(0) = 1;
114 return std::make_tuple(r, J);
115 };
116};
117
122private:
123 const double m_betaspec;
124 const std::size_t m_iphase;
125public:
126 BetaSpecification(double beta, std::size_t iphase) : m_betaspec(beta), m_iphase(iphase) {};
127
128 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
129 double r = x[x.size()-sidecar.Nphases+m_iphase] - m_betaspec;
130 Eigen::ArrayXd Jrow(x.size()); Jrow.setZero();
131 Jrow(x.size()-sidecar.Nphases+m_iphase) = 1;
132 return std::make_tuple(r, Jrow);
133 };
134};
135
142private:
143 const double m_p;
144public:
145 PSpecification(double p_Pa) : m_p(p_Pa) {};
146
147 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
148 double r = *sidecar.ptr_p_phase0 - m_p;
149 Eigen::ArrayXd Jrow(x.size()); Jrow.setZero();
150
151 Jrow(0) = *sidecar.ptr_dpdT_phase0;
152 Jrow.segment(1, sidecar.Ncomponents) = *sidecar.ptr_dpdrho_phase0;
153 return std::make_tuple(r, Jrow);
154 };
155};
156
161private:
162 const double m_vspec_m3mol;
163public:
164 MolarVolumeSpecification(double v_m3mol) : m_vspec_m3mol(v_m3mol) {};
165
166 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
167 double T = x[0];
168 std::vector<Eigen::Map<const Eigen::ArrayXd>> rhovecs;
169 std::vector<double> rho_phase;
170 for (auto iphase_ = 0; iphase_ < sidecar.Nphases; ++iphase_){
171 rhovecs.push_back(Eigen::Map<const Eigen::ArrayXd>(&x[1 + iphase_*sidecar.Ncomponents], sidecar.Ncomponents));
172 rho_phase.push_back(rhovecs.back().sum());
173 }
174 const Eigen::Map<const Eigen::ArrayXd> betas(&x[x.size()-sidecar.Nphases], sidecar.Nphases);
175
176 Eigen::ArrayXd Jrow(x.size()); Jrow.setZero();
177 double v = 0.0;
178 for (auto iphase = 0; iphase < sidecar.Nphases; ++iphase){
179 v += betas[iphase]/rho_phase[iphase];
180 Jrow(x.size()-sidecar.Nphases+iphase) = 1/rho_phase[iphase];
181 Jrow.segment(1+iphase*sidecar.Ncomponents, sidecar.Ncomponents) = -betas[iphase]/rho_phase[iphase]/rho_phase[iphase];
182 }
183 double r = v - m_vspec_m3mol;
184 return std::make_tuple(r, Jrow);
185 };
186};
187
192private:
193 const double m_s_JmolK;
194public:
195 MolarEntropySpecification(double s_JmolK) : m_s_JmolK(s_JmolK) {};
196
197 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
198 double T = x[0];
199 std::vector<Eigen::Map<const Eigen::ArrayXd>> rhovecs;
200 std::vector<double> rho_phase;
201 for (auto iphase_ = 0; iphase_ < sidecar.Nphases; ++iphase_){
202 rhovecs.push_back(Eigen::Map<const Eigen::ArrayXd>(&x[1 + iphase_*sidecar.Ncomponents], sidecar.Ncomponents));
203 rho_phase.push_back(rhovecs.back().sum());
204 }
205 const Eigen::Map<const Eigen::ArrayXd> betas(&x[x.size()-sidecar.Nphases], sidecar.Nphases);
206 if (sidecar.caloricderivatives == nullptr){
207 throw teqp::InvalidArgument("Must have connected the ideal gas pointer");
208 }
209
210 Eigen::ArrayXd Jrow(x.size()); Jrow.setZero();
211 double s = 0.0;
212 for (auto iphase = 0; iphase < sidecar.Nphases; ++iphase){
213 const auto& cal = (*sidecar.caloricderivatives)[iphase];
214 const RequiredPhaseDerivatives& der = (*sidecar.derivatives)[iphase];
215 s += betas[iphase]*cal.s(T, rhovecs[iphase], der);
216 Jrow(0) += betas[iphase]*cal.dsdT(T, rhovecs[iphase], der); // Temperature derivative, all phases
217 Jrow(x.size()-sidecar.Nphases+iphase) = cal.s(T, rhovecs[iphase], der);
218 Jrow.segment(1+iphase*sidecar.Ncomponents, sidecar.Ncomponents) = betas[iphase]*cal.dsdrhovec(T, rhovecs[iphase], der);
219 }
220 double r = s - m_s_JmolK;
221 return std::make_tuple(r, Jrow);
222 };
223};
224
229private:
230 const double m_u_Jmol;
231public:
232 MolarInternalEnergySpecification(double u_Jmol) : m_u_Jmol(u_Jmol) {};
233
234 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
235 double T = x[0];
236 std::vector<Eigen::Map<const Eigen::ArrayXd>> rhovecs;
237 std::vector<double> rho_phase;
238 for (auto iphase_ = 0; iphase_ < sidecar.Nphases; ++iphase_){
239 rhovecs.push_back(Eigen::Map<const Eigen::ArrayXd>(&x[1 + iphase_*sidecar.Ncomponents], sidecar.Ncomponents));
240 rho_phase.push_back(rhovecs.back().sum());
241 }
242 const Eigen::Map<const Eigen::ArrayXd> betas(&x[x.size()-sidecar.Nphases], sidecar.Nphases);
243 if (sidecar.caloricderivatives == nullptr){
244 throw teqp::InvalidArgument("Must have connected the ideal gas pointer");
245 }
246
247 Eigen::ArrayXd Jrow(x.size()); Jrow.setZero();
248 double u = 0.0;
249 for (auto iphase = 0; iphase < sidecar.Nphases; ++iphase){
250 const auto& cal = (*sidecar.caloricderivatives)[iphase];
251 const RequiredPhaseDerivatives& der = (*sidecar.derivatives)[iphase];
252 auto u_phase = cal.u(T, rhovecs[iphase], der);
253 u += betas[iphase]*u_phase;
254 Jrow(0) += betas[iphase]*cal.dudT(T, rhovecs[iphase], der); // Temperature derivative, all phases
255 Jrow(x.size()-sidecar.Nphases+iphase) = u_phase;
256 Jrow.segment(1+iphase*sidecar.Ncomponents, sidecar.Ncomponents) = betas[iphase]*cal.dudrhovec(T, rhovecs[iphase], der);
257 }
258 double r = u - m_u_Jmol;
259 return std::make_tuple(r, Jrow);
260 };
261};
262
267private:
268 const double m_h_Jmol;
269public:
270 MolarEnthalpySpecification(double h_Jmol) : m_h_Jmol(h_Jmol) {};
271
272 virtual std::tuple<double, Eigen::ArrayXd> r_Jacobian(const Eigen::ArrayXd& x, const SpecificationSidecar& sidecar) const override {
273 double T = x[0];
274 std::vector<Eigen::Map<const Eigen::ArrayXd>> rhovecs;
275 std::vector<double> rho_phase;
276 for (auto iphase_ = 0; iphase_ < sidecar.Nphases; ++iphase_){
277 rhovecs.push_back(Eigen::Map<const Eigen::ArrayXd>(&x[1 + iphase_*sidecar.Ncomponents], sidecar.Ncomponents));
278 rho_phase.push_back(rhovecs.back().sum());
279 }
280 const Eigen::Map<const Eigen::ArrayXd> betas(&x[x.size()-sidecar.Nphases], sidecar.Nphases);
281 if (sidecar.caloricderivatives == nullptr){
282 throw teqp::InvalidArgument("Must have connected the ideal gas pointer");
283 }
284
285 Eigen::ArrayXd Jrow(x.size()); Jrow.setZero();
286 double h = 0.0;
287 for (auto iphase = 0; iphase < sidecar.Nphases; ++iphase){
288 const auto& cal = (*sidecar.caloricderivatives)[iphase];
289 const RequiredPhaseDerivatives& der = (*sidecar.derivatives)[iphase];
290 auto h_phase = cal.h(T, rhovecs[iphase], der);
291 h += betas[iphase]*h_phase;
292 Jrow(0) += betas[iphase]*cal.dhdT(T, rhovecs[iphase], der); // Temperature derivative, all phases
293 Jrow(x.size()-sidecar.Nphases+iphase) = h_phase;
294 Jrow.segment(1+iphase*sidecar.Ncomponents, sidecar.Ncomponents) = betas[iphase]*cal.dhdrhovec(T, rhovecs[iphase], der);
295 }
296 double r = h - m_h_Jmol;
297 return std::make_tuple(r, Jrow);
298 };
299};
300
305private:
306 auto get_Ncomponents(const std::vector<Eigen::ArrayXd>& rhovecs){
307 // Check all are the same size, and nonempty
308 std::set<std::size_t> sizes;
309 for (const auto& rhovec : rhovecs){
310 sizes.emplace(rhovec.size());
311 }
312 if (sizes.size() != 1){
313 throw;
314 }
315 for (auto size : sizes){
316 if (size == 0){
317 throw;
318 }
319 return size;
320 }
321 throw;
322 }
323public:
324
326 Eigen::VectorXd r;
327 Eigen::MatrixXd J;
328 };
329
331 public:
332 const double T;
333 const std::vector<Eigen::ArrayXd> rhovecs;
334 const Eigen::ArrayXd betas;
335 UnpackedVariables(const double T, const std::vector<Eigen::ArrayXd>& rhovecs, const Eigen::ArrayXd& betas) : T(T), rhovecs(rhovecs), betas(betas){};
336
337 auto pack(){
338 auto Nphases = betas.size();
339 auto Ncomponents = rhovecs[0].size();
340 Eigen::ArrayXd x(1+(Ncomponents+1)*Nphases);
341 x[0] = T;
342 for (auto iphase_ = 0; iphase_ < Nphases; ++iphase_){
343 Eigen::Map<Eigen::ArrayXd>(&x[1 + iphase_*Ncomponents], Ncomponents) = rhovecs[iphase_];
344 }
345 x.tail(betas.size()) = betas;
346 return x;
347 }
348 };
350 std::optional<std::shared_ptr<const AbstractModel>> idealgasptr;
351 const Eigen::ArrayXd zbulk;
352 const std::size_t Ncomponents,
355 const std::vector<std::shared_ptr<AbstractSpecification>> specifications;
357
371 const Eigen::ArrayXd& zbulk,
372 const UnpackedVariables& init,
373 const std::vector<std::shared_ptr<AbstractSpecification>>& specifications
374 )
375 : residptr(residmodel), zbulk(zbulk), Ncomponents(get_Ncomponents(init.rhovecs)), Nphases(init.betas.size()), Nindependent(1+(Ncomponents+1)*Nphases), specifications(specifications)
376 {
377 if (init.betas.size() != init.rhovecs.size()){
378 throw teqp::InvalidArgument("bad sizes for initial betas and rhovecs");
379 }
380 if (specifications.size() != 2){
381 throw teqp::InvalidArgument("specification vector should be of length 2");
382 }
383
384 // Resize the working buffers
385 res.r.resize(Nindependent);
387 }
388 auto attach_ideal_gas(const std::shared_ptr<const AbstractModel>& ptr){
389 idealgasptr = ptr;
390 }
391
396 auto call(const Eigen::ArrayXd&x){
397 // Two references to save some typing later on
398 auto& J = res.J; J.setZero();
399 auto& r = res.r; r.setZero();
400 auto Ncomp = Ncomponents;
401
402 // Unpack into the variables of interest (as maps where possible to avoid copies)
403 if (x.size() != Nindependent){
404 throw teqp::InvalidArgument("Wrong size; should be of size"+ std::to_string(Nindependent) + "; is of size " + std::to_string(x.size()));
405 }
406 double T = x[0];
407 std::vector<Eigen::Map<const Eigen::ArrayXd>> rhovecs;
408 for (auto iphase_ = 0; iphase_ < Nphases; ++iphase_){
409 rhovecs.push_back(Eigen::Map<const Eigen::ArrayXd>(&x[1 + iphase_*Ncomp], Ncomponents));
410 }
411 const Eigen::Map<const Eigen::ArrayXd> betas(&x[x.size()-Nphases], Nphases);
412 double R = residptr.get_R(zbulk); // TODO: think about what to do when the phases have different R values and dR/drho_i is nonzero
413
414 // Calculate the required derivatives for each phase
415 // based on its temperature and molar concentrations
416 auto calculate_required_derivatives = [this, R](auto& modelref, double T, const Eigen::ArrayXd& rhovec) -> RequiredPhaseDerivatives{
418 der.rho = rhovec.sum();
419 der.R = R;
420 // Three in one via tuple unpacking
421 std::tie(der.Psir, der.gradient_Psir, der.Hessian_Psir) = modelref.build_Psir_fgradHessian_autodiff(T, rhovec);
422 // And then the temperature derivatives
423 // Psir = ar*R*T*rho
424 // d(Psir)/dT = d(rho*alphar*R*T)/dT = rho*R*d(alphar*T)/dT = rho*R*(T*dalphar/dT + alphar) = rho*R*(T*dalphar/dT) + Psir/T
425 // and T*dalphar/dT = -Ar10 so
426 der.d_Psir_dT = der.rho*R*(-modelref.get_Ar10(T, der.rho, rhovec/der.rho)) + der.Psir/T;
427 der.d_gradient_Psir_dT = modelref.build_d2PsirdTdrhoi_autodiff(T, rhovec);
428 return der;
429 };
430 std::vector<RequiredPhaseDerivatives> derivatives;
431 for (auto iphase_ = 0; iphase_ < Nphases; ++iphase_){
432 derivatives.emplace_back(calculate_required_derivatives(this->residptr, T, rhovecs[iphase_]));
433 }
434
435 // First we have the equalities in (natural) logarithm of fugacity coefficient (always present)
436 std::size_t irow = 0;
437 std::size_t iphase = 0;
438
439 auto lnf_phase0 = log(rhovecs[iphase]*R*T) + 1/(R*T)*derivatives[iphase].gradient_Psir;
440 auto dlnfdT_phase0 = 1/T + 1/(R*T)*derivatives[iphase].d_gradient_Psir_dT - 1/(R*T*T)*derivatives[iphase].gradient_Psir;
441 Eigen::ArrayXXd dlnfdrho_phase0 = Eigen::MatrixXd::Identity(Ncomp, Ncomp);
442 dlnfdrho_phase0.matrix().diagonal().array() /= rhovecs[iphase];
443 dlnfdrho_phase0 += 1/(R*T)*derivatives[iphase].Hessian_Psir;
444
445 for (auto iphasei = 1; iphasei < Nphases; ++iphasei){
446 // Equality of all ln(f) for phase with index 0 and that of index iphase
447 auto lnf_phasei = log(rhovecs[iphasei]*R*T) + 1.0/(R*T)*derivatives[iphasei].gradient_Psir;
448 auto dlnfdT_phasei = 1/T + 1.0/(R*T)*derivatives[iphasei].d_gradient_Psir_dT - 1/(R*T*T)*derivatives[iphasei].gradient_Psir;
449 Eigen::ArrayXXd dlnfdrho_phasei = Eigen::MatrixXd::Identity(Ncomp, Ncomp);
450 dlnfdrho_phasei.matrix().diagonal().array() /= rhovecs[iphasei];
451 dlnfdrho_phasei += 1/(R*T)*derivatives[iphasei].Hessian_Psir;
452
453 // There are Ncomp equalities for this phase for each component
454 r.segment(irow, Ncomp) = lnf_phase0 - lnf_phasei;
455 // And Ncomp entries in the first column (of index 0) in the Jacobian for the
456 // temperature derivative
457 J.block(irow, 0, Ncomp, 1) = dlnfdT_phase0 - dlnfdT_phasei;
458 // And in the rows in the Jacobian, there is a block for each phase,
459 // including the first one with index 0, and the correct block needs to be selected
460 // which for first phase is of positive sign, and elsewhere, of negative sign
461 for (auto iphasej = 0; iphasej < Ncomp; ++iphasej){
462 if (iphasej == 0){
463 J.block(irow, 1+iphasej*Ncomp, Ncomp, Ncomp) = dlnfdrho_phase0;
464 }
465 else{
466 J.block(irow, 1+iphasej*Ncomp, Ncomp, Ncomp) = -dlnfdrho_phasei;
467 }
468 }
469 irow += Ncomp;
470 }
471
472 // Then we have the equality of pressure between all the phases (always present)
473 iphase = 0;
474 double p_phase0 = derivatives[iphase].p(T, rhovecs[iphase]);
475 double dpdT_phase0 = derivatives[iphase].dpdT(T, rhovecs[iphase]);
476 Eigen::ArrayXd dpdrho_phase0 = derivatives[iphase].dpdrhovec(T, rhovecs[iphase]);
477 for (auto iphasei = 1; iphasei < Nphases; ++iphasei){
478 double p_phasei = derivatives[iphasei].p(T, rhovecs[iphasei]);
479 double dpdT_phasei = derivatives[iphasei].dpdT(T, rhovecs[iphasei]);
480 Eigen::ArrayXd dpdrho_phasei = derivatives[iphasei].dpdrhovec(T, rhovecs[iphasei]);
481 r[irow] = p_phase0 - p_phasei;
482 J(irow, 0) = dpdT_phase0 - dpdT_phasei;
483 for (auto iphasej = 0; iphasej < Nphases; ++iphasej){
484 if (iphasej == 0){
485 J.block(irow, 1+iphasej*Ncomp, 1, Ncomp) = dpdrho_phase0.transpose();
486 }
487 else{
488 J.block(irow, 1+iphasej*Ncomp, 1, Ncomp) = -dpdrho_phasei.transpose();
489 }
490 }
491 // Note: no Jacobian contribution for derivatives w.r.t. betas
492 irow += 1;
493 }
494
495 // Then we have the Ncomp-1 material balances (always present)
496 for (auto icomp = 0; icomp < Ncomp-1; ++icomp){
497 double summer = 0;
498 for (iphase = 0; iphase < Nphases; ++iphase){
499 double rho_phase = rhovecs[iphase].sum();
500 double xj_phk = rhovecs[iphase][icomp]/rho_phase; // mole fraction of component j in phase k
501 summer += betas[iphase]*xj_phk;
502 J(irow, J.cols()-Nphases+iphase) = xj_phk;
503 for (auto icompj = 0; icompj < Ncomp; ++icompj){
504 auto Kronecker = [](int i, int j){ return i==j; };
505 J(irow, 1+iphase*Ncomp + icompj) = betas[iphase]*(Kronecker(icomp,icompj) - rhovecs[iphase][icomp]/rho_phase)/rho_phase;
506 }
507 }
508 r[irow] = summer - zbulk[icomp];
509 irow++;
510 }
511
512 // Summation of molar phase fractions beta (always present)
513 r[irow] = betas.sum()-1;
514 J.block(irow, Nindependent-Nphases, 1, Nphases).fill(1.0); // All other derivatives zero
515 irow += 1;
516
517 // And two specification equations
518 SpecificationSidecar sidecar;
519 sidecar.Nphases = Nphases;
520 sidecar.Ncomponents = Ncomponents;
521 sidecar.Nindependent = Nindependent;
522 sidecar.ptr_p_phase0 = &p_phase0;
523 sidecar.ptr_dpdT_phase0 = &dpdT_phase0;
524 sidecar.ptr_dpdrho_phase0 = &dpdrho_phase0;
525
526 // If any of the specification equations require caloric properties, calculate them
527 // for all phases
528 auto calculate_caloric_derivatives = [this, R](auto& modelref, auto& modelresid, double T, const Eigen::ArrayXd& rhovec) -> CaloricPhaseDerivatives{
530 der.rho = rhovec.sum();
531 der.R = R;
532 auto molefracs = (rhovec/der.rho).eval();
533 der.Psiig = modelref.get_Ar00(T, der.rho, molefracs)*R*T*der.rho;
534 // And then the temperature derivatives
535 // Psir = ar*R*T*rho
536 // d(Psir)/dT = d(rho*alphar*R*T)/dT = rho*R*d(alphar*T)/dT = rho*R*(T*dalphar/dT + alphar)
537 // and T*dalphar/dT = -Ar10 so
538 der.d_Psiig_dT = der.rho*R*(-modelref.get_Ar10(T, der.rho, molefracs)) + der.Psiig/T;
539 // d^2(Psir)/dT^2 = rho*R*(T*d2alphar/dT2 + dalphar/dT + dalphar/dT) = rho*R*(T*d2alphar/dT2 + 2*dalphar/dT) = rho*R/T*(T^2*d2alphar/dT2 + 2*T*dalphar/dT)
540 // And from Eqs. 3.46 and 3.47 from Span we get A20 for the term in (...)
541 der.d2_Psiig_dT2 = der.rho*R/T*modelref.get_Ar20(T, der.rho, molefracs);
542 der.d2_Psir_dT2 = der.rho*R/T*modelresid.get_Ar20(T, der.rho, molefracs);
543 der.gradient_Psiig = modelref.build_Psir_gradient_autodiff(T, rhovec);
544 der.d_gradient_Psiig_dT = modelref.build_d2PsirdTdrhoi_autodiff(T, rhovec);
545 return der;
546 };
547 std::vector<CaloricPhaseDerivatives> caloricderivatives;
548 if (this->idealgasptr){
549 for (auto iphase_ = 0; iphase_ < Nphases; ++iphase_){
550 caloricderivatives.emplace_back(calculate_caloric_derivatives(*this->idealgasptr->get(), residptr, T, rhovecs[iphase_]));
551 }
552 sidecar.derivatives = &derivatives;
553 sidecar.caloricderivatives = &caloricderivatives;
554 }
555
556 for (auto& spec: specifications){
557 auto [r_, J_] = spec->r_Jacobian(x, sidecar);
558 r[irow] = r_;
559 J.row(irow) = J_;
560 irow += 1;
561 }
562
563 // Finally, zero out the rows and columns in the Jacobian where mole fractions are zero, which would otherwise cause issues
564 }
565 auto num_Jacobian(const Eigen::ArrayXd& x, const Eigen::ArrayXd& dx){
566 Eigen::MatrixXd J(Nindependent, Nindependent);
567 call(x);
568 Eigen::ArrayXd r0 = res.r;
569 for (auto i = 0; i < Nindependent; ++i){
570 double dx_ = (dx[i] != 0) ? dx[i] : 1e-6;
571 Eigen::ArrayXd xplus = x; xplus[i] += dx_;
572 Eigen::ArrayXd xminus = x; xminus[i] -= dx_;
573 call(xplus);
574 auto rplus = res.r;
575 call(xminus);
576 auto rminus = res.r;
577 J.col(i) = (rplus - rminus)/(2*dx_);
578 }
579 return J;
580 }
581};
582
583}
std::optional< std::shared_ptr< const AbstractModel > > idealgasptr
The pointer for the ideal-gas portion of .
GeneralizedPhaseEquilibrium(const AbstractModel &residmodel, const Eigen::ArrayXd &zbulk, const UnpackedVariables &init, const std::vector< std::shared_ptr< AbstractSpecification > > &specifications)
A helper class for doing multi-phase phase equilibrium calculations with additional specification equ...
const std::vector< std::shared_ptr< AbstractSpecification > > specifications
The specification equations.
CallResult res
The internal buffer of residual vector and Jacobian (to minimize copies)
const std::size_t Ncomponents
The number of components in each phase.
auto num_Jacobian(const Eigen::ArrayXd &x, const Eigen::ArrayXd &dx)
const AbstractModel & residptr
The pointer for the residual portion of .
const std::size_t Nindependent
The number of independent variables to be solved for.
auto call(const Eigen::ArrayXd &x)
Call the routines to build the vector of residuals and Jacobian and cache it internally.
const Eigen::ArrayXd zbulk
The bulk composition of the mixture.
auto attach_ideal_gas(const std::shared_ptr< const AbstractModel > &ptr)
virtual double get_R(const EArrayd &) const =0
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const =0
Specification of molar phase fraction in given phase.
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override
BetaSpecification(double beta, std::size_t iphase)
Eigen::ArrayXd dudrhovec(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double s(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double dhdT(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double a(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double u(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double dsdT(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double dudT(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
Eigen::ArrayXd dsdrhovec(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
Eigen::ArrayXd dhdrhovec(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double h(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
double dadT(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
Eigen::ArrayXd dadrhovec(const double T, const Eigen::ArrayXd &rhovec, const RequiredPhaseDerivatives &resid) const
UnpackedVariables(const double T, const std::vector< Eigen::ArrayXd > &rhovecs, const Eigen::ArrayXd &betas)
Specification equation for molar enthalpy.
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override
Specification equation for molar entropy.
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override
Specification equation for molar internal energy.
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override
Specification equation for molar volume.
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override
Specification equation for pressure.
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override
double p(const double T, const Eigen::ArrayXd &rhovec) const
double dpdT(const double T, const Eigen::ArrayXd &rhovec) const
Eigen::ArrayXd dpdrhovec(const double T, const Eigen::ArrayXd &rhovec) const
std::vector< CaloricPhaseDerivatives > * caloricderivatives
std::vector< RequiredPhaseDerivatives > * derivatives
virtual std::tuple< double, Eigen::ArrayXd > r_Jacobian(const Eigen::ArrayXd &x, const SpecificationSidecar &sidecar) const override