9#include "nlohmann/json.hpp"
20namespace GrossSadowski2001{
21 extern const Eigen::Array<double, 3, 7>
a,
b;
23namespace LiangIECR2012{
24 extern const Eigen::Array<double, 3, 7>
a,
b;
26namespace LiangIECR2014{
27 extern const Eigen::Array<double, 3, 7>
a,
b;
50 std::map<std::string, SAFTCoeffs> coeffs;
57 void insert_normal_fluid(
const std::string& name,
double m,
const double sigma_Angstrom,
const double epsilon_over_k,
const std::string& BibTeXKey) {
64 coeffs.insert(std::pair<std::string, SAFTCoeffs>(name, coeff));
67 auto it = coeffs.find(name);
68 if (it != coeffs.end()) {
72 throw std::invalid_argument(
"Bad name:" + name);
76 std::vector<SAFTCoeffs> c;
87template <
typename Eta,
typename Mbar>
88auto C1(
const Eta& eta,
const Mbar& mbar) {
89 auto oneeta = 1.0 - eta;
91 + mbar * (8.0 * eta - 2.0 * eta * eta) / (oneeta*oneeta*oneeta*oneeta)
92 + (1.0 - mbar) * (20.0 * eta - 27.0 * eta * eta + 12.0 * eta*eta*eta - 2.0 * eta*eta*eta*eta) / ((1.0 - eta) * (2.0 - eta)*(1.0 - eta) * (2.0 - eta))));
95template <
typename Eta,
typename Mbar>
96auto C2(
const Eta& eta,
const Mbar& mbar) {
98 mbar * (-4.0 * eta * eta + 20.0 * eta + 8.0) /
pow(1.0 - eta, 5)
99 + (1.0 - mbar) * (2.0 * eta * eta * eta + 12.0 * eta * eta - 48.0 * eta + 40.0) /
pow((1.0 - eta) * (2.0 - eta), 3)
104template<
typename VecType,
typename VecType2>
133 auto Upsilon = 1.0 - zeta[3];
136 3.0*D[1]/D[0]*zeta[2]/Upsilon
137 + D[2]*D[2]*zeta[2]/(D[3]*D[0]*Upsilon*Upsilon)
139 + (D[2]*D[2]*D[2])/(D[3]*D[3]*D[0])*log(Upsilon)
143 return forceeval(1.0 / zeta[0] * (3.0 * zeta[1] * zeta[2] / Upsilon
144 + zeta[2] * zeta[2] * zeta[2] / zeta[3] / Upsilon / Upsilon
145 + (zeta[2] * zeta[2] * zeta[2] / (zeta[3] * zeta[3]) - zeta[0]) * log(1.0 - zeta[3])
150template<
typename zVecType,
typename dVecType>
151auto gij_HS(
const zVecType& zeta,
const dVecType& d,
152 std::size_t i, std::size_t j) {
153 auto Upsilon = 1.0 - zeta[3];
154#if defined(PCSAFTDEBUG)
156 auto term2 =
forceeval(d[i] * d[j] / (d[i] + d[j]) * 3.0 * zeta[2] /
pow(Upsilon, 2));
157 auto term3 =
forceeval(
pow(d[i] * d[j] / (d[i] + d[j]), 2) * 2.0 * zeta[2]*zeta[2] /
pow(Upsilon, 3));
159 return forceeval(1.0 / (Upsilon)+d[i] * d[j] / (d[i] + d[j]) * 3.0 * zeta[2] /
pow(Upsilon, 2)
160 +
pow(d[i] * d[j] / (d[i] + d[j]), 2) * 2.0 * zeta[2]*zeta[2] /
pow(Upsilon, 3));
166template<
typename VecType1,
typename NType>
167auto powvec(
const VecType1& v1, NType n) {
169 for (
auto i = 0; i < v1.size(); ++i) {
170 o[i] =
pow(v1[i], n);
178template<
typename VecType1,
typename VecType2,
typename VecType3>
179auto sumproduct(
const VecType1& v1,
const VecType2& v2,
const VecType3& v3) {
180 using ResultType =
typename std::common_type_t<
decltype(v1[0]),
decltype(v2[0]),
decltype(v3[0])>;
181 return forceeval((v1.template cast<ResultType>().array() * v2.template cast<ResultType>().array() * v3.template cast<ResultType>().array()).sum());
190 const Eigen::ArrayX<double>
m,
195 Eigen::Array<double, 3, 7>
a,
199 PCSAFTHardChainContribution(
const Eigen::ArrayX<double> &
m,
const Eigen::ArrayX<double> &
mminus1,
const Eigen::ArrayX<double> &
sigma_Angstrom,
const Eigen::ArrayX<double> &
epsilon_over_k,
const Eigen::ArrayXXd &
kmat,
const Eigen::Array<double, 3, 7>&
a,
const Eigen::Array<double, 3,7>&
b)
204 template<
typename TTYPE,
typename RhoType,
typename VecType>
205 auto eval(
const TTYPE& T,
const RhoType& rhomolar,
const VecType& mole_fractions)
const {
207 Eigen::Index N =
m.size();
209 if (mole_fractions.size() != N) {
210 throw std::invalid_argument(
"Length of mole_fractions (" + std::to_string(mole_fractions.size()) +
") is not the length of components (" + std::to_string(N) +
")");
213 using TRHOType = std::common_type_t<std::decay_t<TTYPE>, std::decay_t<RhoType>, std::decay_t<
decltype(mole_fractions[0])>, std::decay_t<
decltype(
m[0])>>;
215 Eigen::ArrayX<TTYPE> d(N);
216 TRHOType m2_epsilon_sigma3_bar = 0.0;
217 TRHOType m2_epsilon2_sigma3_bar = 0.0;
218 for (
auto i = 0L; i < N; ++i) {
220 for (
auto j = 0; j < N; ++j) {
224 auto sigmaij3 = sigma_ij*sigma_ij*sigma_ij;
225 auto ekT = eij_over_k/T;
226 m2_epsilon_sigma3_bar += mole_fractions[i] * mole_fractions[j] *
m[i] *
m[j] * ekT * sigmaij3;
227 m2_epsilon2_sigma3_bar += mole_fractions[i] * mole_fractions[j] *
m[i] *
m[j] * (ekT*ekT) * sigmaij3;
230 auto mbar = (mole_fractions.template cast<TRHOType>().array()*
m.template cast<TRHOType>().array()).sum();
233 RhoType rho_A3 = rhomolar * N_A * 1e-30;
235 constexpr double MY_PI = EIGEN_PI;
236 double pi6 = (MY_PI / 6.0);
239 using ta = std::common_type_t<
decltype(
m[0]),
decltype(d[0]),
decltype(rho_A3)>;
240 std::vector<ta> zeta(4), D(4);
241 for (std::size_t n = 0; n < 4; ++n) {
243 auto dn =
pow(d,
static_cast<int>(n));
244 TRHOType xmdn =
forceeval((mole_fractions.template cast<TRHOType>().array()*
m.template cast<TRHOType>().array()*dn.template cast<TRHOType>().array()).sum());
252 Eigen::Array<
decltype(eta), 7, 1> etapowers; etapowers(0) = 1.0;
for (
auto i = 1U; i <= 6; ++i){ etapowers(i) = eta*etapowers(i-1); }
253 using TYPE = TRHOType;
254 Eigen::Array<TYPE, 7, 1> abar = (
a.row(0).cast<TYPE>().array() + ((mbar - 1.0) / mbar) *
a.row(1).cast<TYPE>().array() + ((mbar - 1.0) / mbar) * ((mbar - 2.0) / mbar) *
a.row(2).cast<TYPE>().array()).
eval();
255 Eigen::Array<TYPE, 7, 1> bbar = (
b.row(0).cast<TYPE>().array() + ((mbar - 1.0) / mbar) *
b.row(1).cast<TYPE>().array() + ((mbar - 1.0) / mbar) * ((mbar - 2.0) / mbar) *
b.row(2).cast<TYPE>().array()).
eval();
256 auto I1 = (abar.array().template cast<decltype(eta)>()*etapowers).sum();
257 auto I2 = (bbar.array().template cast<decltype(eta)>()*etapowers).sum();
260 using tt = std::common_type_t<
decltype(zeta[0]),
decltype(d[0])>;
261 Eigen::ArrayX<tt> lngii_hs(mole_fractions.size());
262 for (
auto i = 0; i < lngii_hs.size(); ++i) {
263 lngii_hs[i] = log(
gij_HS(zeta, d, i, i));
268 auto C1_ =
C1(eta, mbar);
269 auto alphar_disp =
forceeval(-2 * MY_PI * rho_A3 * I1 * m2_epsilon_sigma3_bar - MY_PI * rho_A3 * mbar * C1_ * I2 * m2_epsilon2_sigma3_bar);
271#if defined(PCSAFTDEBUG)
273 throw teqp::InvalidValue(
"An invalid value was obtained for alphar_hc; please investigate");
285 throw teqp::InvalidValue(
"An invalid value was obtained for alphar_disp; please investigate");
288 struct PCSAFTHardChainContributionTerms{
291 TRHOType alphar_disp;
293 return PCSAFTHardChainContributionTerms{eta, alphar_hc, alphar_disp};
308 Eigen::ArrayX<double>
m,
316 std::optional<PCSAFTDipolarContribution>
dipolar;
323 if (
kmat.cols() == 0) {
326 else if (
kmat.cols() != N) {
327 throw teqp::InvalidArgument(
"kmat needs to be a square matrix the same size as the number of components");
334 auto build_hardchain(
const std::vector<SAFTCoeffs> &coeffs,
const Eigen::Array<double, 3, 7>& a,
const Eigen::Array<double, 3, 7>& b){
337 m.resize(coeffs.size());
341 names.resize(coeffs.size());
342 bibtex.resize(coeffs.size());
344 for (
const auto &coeff : coeffs) {
349 names[i] = coeff.name;
350 bibtex[i] = coeff.BibTeXKey;
356 std::vector<std::string> names_;
357 for (
const auto& c: coeffs){
358 names_.push_back(c.name);
362 auto build_dipolar(
const std::vector<SAFTCoeffs> &coeffs) -> std::optional<PCSAFTDipolarContribution>{
363 Eigen::ArrayXd mustar2(coeffs.size()), nmu(coeffs.size());
365 for (
const auto &coeff : coeffs) {
366 mustar2[i] = coeff.mustar2;
370 if ((mustar2*nmu).cwiseAbs().sum() == 0){
376 auto build_quadrupolar(
const std::vector<SAFTCoeffs> &coeffs) -> std::optional<PCSAFTQuadrupolarContribution>{
378 Eigen::ArrayXd Qstar2(coeffs.size()), nQ(coeffs.size());
380 for (
const auto &coeff : coeffs) {
381 Qstar2[i] = coeff.Qstar2;
385 if ((Qstar2*nQ).cwiseAbs().sum() == 0){
411 std::string s = std::string(
"i m sigma / A e/kB / K \n ++++++++++++++") +
"\n";
412 for (
auto i = 0; i <
m.size(); ++i) {
413 s += std::to_string(i) +
" " + std::to_string(
m[i]) +
" " + std::to_string(
sigma_Angstrom[i]) +
" " + std::to_string(
epsilon_over_k[i]) +
"\n";
418 template<
typename VecType>
419 double max_rhoN(
const double T,
const VecType& mole_fractions)
const {
420 auto N = mole_fractions.size();
421 Eigen::ArrayX<double> d(N);
422 for (
auto i = 0; i < N; ++i) {
425 return 6 * 0.74 / EIGEN_PI / (mole_fractions*
m*
powvec(d, 3)).sum()*1e30;
428 template<
class VecType>
429 auto R(
const VecType& molefrac)
const {
433 template<
typename TTYPE,
typename RhoType,
typename VecType>
434 auto alphar(
const TTYPE& T,
const RhoType& rhomolar,
const VecType& mole_fractions)
const {
439 auto rho_A3 =
forceeval(rhomolar*N_A*1e-30);
442 auto valsdip =
dipolar.value().eval(T, rho_A3, vals.eta, mole_fractions);
447 auto valsquad =
quadrupolar.value().eval(T, rho_A3, vals.eta, mole_fractions);
456 std::optional<Eigen::ArrayXXd> kmat;
457 if (spec.contains(
"kmat") && spec.at(
"kmat").is_array() && spec.at(
"kmat").size() > 0){
464 if (spec.contains(
"ab")){
465 std::string source = spec.at(
"ab");
466 if (source ==
"Liang-IECR-2012"){
470 else if (source ==
"Liang-IECR-2014"){
479 if (spec.contains(
"names")){
480 std::vector<std::string> names = spec[
"names"];
481 if (kmat &&
static_cast<std::size_t
>(kmat.value().rows()) != names.size()){
482 throw teqp::InvalidArgument(
"Provided length of names of " + std::to_string(names.size()) +
" does not match the dimension of the kmat of " + std::to_string(kmat.value().rows()));
484 return PCSAFTMixture(names, a, b, kmat.value_or(Eigen::ArrayXXd{}));
486 else if (spec.contains(
"coeffs")){
487 std::vector<SAFTCoeffs> coeffs;
488 for (
auto j : spec[
"coeffs"]) {
490 c.
name = j.at(
"name");
495 if (j.contains(
"(mu^*)^2") && j.contains(
"nmu")){
499 if (j.contains(
"(Q^*)^2") && j.contains(
"nQ")){
500 c.
Qstar2 = j.at(
"(Q^*)^2");
505 if (kmat &&
static_cast<std::size_t
>(kmat.value().rows()) != coeffs.size()){
506 throw teqp::InvalidArgument(
"Provided length of coeffs of " + std::to_string(coeffs.size()) +
" does not match the dimension of the kmat of " + std::to_string(kmat.value().rows()));
508 return PCSAFTMixture(coeffs, a, b, kmat.value_or(Eigen::ArrayXXd{}));
511 throw std::invalid_argument(
"you must provide names or coeffs, but not both");
const Eigen::ArrayX< double > sigma_Angstrom
auto eval(const TTYPE &T, const RhoType &rhomolar, const VecType &mole_fractions) const
const Eigen::ArrayXXd kmat
binary interaction parameter matrix
const Eigen::ArrayX< double > epsilon_over_k
depth of pair potential divided by Boltzman constant
PCSAFTHardChainContribution & operator=(const PCSAFTHardChainContribution &)=delete
PCSAFTHardChainContribution(const Eigen::ArrayX< double > &m, const Eigen::ArrayX< double > &mminus1, const Eigen::ArrayX< double > &sigma_Angstrom, const Eigen::ArrayX< double > &epsilon_over_k, const Eigen::ArrayXXd &kmat, const Eigen::Array< double, 3, 7 > &a, const Eigen::Array< double, 3, 7 > &b)
Eigen::Array< double, 3, 7 > b
The universal constants used in Eqn. A.19 of G&S.
Eigen::Array< double, 3, 7 > a
The universal constants used in Eqn. A.18 of G&S.
const Eigen::ArrayX< double > m
number of segments
const Eigen::ArrayX< double > mminus1
m-1
Manager class for PCSAFT coefficients.
auto get_coeffs(const std::vector< std::string > &names)
void insert_normal_fluid(const std::string &name, double m, const double sigma_Angstrom, const double epsilon_over_k, const std::string &BibTeXKey)
const auto & get_normal_fluid(const std::string &name)
auto build_hardchain(const std::vector< SAFTCoeffs > &coeffs, const Eigen::Array< double, 3, 7 > &a, const Eigen::Array< double, 3, 7 > &b)
std::vector< std::string > names
auto get_coeffs_from_names(const std::vector< std::string > &the_names)
std::optional< PCSAFTQuadrupolarContribution > quadrupolar
PCSAFTMixture(const std::vector< std::string > &names, const Eigen::Array< double, 3, 7 > &a=teqp::saft::PCSAFT::PCSAFTMatrices::GrossSadowski2001::a, const Eigen::Array< double, 3, 7 > &b=teqp::saft::PCSAFT::PCSAFTMatrices::GrossSadowski2001::b, const Eigen::ArrayXXd &kmat={})
Eigen::ArrayX< double > epsilon_over_k
depth of pair potential divided by Boltzman constant
std::optional< PCSAFTDipolarContribution > dipolar
Eigen::ArrayX< double > m
number of segments
Eigen::ArrayXXd kmat
binary interaction parameter matrix
auto build_quadrupolar(const std::vector< SAFTCoeffs > &coeffs) -> std::optional< PCSAFTQuadrupolarContribution >
Eigen::ArrayX< double > sigma_Angstrom
auto get_epsilon_over_k_K() const
auto alphar(const TTYPE &T, const RhoType &rhomolar, const VecType &mole_fractions) const
PCSAFTMixture(const std::vector< SAFTCoeffs > &coeffs, const Eigen::Array< double, 3, 7 > &a=teqp::saft::PCSAFT::PCSAFTMatrices::GrossSadowski2001::a, const Eigen::Array< double, 3, 7 > &b=teqp::saft::PCSAFT::PCSAFTMatrices::GrossSadowski2001::b, const Eigen::ArrayXXd &kmat={})
auto get_BibTeXKeys() const
double max_rhoN(const double T, const VecType &mole_fractions) const
Eigen::ArrayX< double > mminus1
m-1
auto build_dipolar(const std::vector< SAFTCoeffs > &coeffs) -> std::optional< PCSAFTDipolarContribution >
PCSAFTHardChainContribution hardchain
teqp::saft::polar_terms::GrossVrabec::DipolarContributionGrossVrabec PCSAFTDipolarContribution
std::vector< std::string > bibtex
auto R(const VecType &molefrac) const
auto get_sigma_Angstrom() const
teqp::saft::polar_terms::GrossVrabec::QuadrupolarContributionGross PCSAFTQuadrupolarContribution
PCSAFTMixture & operator=(const PCSAFTMixture &)=delete
auto extract_names(const std::vector< SAFTCoeffs > &coeffs)
void check_kmat(Eigen::Index N)
const Eigen::Array< double, 3, 7 > a
const Eigen::Array< double, 3, 7 > b
const Eigen::Array< double, 3, 7 > a
const Eigen::Array< double, 3, 7 > b
const Eigen::Array< double, 3, 7 > a
const Eigen::Array< double, 3, 7 > b
auto PCSAFTfactory(const nlohmann::json &spec)
A JSON-based factory function for the PC-SAFT model.
auto sumproduct(const VecType1 &v1, const VecType2 &v2, const VecType3 &v3)
auto powvec(const VecType1 &v1, NType n)
auto get_alphar_hs(const VecType &zeta, const VecType2 &D)
Residual contribution to alphar from hard-sphere (Eqn. A.6)
auto C1(const Eta &eta, const Mbar &mbar)
auto gij_HS(const zVecType &zeta, const dVecType &d, std::size_t i, std::size_t j)
Term from Eqn. A.7.
auto C2(const Eta &eta, const Mbar &mbar)
Eqn. A.31.
auto getbaseval(const T &expr)
auto pow(const double &x, const double &e)
auto get_R_gas()
< Gas constant, according to CODATA 2019, in the given number type
Coefficients for one fluid.
double nQ
number of quadrupolar segments
double sigma_Angstrom
[A] segment diameter
double Qstar2
nondimensional, the reduced quadrupole squared
double nmu
number of dipolar segments
double mustar2
nondimensional, the reduced dipole moment squared
double m
number of segments
std::string BibTeXKey
The BibTeXKey for the reference for these coefficients.
double epsilon_over_k
[K] depth of pair potential divided by Boltzman constant
std::string name
Name of fluid.