teqp 0.22.0
Loading...
Searching...
No Matches
association.hpp
Go to the documentation of this file.
1
10#pragma once
11
12#include <map>
13#include <set>
14#include <variant>
15
16#include "teqp/constants.hpp"
17#include "teqp/types.hpp"
18#include "teqp/exceptions.hpp"
19
20#include <Eigen/Dense>
23#include "teqp/json_tools.hpp"
24
25namespace teqp{
26namespace association{
27
29 std::map<std::string, std::vector<std::string>> interaction_partners;
30 std::vector<std::string> site_order;
33 std::vector<bool> self_association_mask;
35 double alpha = 0.5;
36 double rtol = 1e-12, atol = 1e-12;
37 int max_iters = 100;
38};
39inline void from_json(const nlohmann::json& j, AssociationOptions& o) {
40 if (j.contains("alpha")){ j.at("alpha").get_to(o.alpha); }
41 if (j.contains("rtol")){ j.at("rtol").get_to(o.rtol); }
42 if (j.contains("atol")){ j.at("atol").get_to(o.atol); }
43 if (j.contains("max_iters")){ j.at("max_iters").get_to(o.max_iters); }
44}
45
46
47namespace DufalMatrices{
48 extern const std::unordered_map<int, Eigen::MatrixXd> bcoeffs;
49}
50
51/***
52 A general class for calculating association fractions and association energy for mixtures
53
54 A mixture is formed of multiple components, each component has an index i.
55 For each component, it may multiple unique sites, and each unique site has a multiplicity associated with it.
56
57 For instance, for 4C water, there are two "e" sites and two "H" sites in the nomenclature of Clapeyron.
58 Thus the "e" site has multiplicity of 2 and the "H" site has multiplicity of 2
59 */
61public:
62 using CompSite = std::tuple<std::size_t, std::string>;
63 using CompCIndex = std::tuple<std::size_t, std::size_t>;
65 std::map<std::size_t, CompSite> to_CompSite;
66 std::map<CompSite, std::size_t> to_siteid;
67 std::map<CompCIndex, std::size_t> CompCIndex_to_siteid;
68 Eigen::ArrayXi counts;
69 Eigen::ArrayXi N_sites;
70 Eigen::ArrayXi N_unique_sites;
71 Eigen::ArrayXi comp_index;
72 std::vector<std::vector<std::string>> molecule_sites;
73 };
74private:
75 IndexMapper make_mapper(const std::vector<std::vector<std::string>>& molecule_sites, const AssociationOptions& options) const {
76 IndexMapper ind;
77 ind.counts.resize(1000);
78 ind.comp_index.resize(1000);
79 ind.N_sites.resize(molecule_sites.size());
80 ind.N_unique_sites.resize(molecule_sites.size());
81 ind.molecule_sites = molecule_sites;
82
83 // Build the maps from siteid->(component index, site name) and (component index, site name)->siteid
84 std::size_t icomp = 0;
85 std::size_t siteid = 0;
86 for (auto& molecule: molecule_sites){
88 std::map<std::string, int> site_counts;
89 for (auto& site: molecule){
90 ++site_counts[site];
91 }
92 auto unique_sites_on_molecule = std::set(molecule.begin(), molecule.end());
93 if (!options.site_order.empty()){
94 // TODO: enforce sites to appear in the order matching the specification
95 // TODO: this would be required to for instance check the D matrix of Langenbach and Enders
96 }
97 std::size_t Cindex = 0;
98 for (auto& site: unique_sites_on_molecule){
99 CompSite cs{icomp, site};
100 ind.to_CompSite[siteid] = cs;
101 ind.to_siteid[cs] = siteid;
102 ind.CompCIndex_to_siteid[CompCIndex{icomp, Cindex}] = siteid;
103 ind.counts[siteid] = site_counts[site];
104 ind.comp_index[siteid] = static_cast<int>(icomp);
105 Cindex++;
106 siteid++;
107 }
108 ind.N_sites[icomp] = static_cast<int>(molecule.size());
109 ind.N_unique_sites[icomp] = static_cast<int>(unique_sites_on_molecule.size());
110 icomp++;
111 }
112
113 ind.counts.conservativeResize(siteid);
114 ind.comp_index.conservativeResize(siteid);
115 return ind;
116 }
117
118 /***
119 Construct the counting matrix \f$ D_{IJ} \f$ as given by Langenbach and Enders
120 */
121 auto make_D(const IndexMapper& ind, const AssociationOptions& options ) const{
122
123 auto get_DIJ = [&ind, &options](std::size_t I, std::size_t J) -> int {
128 auto [ph1, typei] = ind.to_CompSite.at(I);
129 auto [ph2, typej] = ind.to_CompSite.at(J);
130
131 // If self-association is disabled for this site, then return zero for the D matrix
132 if (!options.self_association_mask.empty() && ph1 == ph2 && !options.self_association_mask[ph1]){
133 return 0;
134 }
135 auto contains = [](auto& container, const auto& val){ return std::find(container.begin(), container.end(), val) != container.end(); };
137 if (options.interaction_partners.empty() || (contains(options.interaction_partners.at(typei), typej))){
138 return ind.counts[J];
139 }
140 return 0;
141 };
142 if (!options.self_association_mask.empty() && options.self_association_mask.size() != static_cast<std::size_t>(ind.N_sites.size())){
143 throw teqp::InvalidArgument("self_association_mask is of the wrong size");
144 }
145 int Ngroups = static_cast<int>(ind.to_siteid.size());
146 Eigen::ArrayXXi Dmat(Ngroups, Ngroups);
147 // I and J are the numerical indices of the sites
148 for (int I = 0; I < Ngroups; ++I){
149 for (int J = 0; J < Ngroups; ++J){
150 Dmat(I, J) = get_DIJ(I, J);
151 }
152 }
153 return Dmat;
154 }
155 static auto toEig(const nlohmann::json& j, const std::string& k) -> Eigen::ArrayXd{
156 std::vector<double> vec = j.at(k);
157 return Eigen::Map<Eigen::ArrayXd>(&vec[0], vec.size());
158 };
159 static auto get_association_options(const nlohmann::json&j){
160 AssociationOptions opt;
161 if (j.contains("options")){
162 opt = j.at("options").get<AssociationOptions>();
163 // Copy over the self-association mask
164 if (j.contains("/options/self_association_mask"_json_pointer)){
165 opt.self_association_mask = j.at("/options/self_association_mask"_json_pointer).template get<std::vector<bool>>();
166 }
167 }
168 return opt;
169 }
170public:
173 const Eigen::ArrayXXi D;
175 const std::variant<CanonicalData, DufalData> datasidecar;
176
177 Association(const Eigen::ArrayXd& b_m3mol, const Eigen::ArrayXd& beta, const Eigen::ArrayXd& epsilon_Jmol, const std::vector<std::vector<std::string>>& molecule_sites, const AssociationOptions& options) : options(options), mapper(make_mapper(molecule_sites, options)), D(make_D(mapper, options)), m_Delta_rule(options.Delta_rule), datasidecar(CanonicalData{b_m3mol, beta, epsilon_Jmol, options.radial_dist}){
179 throw std::invalid_argument("Delta rule is invalid; options are: {CR1}");
180 }
181 }
182 Association(const decltype(datasidecar)& data, const std::vector<std::vector<std::string>>& molecule_sites, const AssociationOptions& options) : options(options), mapper(make_mapper(molecule_sites, options)), D(make_D(mapper, options)), m_Delta_rule(options.Delta_rule), datasidecar(data) {
183 }
184 static Association factory(const nlohmann::json& j){
185
186 // Collect the set of unique site types among all the molecules
187 std::set<std::string> unique_site_types;
188 for (auto molsite : j.at("molecule_sites")) {
189 for (auto & s : molsite){
190 unique_site_types.insert(s);
191 }
192 }
193
194 auto get_interaction_partners = [&](const nlohmann::json& j){
195 std::map<std::string, std::vector<std::string>> interaction_partners;
196
197 if (j.contains("options") && j.at("options").contains("interaction_partners")){
198 interaction_partners = j.at("options").at("interaction_partners");
199 for (auto [k,partners] : interaction_partners){
200 if (unique_site_types.count(k) == 0){
201 throw teqp::InvalidArgument("Site is invalid in interaction_partners: " + k);
202 }
203 for (auto& partner : partners){
204 if (unique_site_types.count(partner) == 0){
205 throw teqp::InvalidArgument("Partner " + partner + " is invalid for site " + k);
206 }
207 }
208 }
209 }
210 else{
211 // Every site type is assumed to interact with every other site type, except for itself
212 for (auto& site1 : unique_site_types){
213 std::vector<std::string> partners;
214 for (auto& site2: unique_site_types){
215 if (site1 != site2){
216 partners.push_back(site2);
217 }
218 }
219 interaction_partners[site1] = partners;
220 }
221 }
222 return interaction_partners;
223 };
224
225 if (j.contains("Delta_rule")){
226 std::string Delta_rule = j.at("Delta_rule");
227 if (Delta_rule == "CR1"){
228 CanonicalData data;
229 data.b_m3mol = toEig(j, "b / m^3/mol");
230 data.beta = toEig(j, "beta");
231 data.epsilon_Jmol = toEig(j, "epsilon / J/mol");
232 auto options = get_association_options(j);
235 options.interaction_partners = get_interaction_partners(j);
236 return {data, j.at("molecule_sites"), options};
237 }
238 else if(Delta_rule == "Dufal"){
239 DufalData data;
240
241 // Parameters for the dispersive part
242 data.sigma_m = toEig(j, "sigma / m");
243 if (j.contains("epsilon / J/mol")){
244 data.epsilon_Jmol = toEig(j, "epsilon / J/mol");
245 }
246 else if (j.contains("epsilon/kB / K")){
247 data.epsilon_Jmol = toEig(j, "epsilon/kB / K")*constants::R_CODATA2017;
248 }
249 else{
250 throw teqp::InvalidArgument("One of the epsilon variables must be provided");
251 }
252 data.lambda_r = toEig(j, "lambda_r");
253 data.kmat = build_square_matrix(j.at("kmat"));
254 // Parameters for the associating part
255 data.epsilon_HB_Jmol = toEig(j, "epsilon_HB / J/mol");
256 data.K_HB_m3 = toEig(j, "K_HB / m^3");
257 data.apply_mixing_rules();
258
259 auto options = get_association_options(j);
261 options.interaction_partners = get_interaction_partners(j);
262 return {data, j.at("molecule_sites"), options};
263 }
264 }
265 throw std::invalid_argument("The Delta_rule has not been specified");
266 }
267
274 template<typename TType, typename RhoType, typename MoleFracsType>
275 auto get_Delta(const TType& T, const RhoType& rhomolar, const MoleFracsType& molefracs) const {
276
277 using resulttype = std::common_type_t<decltype(T), decltype(rhomolar), decltype(molefracs[0])>; // Type promotion, without the const-ness
278 using Mat = Eigen::Array<resulttype, Eigen::Dynamic, Eigen::Dynamic>;
279 auto Ngroups = mapper.to_CompSite.size();
280
281 // Calculate the radial_dist if it is meaningful
282 using eta_t = std::common_type_t<decltype(rhomolar), decltype(molefracs[0])>; // Type promotion, without the const-ness
283 std::optional<eta_t> g;
285 const CanonicalData& d = std::get<CanonicalData>(datasidecar);
286 auto bmix = (molefracs*d.b_m3mol).sum();
287 auto eta = bmix*rhomolar/4.0;
288 switch(d.radial_dist){
289 case radial_dists::CS:
290 g = (2.0-eta)/(2.0*(1.0-eta)*(1.0-eta)*(1.0-eta)); break;
291 case radial_dists::KG:
292 g = 1.0 / (1.0 - 1.9*eta); break;
293 default:
294 throw std::invalid_argument("Bad radial distribution");
295 }
296 }
297
299 auto get_I_Dufal = [](const auto& Tstar, const auto& rhostar, const auto& lambda_r){
300
301 using result_t = std::decay_t<std::common_type_t<decltype(Tstar), decltype(rhostar), decltype(lambda_r)>>;
302 result_t summer = 0.0;
303
304 std::decay_t<decltype(rhostar)> rhostar_to_i = 1.0;
305 for (auto i = 0U; i <= 10; ++i){
306 std::decay_t<decltype(Tstar)> Tstar_to_j = 1.0;
307 for (auto j = 0U; i + j <= 10; ++j){
308 double aij = 0.0, lambdar_to_k = 1.0;
309 for (auto k = 0; k <= 6; ++k){
310 double bijk = DufalMatrices::bcoeffs.at(k)(i,j);
311 aij += bijk*lambdar_to_k;
312 lambdar_to_k *= lambda_r;
313 }
314 summer += aij*rhostar_to_i*Tstar_to_j;
315 Tstar_to_j *= Tstar;
316 }
317 rhostar_to_i *= rhostar;
318 }
319 return summer;
320 };
321
322 using rhostar_t = std::common_type_t<decltype(rhomolar), decltype(molefracs[0])>; // Type promotion, without the const-ness
323 std::optional<rhostar_t> rhostar;
325 const DufalData& d = std::get<DufalData>(datasidecar);
326 // Calculate the one-fluid vdW1 sigma from Eq. 40 of Dufal
327 std::decay_t<decltype(molefracs[0])> sigma3_vdW1 = 0.0;
328 auto N = molefracs.size();
329 for (auto i = 0U; i < N; ++i){
330 for (auto j = 0U; j < N; ++j){
331 double sigma3_ij_m3 = POW3((d.sigma_m[i] + d.sigma_m[j])/2.0);
332 sigma3_vdW1 += molefracs[i]*molefracs[j]*sigma3_ij_m3;
333 }
334 }
335 rhostar = rhomolar*N_A*sigma3_vdW1;
336 }
337
338 Mat Delta = Mat::Zero(Ngroups, Ngroups);
339 for (auto I = 0U; I < Ngroups; ++I){
340 auto i = std::get<0>(mapper.to_CompSite.at(I));
341 for (auto J = 0U; J < Ngroups; ++J){
342 auto j = std::get<0>(mapper.to_CompSite.at(J));
343
344 using namespace teqp::constants;
345
347 // The CR1 rule is used to calculate the cross contributions
348 const CanonicalData& d = std::get<CanonicalData>(datasidecar);
349 auto b_ij = (d.b_m3mol[i] + d.b_m3mol[j])/2.0;
350 auto epsilon_ij_Jmol = (d.epsilon_Jmol[i] + d.epsilon_Jmol[j])/2.0;
351 auto beta_ij = sqrt(d.beta[i]*d.beta[j]);
352 Delta(I, J) = g.value()*b_ij*beta_ij*(exp(epsilon_ij_Jmol/(R_CODATA2017*T))-1.0)/N_A; // m^3
353 }
354 else if (m_Delta_rule == Delta_rules::Dufal){
355 const DufalData& d = std::get<DufalData>(datasidecar);
356 auto Tstar = forceeval(T/d.EPSILONOVERKij_K(i,j));
357 auto _I = get_I_Dufal(Tstar, rhostar.value(), d.LAMBDA_Rij(i, j));
358 auto F_Meyer = exp(d.EPSILONOVERK_HBij_K(i, j)/T)-1.0;
359 Delta(I, J) = F_Meyer*d.KHBij_m3(i,j)*_I;
360 }
361 else{
362 throw std::invalid_argument("Don't know what to do with this Delta rule");
363 }
364 }
365 }
366 return Delta;
367 }
368
369 template<typename TType, typename RhoType, typename MoleFracsType, typename XType>
370 auto successive_substitution(const TType& T, const RhoType& rhomolar, const MoleFracsType& molefracs, const XType& X_init) const {
371
372 if (X_init.size() != static_cast<long>(mapper.to_siteid.size())){
373 throw teqp::InvalidArgument("Wrong size of X_init; should be "+ std::to_string(mapper.to_siteid.size()));
374 }
375
376 using resulttype = std::common_type_t<decltype(T), decltype(rhomolar), decltype(molefracs[0])>; // Type promotion, without the const-ness
377 using Mat = Eigen::Array<resulttype, Eigen::Dynamic, Eigen::Dynamic>;
378
379 const Mat Delta = get_Delta(T, rhomolar, molefracs);
380// const Mat DD = Delta.array()*D.cast<resulttype>().array(); // coefficient-wise product, with D upcast from int to type of Delta matrix
381
382 auto Ngroups = mapper.to_CompSite.size();
383 Eigen::RowVectorX<typename MoleFracsType::Scalar> xj(Ngroups); // Mole fractions of the component containing each siteid
384 for (auto I = 0U; I< Ngroups; ++I){
385 xj(I) = molefracs(std::get<0>(mapper.to_CompSite.at(I)));
386 }
387
388 using rDDXtype = std::decay_t<std::common_type_t<typename decltype(Delta)::Scalar, decltype(rhomolar), decltype(molefracs[0])>>; // Type promotion, without the const-ness
389 Eigen::ArrayX<std::decay_t<rDDXtype>> X = X_init.template cast<rDDXtype>(), Xnew;
390
391 Eigen::MatrixX<rDDXtype> rDDX = rhomolar*N_A*(Delta.array()*D.cast<resulttype>().array()).matrix();
392 for (auto j = 0; j < rDDX.rows(); ++j){
393 rDDX.row(j).array() = rDDX.row(j).array()*xj.array().template cast<rDDXtype>();
394 }
395// rDDX.rowwise() *= xj;
396
397 // Use explicit solutions in the case that there is a pure
398 // fluid with two kinds of sites, and no self-self interactions
399 // between sites
400 if (options.allow_explicit_fractions && molefracs.size() == 1 && mapper.counts.size() == 2 && (rDDX.matrix().diagonal().unaryExpr([](const auto&x){return getbaseval(x); }).array() == 0.0).all()){
401 auto Delta_ = Delta(0, 1);
402 auto kappa_A = rhomolar*N_A*static_cast<double>(mapper.counts[0])*Delta_;
403 auto kappa_B = rhomolar*N_A*static_cast<double>(mapper.counts[1])*Delta_;
404 // See the derivation in the docs in the association page; see also https://github.com/ClapeyronThermo/Clapeyron.jl/blob/494a75e8a2093a4b48ca54b872ff77428a780bb6/src/models/SAFT/association.jl#L463
405 auto X_A1 = (kappa_A-kappa_B-sqrt(kappa_A*kappa_A-2.0*kappa_A*kappa_B + 2.0*kappa_A + kappa_B*kappa_B + 2.0*kappa_B+1.0)-1.0)/(2.0*kappa_A);
406 auto X_A2 = (kappa_A-kappa_B+sqrt(kappa_A*kappa_A-2.0*kappa_A*kappa_B + 2.0*kappa_A + kappa_B*kappa_B + 2.0*kappa_B+1.0)-1.0)/(2.0*kappa_A);
407 // Keep the positive solution, likely to be X_A2
408 if (getbaseval(X_A1) < 0 && getbaseval(X_A2) > 0){
409 X(0) = X_A2;
410 }
411 else if (getbaseval(X_A1) > 0 && getbaseval(X_A2) < 0){
412 X(0) = X_A1;
413 }
414 auto X_B = 1.0/(1.0+kappa_A*X(0)); // From the law of mass-action
415 X(1) = X_B;
416 return X;
417 }
418
419 for (auto counter = 0; counter < options.max_iters; ++counter){
420 // calculate the new array of non-bonded site fractions X
421 Xnew = options.alpha*X + (1.0-options.alpha)/(1.0+(rDDX*X.matrix()).array());
422 // These unaryExpr extract the numerical value from an Eigen array of generic type, allowing for comparison.
423 // Otherwise for instance it is imposible to compare two complex numbers (if you are using complex step derivatives)
424 auto diff = (Xnew-X).eval().cwiseAbs().unaryExpr([](const auto&x){return getbaseval(x); }).eval();
425 auto tol = (options.rtol*Xnew + options.atol).unaryExpr([](const auto&x){return getbaseval(x); }).eval();
426 if ((diff < tol).all()){
427 break;
428 }
429 X = Xnew;
430 }
431 return X;
432 }
433
437 template<typename TType, typename RhoType, typename MoleFracsType>
438 auto alphar(const TType& T, const RhoType& rhomolar, const MoleFracsType& molefracs) const {
439 if (molefracs.size() != mapper.N_sites.size()){
440 throw teqp::InvalidArgument("Wrong size of molefracs; should be "+ std::to_string(mapper.N_sites.size()));
441 }
442
443 // Do the sucessive substitution to obtain the non-bonded fractions for each unique site
444 Eigen::ArrayXd X_init = Eigen::ArrayXd::Ones(mapper.to_siteid.size());
445 auto X_A = successive_substitution(T, rhomolar, molefracs, X_init);
446
447 // Calculate the contribution alpha based on the "naive" summation like in Clapeyron
448 using resulttype = std::common_type_t<decltype(T), decltype(rhomolar), decltype(molefracs[0])>; // Type promotion, without the const-ness
449 resulttype alpha_r_asso = 0.0;
450 for (auto icomp = 0; icomp < molefracs.size(); ++icomp){ // loop over all components
451 resulttype summer = 0.0;
452 for (auto jsite = 0; jsite < mapper.N_unique_sites[icomp]; ++jsite){
453 // Get the siteid given the (component index, site index on the component)
454 const auto I = mapper.CompCIndex_to_siteid.at({icomp, jsite});
455
456 // each contribution (per unique site on a molecule) is multiplied by its multiplicity within the molecule
457 summer += (log(X_A(I))- X_A(I)/2.0 + 0.5)*static_cast<double>(mapper.counts(I));
458 }
459 alpha_r_asso += molefracs[icomp]*summer;
460 }
461 return alpha_r_asso;
462
463// // And do the summation to calculate the contribution to alpha=a/(RT)
464// using V = std::common_type_t<double, decltype(X_A[0]), int>;
465// auto xj = molefracs(mapper.comp_index); // mole fractions of the components attached at each site
466// auto alpha_r_asso = (xj.template cast<V>()*(log(X_A) - X_A/2.0 + 0.5).template cast<V>()*mapper.counts.cast<double>()).sum();
467// return forceeval(alpha_r_asso);
468 }
469
473 nlohmann::json get_assoc_calcs(double T, double rhomolar, const Eigen::ArrayXd& mole_fractions) const{
474
475 using Mat = Eigen::Array<double, Eigen::Dynamic, Eigen::Dynamic>;
476 const Mat Delta = get_Delta(T, rhomolar, mole_fractions);
477
478 Eigen::ArrayXd XAinit = Eigen::ArrayXd::Ones(mapper.to_siteid.size());
479 auto XA = successive_substitution(T, rhomolar, mole_fractions, XAinit);
480
481 auto fromArrayXd = [](const Eigen::ArrayXd &x){std::valarray<double>n(x.size()); for (auto i = 0U; i < n.size(); ++i){ n[i] = x[i];} return n;};
482 auto fromArrayXXd = [](const Eigen::ArrayXXd &x){
483 std::size_t N = x.rows();
484 std::vector<std::vector<double>> n; n.resize(N);
485 for (auto i = 0U; i < N; ++i){
486 n[i].resize(N);
487 for (auto j = 0U; j < N; ++j){
488 n[i][j] = x(i,j);
489 }
490 }
491 return n;
492 };
493 auto fromArrayXXi = [](const Eigen::ArrayXXi &x){
494 std::size_t N = x.rows();
495 std::vector<std::vector<int>> n; n.resize(N);
496 for (auto i = 0U; i < N; ++i){
497 n[i].resize(N);
498 for (auto j = 0U; j < N; ++j){
499 n[i][j] = x(i,j);
500 }
501 }
502 return n;
503 };
504 return {
505 {"to_CompSite", mapper.to_CompSite},
506 {"to_siteid", mapper.to_siteid},
507 {"counts", mapper.counts},
508 {"D", fromArrayXXi(D.array())},
509 {"Delta", fromArrayXXd(Delta.array())},
510 {"X_A", fromArrayXd(XA.array())},
511 {"self_association_mask", options.self_association_mask},
512 {"note", "X_A is the fraction of non-bonded sites for each siteid"}
513 };
514 }
515
516};
517
518}
519}
nlohmann::json get_assoc_calcs(double T, double rhomolar, const Eigen::ArrayXd &mole_fractions) const
Get things from the association calculations for debug purposes.
std::tuple< std::size_t, std::size_t > CompCIndex
auto get_Delta(const TType &T, const RhoType &rhomolar, const MoleFracsType &molefracs) const
const std::variant< CanonicalData, DufalData > datasidecar
static Association factory(const nlohmann::json &j)
Association(const decltype(datasidecar)&data, const std::vector< std::vector< std::string > > &molecule_sites, const AssociationOptions &options)
std::tuple< std::size_t, std::string > CompSite
Association(const Eigen::ArrayXd &b_m3mol, const Eigen::ArrayXd &beta, const Eigen::ArrayXd &epsilon_Jmol, const std::vector< std::vector< std::string > > &molecule_sites, const AssociationOptions &options)
auto successive_substitution(const TType &T, const RhoType &rhomolar, const MoleFracsType &molefracs, const XType &X_init) const
const AssociationOptions options
auto alphar(const TType &T, const RhoType &rhomolar, const MoleFracsType &molefracs) const
Calculate the contribution , where the Helmholtz energy is on a molar basis, making dimensionless.
#define X(f)
const std::unordered_map< int, Eigen::MatrixXd > bcoeffs
void from_json(const nlohmann::json &j, AssociationOptions &o)
const double R_CODATA2017
molar gas constant from CODATA 2017: https://doi.org/10.1103/RevModPhys.84.1527
Definition constants.hpp:10
auto build_square_matrix
auto POW3(const A &x)
auto getbaseval(const T &expr)
Definition types.hpp:90
auto forceeval(T &&expr)
Definition types.hpp:52
association::radial_dists radial_dist
std::map< std::string, std::vector< std::string > > interaction_partners
std::vector< std::string > site_order
std::vector< bool > self_association_mask
association::Delta_rules Delta_rule
Eigen::ArrayXi N_unique_sites
How many unique types of sites are on each molecule.
Eigen::ArrayXi comp_index
The pure component indices associated with each siteid.
std::map< CompSite, std::size_t > to_siteid
Eigen::ArrayXi N_sites
How many total sites are on each molecule.
std::map< CompCIndex, std::size_t > CompCIndex_to_siteid
std::map< std::size_t, CompSite > to_CompSite
std::vector< std::vector< std::string > > molecule_sites
Eigen::ArrayXi counts
An array of counts of each siteid for the entire mixture.
Eigen::ArrayXd b_m3mol
The covolume b, in m^3/mol, one per component.
Eigen::ArrayXd epsilon_Jmol
The association energy of each molecule, in J/mol, one per component.
Eigen::ArrayXd beta
The volume factor, dimensionless, one per component.
Eigen::ArrayXXd kmat
Matrix of k_{ij} values.