From c532558861d6cdcbc6783ac5d713f200db6a926b Mon Sep 17 00:00:00 2001
From: Ian Bell <ian.bell@nist.gov>
Date: Fri, 7 May 2021 10:29:13 -0400
Subject: [PATCH] Add factory function for CPA

---
 include/teqp/models/CPA.hpp | 69 +++++++++++++++++++++++++++++++++----
 src/tests/catch_tests.cxx   | 30 +++++++++++++---
 2 files changed, 88 insertions(+), 11 deletions(-)

diff --git a/include/teqp/models/CPA.hpp b/include/teqp/models/CPA.hpp
index 84fadc5..e9cccfe 100644
--- a/include/teqp/models/CPA.hpp
+++ b/include/teqp/models/CPA.hpp
@@ -1,9 +1,23 @@
 #pragma once
 
+namespace CPA {
+
 template<typename X> auto POW2(X x) { return x * x; };
 template<typename X> auto POW3(X x) { return x * POW2(x); };
 
 enum class association_classes {not_set, a1A, a2B, a3B, a4C, not_associating};
+
+auto get_association_classes(const std::string& s) {
+    if (s == "1A") { return association_classes::a1A; }
+    else if (s == "2B") { return association_classes::a2B; }
+    else if (s == "2B") { return association_classes::a2B; }
+    else if (s == "3B") { return association_classes::a3B; }
+    else if (s == "4C") { return association_classes::a4C; }
+    else {
+        throw std::invalid_argument("bad association flag:" + s);
+    }
+}
+
 enum class radial_dist { CS, KG, OT };
 
 /// Function that calculates the association binding strength between site A of molecule i and site B on molecule j
@@ -97,6 +111,13 @@ auto XA_calc_pure(int N_sites, association_classes scheme, double epsABi, double
 };
 
 enum class cubic_flag {not_set, PR, SRK};
+auto get_cubic_flag(const std::string& s) {
+    if (s == "PR") { return cubic_flag::PR; }
+    else if (s == "SRK") { return cubic_flag::SRK; }
+    else {
+        throw std::invalid_argument("bad cubic flag:" + s);
+    }
+}
 
 class CPACubic {
 private:
@@ -106,7 +127,7 @@ private:
     std::valarray<std::valarray<double>> k_ij;
 
 public:
-    CPACubic(cubic_flag flag, const std::valarray<double> a0, const std::valarray<double> bi, const std::valarray<double> c1, const std::valarray<double> Tc) : a0(a0), bi(bi), c1(c1), Tc(Tc) {
+    CPACubic(cubic_flag flag, const std::valarray<double> &a0, const std::valarray<double> &bi, const std::valarray<double> &c1, const std::valarray<double> &Tc) : a0(a0), bi(bi), c1(c1), Tc(Tc) {
         switch (flag) {
         case cubic_flag::PR:
         { delta_1 = 1 + sqrt(2); delta_2 = 1 - sqrt(2); break; }
@@ -150,7 +171,7 @@ public:
 template<typename Cubic>
 class CPAAssociation {
 private:
-    const Cubic& cubic;
+    const Cubic cubic;
     const std::vector<association_classes> classes;
     const std::vector<int> N_sites;
     const std::valarray<double> epsABi, betaABi;
@@ -173,7 +194,7 @@ private:
     }
 
 public:
-    CPAAssociation(const Cubic &cubic, const std::vector<association_classes>& classes, const std::valarray<double> &epsABi, const std::valarray<double> &betaABi) 
+    CPAAssociation(const Cubic &&cubic, const std::vector<association_classes>& classes, const std::valarray<double> &epsABi, const std::valarray<double> &betaABi) 
         : cubic(cubic), classes(classes), epsABi(epsABi), betaABi(betaABi), N_sites(get_N_sites(classes)) {};
 
     template<typename TType, typename RhoType, typename VecType>
@@ -200,10 +221,10 @@ public:
 template <typename Cubic, typename Assoc>
 class CPA {
 public:
-    Cubic cubic;
-    Assoc assoc;
+    const Cubic cubic;
+    const Assoc assoc;
 
-    CPA(Cubic cubic, Assoc assoc) : cubic(cubic), assoc(assoc) {
+    CPA(Cubic &&cubic, Assoc &&assoc) : cubic(cubic), assoc(assoc) {
     }
 
     /// Residual dimensionless Helmholtz energy from the SRK or PR core and contribution due to association
@@ -219,4 +240,38 @@ public:
 
         return forceeval(alpha_r_cubic + alpha_r_assoc);
     }
-};
\ No newline at end of file
+};
+
+/// A factory function to return an instantiated CPA instance given
+/// the JSON representation of the model
+auto CPAfactory(const nlohmann::json &j){
+    auto build_cubic = [](const auto& j) {
+        auto N = j["pures"].size();
+        std::valarray<double> a0i(N), bi(N), c1(N), Tc(N);
+        std::size_t i = 0;
+        for (auto p : j["pures"]) {
+            a0i[i] = p["a0i / Pa m^6/mol^2"];
+            bi[i] = p["bi / m^3/mol"];
+            c1[i] = p["c1"];
+            Tc[i] = p["Tc / K"];
+            i++;
+        }
+        return CPACubic(get_cubic_flag(j["cubic"]), a0i, bi, c1, Tc);
+    };
+	auto build_assoc = [](const auto &&cubic, const auto& j) {
+        auto N = j["pures"].size();
+        std::vector<association_classes> classes;
+        std::valarray<double> epsABi(N), betaABi(N);
+        std::size_t i = 0;
+        for (auto p : j["pures"]) {
+            epsABi[i] = p["epsABi / J/mol"];
+            betaABi[i] = p["betaABi"];
+            classes.push_back(get_association_classes(p["class"]));
+            i++;
+        }
+        return CPAAssociation(std::move(cubic), classes, epsABi, betaABi);
+    };
+	return CPA(build_cubic(j), build_assoc(build_cubic(j), j));
+}
+
+}; /* namespace CPA */
\ No newline at end of file
diff --git a/src/tests/catch_tests.cxx b/src/tests/catch_tests.cxx
index efb6e0f..87603ee 100644
--- a/src/tests/catch_tests.cxx
+++ b/src/tests/catch_tests.cxx
@@ -311,7 +311,7 @@ TEST_CASE("Test pure VLE with non-unity R0/Rr", "") {
 TEST_CASE("Test water", "") {
     std::valarray<double> a0 = {0.12277}, bi = {0.000014515}, c1 = {0.67359}, Tc = {647.096}, 
                           molefrac = {1.0};
-    CPACubic cub(cubic_flag::SRK, a0, bi, c1, Tc);
+    CPA::CPACubic cub(CPA::cubic_flag::SRK, a0, bi, c1, Tc);
     double T = 400, rhomolar = 100;
     
     auto R = 8.3144598;
@@ -322,15 +322,37 @@ TEST_CASE("Test water", "") {
     double p_noassoc = T*rhomolar*R*(1+tdx::get_Ar01(cub, T, rhomolar, z));
     CAPTURE(p_noassoc);
 
-    std::vector<association_classes> schemes = { association_classes::a4C };
+    std::vector<CPA::association_classes> schemes = { CPA::association_classes::a4C };
     std::valarray<double> epsAB = { 16655 }, betaAB = { 0.0692 };
-    CPAAssociation cpaa(cub, schemes, epsAB, betaAB);
+    CPA::CPAAssociation cpaa(std::move(cub), schemes, epsAB, betaAB);
 
-    CPA cpa(cub, cpaa);
+    CPA::CPA cpa(std::move(cub), std::move(cpaa));
     using tdc = TDXDerivatives<decltype(cpa)>;
     auto Ar01 = tdc::get_Ar01(cpa, T, rhomolar, z);
     double p_withassoc = T*rhomolar*R*(1 + Ar01);
     CAPTURE(p_withassoc);
 
     REQUIRE(p_withassoc == 3.14);
+}
+
+TEST_CASE("Test water w/ factory", "") {
+   using namespace CPA;
+   nlohmann::json water = {
+        {"a0i / Pa m^6/mol^2",0.12277 }, {"bi / m^3/mol", 0.000014515}, {"c1", 0.67359}, {"Tc / K", 647.096},
+        {"epsABi / J/mol", 16655.0}, {"betaABi",0.0692}, {"class","4C"}
+   };
+   nlohmann::json j = {
+       {"cubic","SRK"},
+       {"pures", {water}}
+   };
+   auto cpa = CPAfactory(j);
+
+   double T = 400, rhomolar = 100, R = 8.3144598;
+   auto z = (Eigen::ArrayXd(1) << 1).finished();
+   using tdc = TDXDerivatives<decltype(cpa)>;
+   auto Ar01 = tdc::get_Ar01(cpa, T, rhomolar, z);
+   double p_withassoc = T * rhomolar * R * (1 + Ar01);
+   CAPTURE(p_withassoc);
+
+   REQUIRE(p_withassoc == 3.14);
 }
\ No newline at end of file
-- 
GitLab