teqp 0.22.0
Loading...
Searching...
No Matches
polar_terms.hpp
Go to the documentation of this file.
1#pragma once
2
10#include "teqp/types.hpp"
11#include "teqp/constants.hpp"
12#include "teqp/exceptions.hpp"
14#include <optional>
15#include <Eigen/Dense>
19#include <variant>
20
21namespace teqp{
22
23namespace SAFTpolar{
24
25using namespace teqp::saft::polar_terms;
26
27template<typename KType, typename RhoType, typename Txy>
28auto get_Kijk(const KType& Kint, const RhoType& rhostar, const Txy &Tstarij, const Txy &Tstarik, const Txy &Tstarjk){
29 return forceeval(pow(forceeval(Kint.get_K(Tstarij, rhostar)*Kint.get_K(Tstarik, rhostar)*Kint.get_K(Tstarjk, rhostar)), 1.0/3.0));
30};
31
32// Special treatment needed here because the 334,445 term is negative, so negative*negative*negative is negative, and negative^{1/3} is undefined
33// First flip the sign on the triple product, do the evaluation, the flip it back. Not documented in Gubbins&Twu, but this seem reasonable,
34// in the spirit of the others.
35template<typename KType, typename RhoType, typename Txy>
36auto get_Kijk_334445(const KType& Kint, const RhoType& rhostar, const Txy &Tstarij, const Txy &Tstarik, const Txy &Tstarjk){
37 return forceeval(-pow(-forceeval(Kint.get_K(Tstarij, rhostar)*Kint.get_K(Tstarik, rhostar)*Kint.get_K(Tstarjk, rhostar)), 1.0/3.0));
38};
39
46template<class JIntegral, class KIntegral>
48public:
49 static constexpr multipolar_argument_spec arg_spec = multipolar_argument_spec::TK_rhoNm3_rhostar_molefractions;
50private:
51 const Eigen::ArrayXd sigma_m, epsilon_over_k, mubar2, Qbar2;
52 const bool has_a_polar;
53 const Eigen::ArrayXd sigma_m3, sigma_m5;
54
55 const JIntegral J6{6};
56 const JIntegral J8{8};
57 const JIntegral J10{10};
58 const JIntegral J11{11};
59 const JIntegral J13{13};
60 const JIntegral J15{15};
61 const KIntegral K222_333{222, 333};
62 const KIntegral K233_344{233, 344};
63 const KIntegral K334_445{334, 445};
64 const KIntegral K444_555{444, 555};
65 const double epsilon_0 = 8.8541878128e-12; // https://en.wikipedia.org/wiki/Vacuum_permittivity, in F/m, or C^2⋅N^−1⋅m^−2
66 const double PI_ = static_cast<double>(EIGEN_PI);
67 Eigen::MatrixXd SIGMAIJ, EPSKIJ;
68 multipolar_rhostar_approach approach = multipolar_rhostar_approach::use_packing_fraction;
69
70public:
71 MultipolarContributionGubbinsTwu(const Eigen::ArrayX<double> &sigma_m, const Eigen::ArrayX<double> &epsilon_over_k, const Eigen::ArrayX<double> &mubar2, const Eigen::ArrayX<double> &Qbar2, multipolar_rhostar_approach approach) : sigma_m(sigma_m), epsilon_over_k(epsilon_over_k), mubar2(mubar2), Qbar2(Qbar2), has_a_polar(mubar2.cwiseAbs().sum() > 0 || Qbar2.cwiseAbs().sum() > 0), sigma_m3(sigma_m.pow(3)), sigma_m5(sigma_m.pow(5)), approach(approach) {
72 // Check lengths match
73 if (sigma_m.size() != mubar2.size()){
74 throw teqp::InvalidArgument("bad size of mubar2");
75 }
76 if (sigma_m.size() != Qbar2.size()){
77 throw teqp::InvalidArgument("bad size of Qbar2");
78 }
79 // Pre-calculate the mixing terms;
80 const std::size_t N = sigma_m.size();
81 SIGMAIJ.resize(N, N); EPSKIJ.resize(N, N);
82 for (std::size_t i = 0; i < N; ++i){
83 for (std::size_t j = 0; j < N; ++j){
84 // Lorentz-Berthelot mixing rules
85 double epskij = sqrt(epsilon_over_k[i]*epsilon_over_k[j]);
86 double sigmaij = (sigma_m[i]+sigma_m[j])/2;
87 SIGMAIJ(i, j) = sigmaij;
88 EPSKIJ(i, j) = epskij;
89 }
90 }
91 }
93
94 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType>
95 auto get_alpha2(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions) const{
96 const auto& x = mole_fractions; // concision
97
98 const std::size_t N = mole_fractions.size();
99 using XTtype = std::common_type_t<TTYPE, decltype(mole_fractions[0])>;
100 std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0])> alpha2_112 = 0.0, alpha2_123 = 0.0, alpha2_224 = 0.0;
101
102 const RhoType factor_112 = -2.0*PI_*rhoN/3.0;
103 const RhoType factor_123 = -PI_*rhoN;
104 const RhoType factor_224 = -14.0*PI_*rhoN/5.0;
105
106 for (std::size_t i = 0; i < N; ++i){
107 for (std::size_t j = 0; j < N; ++j){
108
109 TTYPE Tstari = forceeval(T/EPSKIJ(i, i)), Tstarj = forceeval(T/EPSKIJ(j, j));
110 XTtype leading = forceeval(x[i]*x[j]/(Tstari*Tstarj)); // common for all alpha_2 terms
111 TTYPE Tstarij = forceeval(T/EPSKIJ(i, j));
112 double sigmaij = SIGMAIJ(i,j);
113 {
114 double dbl = sigma_m3[i]*sigma_m3[j]/powi(sigmaij,3)*mubar2[i]*mubar2[j];
115 alpha2_112 += leading*dbl*J6.get_J(Tstarij, rhostar);
116 }
117 {
118 double dbl = sigma_m3[i]*sigma_m5[j]/powi(sigmaij,5)*mubar2[i]*Qbar2[j];
119 alpha2_123 += leading*dbl*J8.get_J(Tstarij, rhostar);
120 }
121 {
122 double dbl = sigma_m5[i]*sigma_m5[j]/powi(sigmaij,7)*Qbar2[i]*Qbar2[j];
123 alpha2_224 += leading*dbl*J10.get_J(Tstarij, rhostar);
124 }
125 }
126 }
127 return forceeval(factor_112*alpha2_112 + 2.0*factor_123*alpha2_123 + factor_224*alpha2_224);
128 }
129
130
131 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType>
132 auto get_alpha3(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions) const{
133 const VecType& x = mole_fractions; // concision
134 const std::size_t N = mole_fractions.size();
135 using type = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0])>;
136 using XTtype = std::common_type_t<TTYPE, decltype(mole_fractions[0])>;
137 type summerA_112_112_224 = 0.0, summerA_112_123_213 = 0.0, summerA_123_123_224 = 0.0, summerA_224_224_224 = 0.0;
138 type summerB_112_112_112 = 0.0, summerB_112_123_123 = 0.0, summerB_123_123_224 = 0.0, summerB_224_224_224 = 0.0;
139
140 for (std::size_t i = 0; i < N; ++i){
141 for (std::size_t j = 0; j < N; ++j){
142
143 TTYPE Tstari = forceeval(T/EPSKIJ(i,i)), Tstarj = forceeval(T/EPSKIJ(j,j));
144 TTYPE Tstarij = forceeval(T/EPSKIJ(i,j));
145
146 XTtype leading = forceeval(x[i]*x[j]/pow(forceeval(Tstari*Tstarj), 3.0/2.0)); // common for all alpha_3A terms
147 double sigmaij = SIGMAIJ(i,j);
148 double POW4sigmaij = powi(sigmaij, 4);
149 double POW8sigmaij = POW4sigmaij*POW4sigmaij;
150 double POW10sigmaij = powi(sigmaij, 10);
151 double POW12sigmaij = POW4sigmaij*POW8sigmaij;
152
153 {
154 double dbl = pow(sigma_m[i]*sigma_m[j], 11.0/2.0)/POW8sigmaij*mubar2[i]*mubar2[j]*sqrt(Qbar2[i]*Qbar2[j]);
155 summerA_112_112_224 += leading*dbl*J11.get_J(Tstarij, rhostar);
156 }
157 {
158 double dbl = pow(sigma_m[i]*sigma_m[j], 11.0/2.0)/POW8sigmaij*mubar2[i]*mubar2[j]*sqrt(Qbar2[i]*Qbar2[j]);
159 summerA_112_123_213 += leading*dbl*J11.get_J(Tstarij, rhostar);
160 }
161 {
162 double dbl = pow(sigma_m[i], 11.0/2.0)*pow(sigma_m[j], 15.0/2.0)/POW10sigmaij*mubar2[i]*sqrt(Qbar2[i])*pow(Qbar2[j], 3.0/2.0);
163 summerA_123_123_224 += leading*dbl*J13.get_J(Tstarij, rhostar);
164 }
165 {
166 double dbl = pow(sigma_m[i]*sigma_m[j], 15.0/2.0)/POW12sigmaij*pow(Qbar2[i], 3.0/2.0)*pow(Qbar2[j], 3.0/2.0);
167 summerA_224_224_224 += leading*dbl*J15.get_J(Tstarij, rhostar);
168 }
169
170 for (std::size_t k = 0; k < N; ++k){
171 TTYPE Tstark = forceeval(T/EPSKIJ(k,k));
172 TTYPE Tstarik = forceeval(T/EPSKIJ(i,k));
173 TTYPE Tstarjk = forceeval(T/EPSKIJ(j,k));
174 double sigmaik = SIGMAIJ(i,k), sigmajk = SIGMAIJ(j,k);
175
176 // Lorentz-Berthelot mixing rules for sigma
177 XTtype leadingijk = forceeval(x[i]*x[j]*x[k]/(Tstari*Tstarj*Tstark));
178
179 if (std::abs(mubar2[i]*mubar2[j]*mubar2[k]) > 0){
180 auto K222333 = get_Kijk(K222_333, rhostar, Tstarij, Tstarik, Tstarjk);
181 double dbl = sigma_m3[i]*sigma_m3[j]*sigma_m3[k]/(sigmaij*sigmaik*sigmajk)*mubar2[i]*mubar2[j]*mubar2[k];
182 summerB_112_112_112 += forceeval(leadingijk*dbl*K222333);
183 }
184 if (std::abs(mubar2[i]*mubar2[j]*Qbar2[k]) > 0){
185 auto K233344 = get_Kijk(K233_344, rhostar, Tstarij, Tstarik, Tstarjk);
186 double dbl = sigma_m3[i]*sigma_m3[j]*sigma_m5[k]/(sigmaij*POW2(sigmaik*sigmajk))*mubar2[i]*mubar2[j]*Qbar2[k];
187 summerB_112_123_123 += leadingijk*dbl*K233344;
188 }
189 if (std::abs(mubar2[i]*Qbar2[j]*Qbar2[k]) > 0){
190 auto K334445 = get_Kijk_334445(K334_445, rhostar, Tstarij, Tstarik, Tstarjk);
191 double dbl = sigma_m3[i]*sigma_m5[j]*sigma_m5[k]/(POW2(sigmaij*sigmaik)*POW3(sigmajk))*mubar2[i]*Qbar2[j]*Qbar2[k];
192 summerB_123_123_224 += leadingijk*dbl*K334445;
193 }
194 if (std::abs(Qbar2[i]*Qbar2[j]*Qbar2[k]) > 0){
195 auto K444555 = get_Kijk(K444_555, rhostar, Tstarij, Tstarik, Tstarjk);
196 double dbl = POW5(sigma_m[i]*sigma_m[j]*sigma_m[k])/(POW3(sigmaij*sigmaik*sigmajk))*Qbar2[i]*Qbar2[j]*Qbar2[k];
197 summerB_224_224_224 += leadingijk*dbl*K444555;
198 }
199 }
200 }
201 }
202
203 type alpha3A_112_112_224 = 8.0*PI_*rhoN/25.0*summerA_112_112_224;
204 type alpha3A_112_123_213 = 8.0*PI_*rhoN/75.0*summerA_112_123_213;
205 type alpha3A_123_123_224 = 8.0*PI_*rhoN/35.0*summerA_123_123_224;
206 type alpha3A_224_224_224 = 144.0*PI_*rhoN/245.0*summerA_224_224_224;
207
208 type alpha3A = 3.0*alpha3A_112_112_224 + 6.0*alpha3A_112_123_213 + 6.0*alpha3A_123_123_224 + alpha3A_224_224_224;
209
210 RhoType rhoN2 = rhoN*rhoN;
211
212 type alpha3B_112_112_112 = 32.0*POW3(PI_)*rhoN2/135.0*sqrt(14*PI_/5.0)*summerB_112_112_112;
213 type alpha3B_112_123_123 = 64.0*POW3(PI_)*rhoN2/315.0*sqrt(3.0*PI_)*summerB_112_123_123;
214 type alpha3B_123_123_224 = -32.0*POW3(PI_)*rhoN2/45.0*sqrt(22.0*PI_/63.0)*summerB_123_123_224;
215 type alpha3B_224_224_224 = 32.0*POW3(PI_)*rhoN2/2025.0*sqrt(2002.0*PI_)*summerB_224_224_224;
216
217 type alpha3B = alpha3B_112_112_112 + 3.0*alpha3B_112_123_123 + 3.0*alpha3B_123_123_224 + alpha3B_224_224_224;
218
219 return forceeval(alpha3A + alpha3B);
220 }
221
222 template<typename RhoType, typename PFType, typename MoleFractions>
223 auto get_rhostar(const RhoType rhoN, const PFType& packing_fraction, const MoleFractions& mole_fractions) const{
224 using type = std::common_type_t<RhoType, PFType, decltype(mole_fractions[0])>;
225 type rhostar;
226 if (approach == multipolar_rhostar_approach::calculate_Gubbins_rhostar){
227 // Calculate the effective reduced diameter (cubed) to be used for evaluation
228 // Eq. 24 from Gubbins
229 type sigma_x3 = 0.0;
230 error_if_expr(sigma_m3);
231 auto N = mole_fractions.size();
232 for (auto i = 0; i < N; ++i){
233 for (auto j = 0; j < N; ++j){
234 auto sigmaij = (sigma_m[i] + sigma_m[j])/2;
235 sigma_x3 += mole_fractions[i]*mole_fractions[j]*POW3(sigmaij);
236 }
237 }
238 rhostar = forceeval(rhoN*sigma_x3);
239 }
240 else if (approach == multipolar_rhostar_approach::use_packing_fraction){
241 // The packing fraction is defined by eta = pi/6*rho^*, so use the (temperature-dependent) eta to obtain rho^*
242 rhostar = forceeval(packing_fraction/(static_cast<double>(EIGEN_PI)/6.0));
243 }
244 else{
245 throw teqp::InvalidArgument("The method used to determine rho^* is invalid");
246 }
247 return rhostar;
248 }
249
250 /***
251 * \brief Get the contribution to \f$ \alpha = A/(NkT) \f$
252 */
253 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType>
254 auto eval(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions) const {
255 error_if_expr(T); error_if_expr(rhoN); error_if_expr(mole_fractions[0]);
256 using type = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0])>;
257
258 type alpha2 = 0.0, alpha3 = 0.0, alpha = 0.0;
259 if (has_a_polar){
260 alpha2 = get_alpha2(T, rhoN, rhostar, mole_fractions);
261 alpha3 = get_alpha3(T, rhoN, rhostar, mole_fractions);
262 alpha = forceeval(alpha2/(1.0-alpha3/alpha2));
263 }
264 return MultipolarContributionGubbinsTwuTermsGT<type>{alpha2, alpha3, alpha};
265 }
266};
267
271
278template<class JIntegral, class KIntegral>
280public:
281 static constexpr multipolar_argument_spec arg_spec = multipolar_argument_spec::TK_rhoNm3_rhostar_molefractions;
282private:
283 const Eigen::ArrayXd sigma_m, epsilon_over_k;
284 Eigen::MatrixXd SIGMAIJ, EPSKIJ;
285 const Eigen::ArrayXd mu, Q, mu2, Q2, Q3;
286 const bool has_a_polar;
287 const Eigen::ArrayXd sigma_m3, sigma_m5;
288
289 const JIntegral J6{6};
290 const JIntegral J8{8};
291 const JIntegral J10{10};
292 const JIntegral J11{11};
293 const JIntegral J13{13};
294 const JIntegral J15{15};
295 const KIntegral K222_333{222, 333};
296 const KIntegral K233_344{233, 344};
297 const KIntegral K334_445{334, 445};
298 const KIntegral K444_555{444, 555};
299
300 const double PI_ = static_cast<double>(EIGEN_PI);
301 const double PI3 = PI_*PI_*PI_;
302 const double epsilon_0 = 8.8541878128e-12; // https://en.wikipedia.org/wiki/Vacuum_permittivity, in F/m, or C^2⋅N^−1⋅m^−2
303 const double k_e = teqp::constants::k_e; // coulomb constant, with units of N m^2 / C^2
304 const double k_B = teqp::constants::k_B; // Boltzmann constant, with units of J/K
305
306 multipolar_rhostar_approach approach = multipolar_rhostar_approach::use_packing_fraction;
307
308 // These values were adjusted in the model of Paricaud, JPCB, 2023
310 const double C3, C3b;
311 std::optional<PolarizableArrays> polarizable;
312
313 double get_C3(const std::optional<nlohmann::json>& flags, double default_value=1.0){
314 if (flags){ return flags.value().value("C3", default_value); }
315 return default_value;
316 }
317 double get_C3b(const std::optional<nlohmann::json>& flags, double default_value=1.0){
318 if (flags){ return flags.value().value("C3b", default_value); }
319 return default_value;
320 }
321 multipolar_rhostar_approach get_approach(const std::optional<nlohmann::json>& flags){
322 if (flags){ return flags.value().value("approach", multipolar_rhostar_approach::use_packing_fraction); }
323 return multipolar_rhostar_approach::use_packing_fraction;
324 }
325 std::optional<PolarizableArrays> get_polarizable(const std::optional<nlohmann::json>& flags){
326 if (flags && flags.value().contains("polarizable")){
327 PolarizableArrays arrays;
328 auto toeig = [](const std::valarray<double>& x) -> Eigen::ArrayXd{ return Eigen::Map<const Eigen::ArrayXd>(&(x[0]), x.size());};
329 auto alpha_symm_m3 = toeig(flags.value()["polarizable"].at("alpha_symm / m^3"));
330 auto alpha_asymm_m3 = toeig(flags.value()["polarizable"].at("alpha_asymm / m^3"));
331 arrays.alpha_symm_C2m2J = alpha_symm_m3/k_e;
332 arrays.alpha_asymm_C2m2J = alpha_asymm_m3/k_e;
333 arrays.alpha_isotropic_C2m2J = 1.0/3.0*(arrays.alpha_symm_C2m2J + 2.0*arrays.alpha_asymm_C2m2J);
334 arrays.alpha_anisotropic_C2m2J = arrays.alpha_symm_C2m2J - arrays.alpha_asymm_C2m2J;
335 return arrays;
336 }
337 return std::nullopt;
338 }
339
340public:
341 MultipolarContributionGrayGubbins(const Eigen::ArrayX<double> &sigma_m, const Eigen::ArrayX<double> &epsilon_over_k, const Eigen::MatrixXd& SIGMAIJ, const Eigen::MatrixXd& EPSKIJ, const Eigen::ArrayX<double> &mu, const Eigen::ArrayX<double> &Q, const std::optional<nlohmann::json>& flags)
342
343 : sigma_m(sigma_m), epsilon_over_k(epsilon_over_k), SIGMAIJ(SIGMAIJ), EPSKIJ(EPSKIJ), mu(mu), Q(Q), mu2(mu.pow(2)), Q2(Q.pow(2)), Q3(Q.pow(3)), has_a_polar(Q.cwiseAbs().sum() > 0 || mu.cwiseAbs().sum() > 0), sigma_m3(sigma_m.pow(3)), sigma_m5(sigma_m.pow(5)), approach(get_approach(flags)), C3(get_C3(flags)), C3b(get_C3b(flags)), polarizable(get_polarizable(flags)) {
344 // Check lengths match
345 if (sigma_m.size() != mu.size()){
346 throw teqp::InvalidArgument("bad size of mu");
347 }
348 if (sigma_m.size() != Q.size()){
349 throw teqp::InvalidArgument("bad size of Q");
350 }
351 if (polarizable){
352 if (polarizable.value().alpha_symm_C2m2J.size() != sigma_m.size() || polarizable.value().alpha_asymm_C2m2J.size() != sigma_m.size()){
353 throw teqp::InvalidArgument("bad size of alpha arrays");
354 }
355 }
356 }
358
360 template<typename Jintegral, typename TTYPE, typename RhoStarType>
361 auto get_In(const Jintegral& J, int n, double sigmaij, const TTYPE& Tstar, const RhoStarType& rhostar) const{
362 return 4.0*PI_/pow(sigmaij, n-3)*J.get_J(Tstar, rhostar);
363 }
364
366 template<typename TTYPE, typename RhoStarType>
367 auto Immm(std::size_t i, std::size_t j, std::size_t k, const TTYPE& T, const RhoStarType& rhostar) const {
368 auto Tstarij = T/EPSKIJ(i,j), Tstarik = T/EPSKIJ(i,k), Tstarjk = T/EPSKIJ(j, k);
369 const double coeff = 64.0*PI3/5.0*sqrt(14*PI_/5.0)/SIGMAIJ(i,j)/SIGMAIJ(i,k)/SIGMAIJ(j,k);
370 return coeff*get_Kijk(K222_333, rhostar, Tstarij, Tstarik, Tstarjk);
371 }
372 template<typename TTYPE, typename RhoStarType>
373 auto ImmQ(std::size_t i, std::size_t j, std::size_t k, const TTYPE& T, const RhoStarType& rhostar) const {
374 auto Tstarij = T/EPSKIJ(i,j), Tstarik = T/EPSKIJ(i,k), Tstarjk = T/EPSKIJ(j, k);
375 const double coeff = 2048.0*PI3/7.0*sqrt(3.0*PI_)/SIGMAIJ(i,j)/POW2(SIGMAIJ(i,k)*SIGMAIJ(j,k));
376 return coeff*get_Kijk(K233_344, rhostar, Tstarij, Tstarik, Tstarjk);
377 }
378 template<typename TTYPE, typename RhoStarType>
379 auto ImQQ(std::size_t i, std::size_t j, std::size_t k, const TTYPE& T, const RhoStarType& rhostar) const {
380 auto Tstarij = T/EPSKIJ(i,j), Tstarik = T/EPSKIJ(i,k), Tstarjk = T/EPSKIJ(j, k);
381 const double coeff = -4096.0*PI3/9.0*sqrt(22.0*PI_/7.0)/POW2(SIGMAIJ(i,j)*SIGMAIJ(i,k))/POW3(SIGMAIJ(j,k));
382 return coeff*get_Kijk_334445(K334_445, rhostar, Tstarij, Tstarik, Tstarjk);
383 }
384 template<typename TTYPE, typename RhoStarType>
385 auto IQQQ(std::size_t i, std::size_t j, std::size_t k, const TTYPE& T, const RhoStarType& rhostar) const {
386 auto Tstarij = T/EPSKIJ(i,j), Tstarik = T/EPSKIJ(i,k), Tstarjk = T/EPSKIJ(j, k);
387 const double coeff = 8192.0*PI3/81.0*sqrt(2002.0*PI_)/POW3(SIGMAIJ(i,j)*SIGMAIJ(i,k)*SIGMAIJ(j,k));
388 return coeff*get_Kijk(K444_555, rhostar, Tstarij, Tstarik, Tstarjk);
389 }
390
392 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType, typename MuPrimeType>
393 auto get_alpha2(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions, const MuPrimeType& muprime) const{
394 const auto& x = mole_fractions; // concision
395
396 const std::size_t N = mole_fractions.size();
397
398 std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0]), decltype(muprime[0])> summer = 0.0;
399
400 const TTYPE beta = forceeval(1.0/(k_B*T));
401 const auto muprime2 = POW2(muprime).eval();
402
403 using ztype = std::common_type_t<TTYPE, decltype(muprime[0])>;
404 // We have to do this type promotion to the ztype to allow for multiplication with
405 // Eigen array types, as some type promotion does not happen automatically
406 //
407 // alpha has units of m^3, divide by k_e (has units of J m / C^2) to get C^2m^2/J, beta has units of 1/J, muprime^2 has units of C m
408 Eigen::ArrayX<ztype> z1 = 1.0/3.0*(muprime2.template cast<ztype>()*static_cast<ztype>(beta));
409 Eigen::ArrayX<ztype> z2 = 0.0*z1;
410 if (polarizable){
411 z1 += polarizable.value().alpha_isotropic_C2m2J.template cast<ztype>();
412 z2 += polarizable.value().alpha_isotropic_C2m2J.template cast<ztype>();
413 }
414
415 for (std::size_t i = 0; i < N; ++i){
416 for (std::size_t j = 0; j < N; ++j){
417 TTYPE Tstarij = forceeval(T/EPSKIJ(i, j));
418 double sigmaij = SIGMAIJ(i,j);
419 summer += x[i]*x[j]*(
420 3.0/2.0*(z1[i]*z1[j] - z2[i]*z2[j])*get_In(J6, 6, sigmaij, Tstarij, rhostar)
421 + 3.0/2.0*z1[i]*beta*Q2[j]*get_In(J8, 8, sigmaij, Tstarij, rhostar)
422 +7.0/10.0*beta*beta*Q2[i]*Q2[j]*get_In(J10, 10, sigmaij, Tstarij, rhostar)
423 );
424 }
425 }
426 return forceeval(-rhoN*k_e*k_e*summer); // The factor of k_e^2 takes us from CGS to SI units
427 }
428
430 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType, typename MuPrimeType>
431 auto get_alpha2_muprime_gradient(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions, const MuPrimeType& muprime) const{
432 const auto& x = mole_fractions; // concision
433 const std::size_t N = mole_fractions.size();
434
435 using type_ = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0]), decltype(muprime[0])>;
436
437 const TTYPE beta = 1.0/(k_B*T);
438 using ztype = std::common_type_t<TTYPE, decltype(muprime[0])>;
439 // We have to do this type promotion to the ztype to allow for multiplication with
440 // Eigen array types, as some type promotion does not happen automatically
441 Eigen::ArrayX<ztype> z1 = 1.0/3.0*(POW2(muprime).template cast<ztype>()*static_cast<ztype>(beta));
442 if (polarizable){
443 z1 += polarizable.value().alpha_isotropic_C2m2J.template cast<ztype>();
444 }
445
446 // Exactly the term as defined in Gray et al., Eq. 2.11
447 Eigen::ArrayX<type_> Eprime2(N);
448 for (std::size_t i = 0; i < N; ++i){
449 type_ summer = 0;
450 for (std::size_t j = 0; j < N; ++j){
451 TTYPE Tstarij = T/EPSKIJ(i, j);
452 double sigmaij = SIGMAIJ(i, j);
453 auto rhoj = rhoN*x[j];
454 summer += rhoj*(2.0*z1[i]*get_In(J6, 6, sigmaij, Tstarij, rhostar) + beta*Q2[j]*get_In(J8, 8, sigmaij, Tstarij, rhostar) );
455 }
456 Eprime2[i] = muprime[i]*summer;
457 }
458 // And now to get dalpha2/dmu', multiply by -beta*x
459 return (-k_e*k_e*Eprime2*mole_fractions.template cast<type_>()*beta).eval(); // The factor of k_e^2 takes us from CGS to SI units
460 }
461
463 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType, typename MuPrimeType>
464 auto get_alpha3(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions, const MuPrimeType& muprime) const{
465 const VecType& x = mole_fractions; // concision
466 const std::size_t N = mole_fractions.size();
467 using type = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0]), decltype(muprime[0])>;
468 type summer_a = 0.0, summer_b = 0.0;
469
470 const TTYPE beta = forceeval(1.0/(k_B*T));
471 const auto muprime2 = POW2(muprime).eval();
472 // We have to do this type promotion to the ztype to allow for multiplication with
473 // Eigen array types, as some type promotion does not happen automatically
474 using ztype = std::common_type_t<TTYPE, decltype(muprime[0])>;
475 Eigen::ArrayX<ztype> z1 = 1.0/3.0*(muprime2.template cast<ztype>()*static_cast<ztype>(beta));
476 Eigen::ArrayX<ztype> z2 = 0.0*z1;
477 Eigen::ArrayX<ztype> gamma = 0.0*z1;
478 if (polarizable){
479 z1 += polarizable.value().alpha_isotropic_C2m2J.template cast<ztype>(); // alpha has units of m^3, divide by k_e (has units of J m / C^2) to get C^2m^2/J, beta has units of 1/J, muprime^2 has units of C m
480 z2 += polarizable.value().alpha_isotropic_C2m2J.template cast<ztype>();
481 gamma += polarizable.value().alpha_symm_C2m2J.template cast<ztype>() - polarizable.value().alpha_asymm_C2m2J.template cast<ztype>();
482 }
483
484 for (std::size_t i = 0; i < N; ++i){
485 for (std::size_t j = 0; j < N; ++j){
486
487 TTYPE Tstarij = forceeval(T/EPSKIJ(i,j));
488 double sigmaij = SIGMAIJ(i,j);
489
490 auto a_ij = ((2.0/5.0*beta*beta*muprime2[i]*muprime2[j] + 4.0/5.0*gamma[i]*beta*muprime2[j] + 4.0/25.0*gamma[i]*gamma[j])*beta*Q[i]*Q[j]*get_In(J11, 11, sigmaij, Tstarij, rhostar)
491 +12.0/35.0*(beta*muprime2[i] + gamma[i])*beta*beta*Q[i]*POW3(Q[j])*get_In(J13, 13, sigmaij, Tstarij, rhostar)
492 + 36.0/245.0*POW3(beta)*Q3[i]*Q3[j]*get_In(J15, 15, sigmaij, Tstarij, rhostar)
493 );
494 summer_a += x[i]*x[j]*a_ij;
495
496 for (std::size_t k = 0; k < N; ++k){
497 auto b_ijk = (
498 1.0/2.0*(z1[i]*z1[j]*z1[k] - z2[i]*z2[j]*z2[k])*Immm(i, j, k, T, rhostar)
499 +C3b*(3.0/160.0*z1[i]*z1[j]*beta*Q2[k]*ImmQ(i, j, k, T, rhostar) + 3.0/640.0*z1[i]*POW2(beta)*Q2[j]*Q2[k]*ImQQ(i,j,k,T, rhostar))
500 +1.0/6400.0*POW3(beta)*Q2[i]*Q2[j]*Q2[k]*IQQQ(i, j, k, T, rhostar)
501 );
502 summer_b += x[i]*x[j]*x[k]*b_ijk;
503 }
504 }
505 }
506
507 return forceeval(C3*(rhoN*summer_a + rhoN*rhoN*summer_b)*k_e*k_e*k_e); // The factor of k_e^3 takes us from CGS to SI units
508 }
509
511 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType, typename MuPrimeType>
512 auto get_alpha3_muprime_gradient(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions, const MuPrimeType& muprime) const{
513 const VecType& x = mole_fractions; // concision
514 const std::size_t N = mole_fractions.size();
515 using type_ = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0]), decltype(muprime[0])>;
516
517 const TTYPE beta = forceeval(1.0/(k_B*T));
518 const auto muprime2 = POW2(muprime).eval();
519 // We have to do this type promotion to the ztype to allow for multiplication with
520 // Eigen array types, as some type promotion does not happen automatically
521 using ztype = std::common_type_t<TTYPE, decltype(muprime[0])>;
522 Eigen::ArrayX<ztype> z1 = 1.0/3.0*(muprime2.template cast<ztype>()*static_cast<ztype>(beta));
523 Eigen::ArrayX<ztype> gamma = 0.0*z1;
524 if (polarizable){
525 z1 += polarizable.value().alpha_isotropic_C2m2J.template cast<ztype>(); // alpha has units of m^3, divide by k_e (has units of J m / C^2) to get C^2m^2/J, beta has units of 1/J, muprime^2 has units of C m
526 gamma += polarizable.value().alpha_symm_C2m2J.template cast<ztype>() - polarizable.value().alpha_asymm_C2m2J.template cast<ztype>();
527 }
528
529 // Exactly as in Eq. 2.12 of Gray et al. (aside from the scaling parameters of Paricaud)
530 Eigen::ArrayX<type_> Eprime3(N);
531 type_ summer_ij = 0, summer_ijk = 0;
532 for (std::size_t i = 0; i < N; ++i){
533 for (std::size_t j = 0; j < N; ++j){
534
535 TTYPE Tstarij = forceeval(T/EPSKIJ(i,j));
536 double sigmaij = SIGMAIJ(i,j);
537 auto p_ij = 8.0/5.0*(beta*muprime2[j] + gamma[j])*beta*Q[i]*Q[j]*get_In(J11, 11, sigmaij, Tstarij, rhostar) + 24.0/35.0*beta*beta*Q[i]*Q3[j]*get_In(J13, 13, sigmaij, Tstarij, rhostar);
538 summer_ij += (rhoN*x[j]*p_ij);
539
540 for (std::size_t k = 0; k < N; ++k){
541 auto q_ijk = (
542 z1[j]*z1[k]*Immm(i, j, k, T, rhostar)
543 +C3b*1.0/40.0*z1[j]*beta*Q2[k]*ImmQ(i, j, k, T, rhostar)
544 + 1.0/320.0*POW2(beta)*Q2[j]*Q2[k]*ImQQ(i,j,k,T, rhostar)
545 );
546 summer_ijk += POW2(rhoN)*x[j]*x[k]*q_ijk;
547 }
548 }
549 Eprime3[i] = -muprime[i]*(summer_ij + summer_ijk);
550 }
551
552 return (-C3*Eprime3*k_e*k_e*k_e*mole_fractions.template cast<type_>()*beta).eval(); // The factor of k_e^3 takes us from CGS to SI units
553 }
554
555 template<typename RhoType, typename PFType, typename MoleFractions>
556 auto get_rhostar(const RhoType rhoN, const PFType& packing_fraction, const MoleFractions& mole_fractions) const{
557 using type = std::common_type_t<RhoType, PFType, decltype(mole_fractions[0])>;
558 type rhostar;
559 if (approach == multipolar_rhostar_approach::calculate_Gubbins_rhostar){
560 // Calculate the effective reduced diameter (cubed) to be used for evaluation
561 // Eq. 24 from Gubbins
562 type sigma_x3 = 0.0;
563 error_if_expr(sigma_m3);
564 auto N = mole_fractions.size();
565 for (auto i = 0; i < N; ++i){
566 for (auto j = 0; j < N; ++j){
567 auto sigmaij = (sigma_m[i] + sigma_m[j])/2;
568 sigma_x3 += mole_fractions[i]*mole_fractions[j]*POW3(sigmaij);
569 }
570 }
571 rhostar = forceeval(rhoN*sigma_x3);
572 }
573 else if (approach == multipolar_rhostar_approach::use_packing_fraction){
574 // The packing fraction is defined by eta = pi/6*rho^*, so use the (temperature-dependent) eta to obtain rho^*
575 rhostar = forceeval(packing_fraction/(static_cast<double>(EIGEN_PI)/6.0));
576 }
577 else{
578 throw teqp::InvalidArgument("The method used to determine rho^* is invalid");
579 }
580 return rhostar;
581 }
582
584 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType, typename MuPrimeType>
585 auto get_Eprime(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions, const MuPrimeType& muprime) const{
586 if (!polarizable){
587 throw teqp::InvalidArgument("Can only use polarizable code if polarizability is enabled");
588 }
589// {
590// // The lambda function to evaluate the gradient of the perturbational term
591// // w.r.t. the effective dipole moment
592// auto f = [&](const auto& muprime){
593// auto alpha2 = get_alpha2(T, rhoN, rhostar, mole_fractions, muprime);
594// auto alpha3 = get_alpha3(T, rhoN, rhostar, mole_fractions, muprime);
595// auto alpha = forceeval(alpha2/(1.0-alpha3/alpha2));
596// return forceeval(alpha);
597// };
598// Eigen::ArrayX<autodiff::real> muprimead = muprime.template cast<autodiff::real>();
599// Eigen::ArrayXd dalphaperturb_dmuprimead = autodiff::gradient(f, wrt(muprimead), at(muprimead)); // units of 1/(C m)
600//
601// auto f2 = [&](const auto& muprime){
602// return get_alpha2(T, rhoN, rhostar, mole_fractions, muprime);
603// };
604// Eigen::ArrayXd dalphaperturb2_dmuprimead = autodiff::gradient(f2, wrt(muprimead), at(muprimead)); // units of 1/(C m)
605// Eigen::ArrayXd dalphaperturb2_dmuprime = get_alpha2_muprime_gradient(T, rhoN, rhostar, mole_fractions, muprime);
606//
607// auto f3 = [&](const auto& muprime){
608// return get_alpha3(T, rhoN, rhostar, mole_fractions, muprime);
609// };
610// Eigen::ArrayXd dalphaperturb3_dmuprimead = autodiff::gradient(f3, wrt(muprimead), at(muprimead)); // units of 1/(C m)
611// Eigen::ArrayXd dalphaperturb3_dmuprime = get_alpha3_muprime_gradient(T, rhoN, rhostar, mole_fractions, muprime);
612//
613// std::cout << dalphaperturb3_dmuprimead << " || " << dalphaperturb3_dmuprime << std::endl;
614//
615// auto dmu = 1e-6*muprime[0];
616// Eigen::ArrayXd muprimep = muprime; muprimep[0] += dmu;
617// Eigen::ArrayXd muprimem = muprime; muprimem[0] -= dmu;
618// auto dalphaperturb2_dmuprime_centered = (f2(muprimep)-f2(muprimem))/(2*dmu);
619// }
620
621 auto alpha2 = get_alpha2(T, rhoN, rhostar, mole_fractions, muprime);
622 auto alpha3 = get_alpha3(T, rhoN, rhostar, mole_fractions, muprime);
623 auto dalphaperturb2_dmuprime = get_alpha2_muprime_gradient(T, rhoN, rhostar, mole_fractions, muprime);
624 auto dalphaperturb3_dmuprime = get_alpha3_muprime_gradient(T, rhoN, rhostar, mole_fractions, muprime);
625 auto dalphaperturb_dmuprime = (((1.0-2.0*alpha3/alpha2)*dalphaperturb2_dmuprime + dalphaperturb3_dmuprime)/POW2(1.0-alpha3/alpha2)).eval();
626
627 return (-k_B*T*dalphaperturb_dmuprime).eval(); // Eprime has units of J /(C m) because alpha is dimensionless
628 }
629
631 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType, typename MuPrimeType>
632 auto iterate_muprime_SS(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions, const MuPrimeType& mu, const int max_steps) const{
633 if (!polarizable){
634 throw teqp::InvalidArgument("Can only use polarizable code if polarizability is enabled");
635 }
636 using otype = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0]), decltype(mu[0])>;
637 Eigen::ArrayX<otype> muprime = mu.template cast<otype>();
638 for (auto counter = 0; counter < max_steps; ++counter){
639 auto Eprime = get_Eprime(T, rhoN, rhostar, mole_fractions, muprime); // units of J /(C m)
640 // alpha*Eprime has units of J m^3/(C m), divide by k_e (has units of J m / C^2) to get C m
641 muprime = mu.template cast<otype>() + polarizable.value().alpha_symm_C2m2J.template cast<otype>()*Eprime.template cast<otype>(); // Units of C m
642 }
643 return muprime;
644 }
645
646 /***
647 * \brief Get the contribution to \f$ \alpha = A/(NkT) \f$
648 */
649 template<typename TTYPE, typename RhoType, typename RhoStarType, typename VecType>
650 auto eval(const TTYPE& T, const RhoType& rhoN, const RhoStarType& rhostar, const VecType& mole_fractions) const {
651 error_if_expr(T); error_if_expr(rhoN); error_if_expr(mole_fractions[0]);
652 using type = std::common_type_t<TTYPE, RhoType, RhoStarType, decltype(mole_fractions[0])>;
653
654 if (!polarizable){
655 type alpha2 = 0.0, alpha3 = 0.0, alpha = 0.0;
656 if (has_a_polar){
657 alpha2 = get_alpha2(T, rhoN, rhostar, mole_fractions, mu);
658 alpha3 = get_alpha3(T, rhoN, rhostar, mole_fractions, mu);
659 alpha = forceeval(alpha2/(1.0-alpha3/alpha2));
660 // Handle the case where the polar term is present but the contribution is zero
661 if (getbaseval(alpha2) == 0){
662 alpha2 = 0;
663 alpha = 0;
664 }
665 }
666 return MultipolarContributionGubbinsTwuTermsGT<type>{alpha2, alpha3, alpha};
667 }
668 else{
669 // First solve for the effective dipole moments
670 auto muprime = iterate_muprime_SS(T, rhoN, rhostar, mole_fractions, mu, 10); // C m, array
671 // And the polarization energy derivative, units of J /(C m)
672 auto Eprime = get_Eprime(T, rhoN, rhostar, mole_fractions, muprime); // array
673 using Eprime_t = std::decay_t<decltype(Eprime[0])>;
674 // And finally the polarization contribution to total polar term
675 auto U_p_over_rhoN = 0.5*(mole_fractions.template cast<Eprime_t>()*polarizable.value().alpha_symm_C2m2J.template cast<Eprime_t>()*Eprime*Eprime).eval().sum(); // U_p divided by rhoN, has units of J
676 auto alpha_polarization = U_p_over_rhoN/(k_B*T); // nondimensional, scalar
677
678 auto alpha2 = get_alpha2(T, rhoN, rhostar, mole_fractions, muprime);
679 auto alpha3 = get_alpha3(T, rhoN, rhostar, mole_fractions, muprime);
680 auto alpha = forceeval(alpha2/(1.0-alpha3/alpha2));
681 return MultipolarContributionGubbinsTwuTermsGT<type>{alpha2, alpha3, alpha + alpha_polarization};
682 }
683 }
684};
685
695>;
696
697}
698}
auto get_rhostar(const RhoType rhoN, const PFType &packing_fraction, const MoleFractions &mole_fractions) const
auto IQQQ(std::size_t i, std::size_t j, std::size_t k, const TTYPE &T, const RhoStarType &rhostar) const
auto get_alpha2(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions, const MuPrimeType &muprime) const
Return , thus this is a nondimensional term. This is equivalent to from Gray et al.
auto get_alpha2_muprime_gradient(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions, const MuPrimeType &muprime) const
Return , thus this is a nondimensional term. This is equivalent to from Gray et al.
auto iterate_muprime_SS(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions, const MuPrimeType &mu, const int max_steps) const
Use successive substitution to obtain the effective dipole moment based solely on the perturbation te...
auto get_alpha3_muprime_gradient(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions, const MuPrimeType &muprime) const
Return , thus this is a nondimensional term. This is equivalent to from Gray et al.
MultipolarContributionGrayGubbins & operator=(const MultipolarContributionGrayGubbins &)=delete
static constexpr multipolar_argument_spec arg_spec
auto ImmQ(std::size_t i, std::size_t j, std::size_t k, const TTYPE &T, const RhoStarType &rhostar) const
auto get_alpha3(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions, const MuPrimeType &muprime) const
Return , thus this is a nondimensional term. This is equivalent to from Gray et al.
MultipolarContributionGrayGubbins(const Eigen::ArrayX< double > &sigma_m, const Eigen::ArrayX< double > &epsilon_over_k, const Eigen::MatrixXd &SIGMAIJ, const Eigen::MatrixXd &EPSKIJ, const Eigen::ArrayX< double > &mu, const Eigen::ArrayX< double > &Q, const std::optional< nlohmann::json > &flags)
auto get_Eprime(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions, const MuPrimeType &muprime) const
Get the polarization term .
auto get_In(const Jintegral &J, int n, double sigmaij, const TTYPE &Tstar, const RhoStarType &rhostar) const
Appendix B of Gray et al.
auto Immm(std::size_t i, std::size_t j, std::size_t k, const TTYPE &T, const RhoStarType &rhostar) const
Appendix B of Gray et al.
auto eval(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions) const
auto ImQQ(std::size_t i, std::size_t j, std::size_t k, const TTYPE &T, const RhoStarType &rhostar) const
static constexpr multipolar_argument_spec arg_spec
auto get_alpha2(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions) const
auto get_alpha3(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions) const
auto eval(const TTYPE &T, const RhoType &rhoN, const RhoStarType &rhostar, const VecType &mole_fractions) const
MultipolarContributionGubbinsTwu(const Eigen::ArrayX< double > &sigma_m, const Eigen::ArrayX< double > &epsilon_over_k, const Eigen::ArrayX< double > &mubar2, const Eigen::ArrayX< double > &Qbar2, multipolar_rhostar_approach approach)
auto get_rhostar(const RhoType rhoN, const PFType &packing_fraction, const MoleFractions &mole_fractions) const
MultipolarContributionGubbinsTwu & operator=(const MultipolarContributionGubbinsTwu &)=delete
std::variant< teqp::saft::polar_terms::GrossVrabec::MultipolarContributionGrossVrabec, MultipolarContributionGrayGubbins< GubbinsTwuJIntegral, GubbinsTwuKIntegral >, MultipolarContributionGrayGubbins< GottschalkJIntegral, GottschalkKIntegral >, MultipolarContributionGrayGubbins< LuckasJIntegral, LuckasKIntegral >, MultipolarContributionGubbinsTwu< LuckasJIntegral, LuckasKIntegral >, MultipolarContributionGubbinsTwu< GubbinsTwuJIntegral, GubbinsTwuKIntegral >, MultipolarContributionGubbinsTwu< GottschalkJIntegral, GottschalkKIntegral > > multipolar_contributions_variant
The variant containing the multipolar types that can be provided.
auto get_Kijk_334445(const KType &Kint, const RhoType &rhostar, const Txy &Tstarij, const Txy &Tstarik, const Txy &Tstarjk)
auto get_Kijk(const KType &Kint, const RhoType &rhostar, const Txy &Tstarij, const Txy &Tstarik, const Txy &Tstarjk)
const double k_e
Coulomb constant, with units of N m^2 / C^2.
Definition constants.hpp:16
const double k_B
Boltzmann constant.
Definition constants.hpp:13
auto POW2(const A &x)
auto POW5(const A &x)
auto toeig(const std::vector< double > &v) -> Eigen::ArrayXd
Definition types.hpp:10
auto POW3(const A &x)
auto getbaseval(const T &expr)
Definition types.hpp:90
void error_if_expr(T &&)
Definition types.hpp:69
T powi(const T &x, int n)
From Ulrich Deiters.
Definition types.hpp:139
auto pow(const double &x, const double &e)
Definition types.hpp:195
auto forceeval(T &&expr)
Definition types.hpp:52