From 71e74225b2103d80d3fc9050dca4cd64a80ba2bb Mon Sep 17 00:00:00 2001
From: Ian Bell <ian.bell@nist.gov>
Date: Sun, 31 Jul 2022 15:38:36 -0400
Subject: [PATCH] Fix infinite dilution for isotherm and isobar derivatives

And add test
---
 include/teqp/algorithms/VLE.hpp | 105 +++++++++++++++++++++++++-------
 1 file changed, 82 insertions(+), 23 deletions(-)

diff --git a/include/teqp/algorithms/VLE.hpp b/include/teqp/algorithms/VLE.hpp
index 75e8265..4fe223d 100644
--- a/include/teqp/algorithms/VLE.hpp
+++ b/include/teqp/algorithms/VLE.hpp
@@ -373,8 +373,8 @@ auto get_drhovecdp_Tsat(const Model& model, const Scalar &T, const VecType& rhov
                     auto Aij = (Hliq.row(j).array().cwiseProduct(((i == 0) ? rhovecV : rhovecL).array().transpose())).eval(); // coefficient - wise product
                     // A throwaway boolean for clarity
                     bool is_liq = (i == 1);
-                    // Apply correction to the j term (RT if liquid, RT*phi for vapor)
-                    Aij[j] = (is_liq) ? RL*T : RL*T*exp(-(murV[j] - murL[j])/(RL*T));
+                    // Apply correction to the divergent term
+                    Aij[j] = RL*T;  // Note not as given in Deiters
                     // Fill in entry
                     A(i, j) = Aij.sum();
                 }
@@ -402,17 +402,12 @@ auto get_drhovecdp_Tsat(const Model& model, const Scalar &T, const VecType& rhov
     return std::make_tuple(drhodp_liq, drhodp_vap);
 }
 
-/***
-* \brief Derivative of molar concentration vectors w.r.t. T along an isobar of the phase envelope for binary mixtures
-*
-* \f[
-* \left(\frac{d \vec\rho' }{d T}\right)_{p,\sigma}
-* \f]
-* 
-* See Eq 13 and 14 of Deiters and Bell, AICHEJ: https://doi.org/10.1002/aic.16730
+/**
+ * Derivative of molar concentration vectors w.r.t. p along an isobar of the phase envelope for binary mixtures
 */
 template<class Model, class Scalar, class VecType>
-auto get_drhovecdT_psat(const Model& model, const Scalar& T, const VecType& rhovecL, const VecType& rhovecV) {
+auto get_drhovecdT_psat(const Model& model, const Scalar &T, const VecType& rhovecL, const VecType& rhovecV) {
+    
     using id = IsochoricDerivatives<Model, Scalar, VecType>;
     if (rhovecL.size() != 2) { throw std::invalid_argument("Binary mixtures only"); }
     assert(rhovecL.size() == rhovecV.size());
@@ -422,9 +417,10 @@ auto get_drhovecdT_psat(const Model& model, const Scalar& T, const VecType& rhov
 
     auto N = rhovecL.size();
     Eigen::MatrixXd A = decltype(Hliq)::Zero(N, N);
-    Eigen::MatrixXd R = decltype(Hliq)::Ones(N, 1);
-    Eigen::MatrixXd drhodT_liq, drhodT_vap;
-    
+    Eigen::MatrixXd b = decltype(Hliq)::Ones(N, 1);
+    decltype(Hliq) drhovecdT_liq, drhovecdT_vap;
+    assert(rhovecL.size() == rhovecV.size());
+
     if ((rhovecL != 0).all() && (rhovecV != 0).all()) {
         // Normal treatment for all concentrations not equal to zero
         A(0, 0) = Hliq.row(0).dot(rhovecV.matrix());
@@ -432,18 +428,81 @@ auto get_drhovecdT_psat(const Model& model, const Scalar& T, const VecType& rhov
         A(1, 0) = Hliq.row(0).dot(rhovecL.matrix());
         A(1, 1) = Hliq.row(1).dot(rhovecL.matrix());
 
-        VecType deltas = (id::get_dchempotdT_autodiff(model, T, rhovecV) - id::get_dchempotdT_autodiff(model, T, rhovecL)).eval();
-
-        R(0,0) = (deltas.matrix().dot(rhovecV.matrix())-id::get_dpdT_constrhovec(model, T, rhovecV));
-        R(1,0) = -id::get_dpdT_constrhovec(model, T, rhovecL);
+        auto DELTAdmu_dT = (id::get_dchempotdT_autodiff(model, T, rhovecV) - id::get_dchempotdT_autodiff(model, T, rhovecL)).eval();
+        b(0) = DELTAdmu_dT.matrix().dot(rhovecV.matrix()) - id::get_dpdT_constrhovec(model, T, rhovecV);
+        b(1) = -id::get_dpdT_constrhovec(model, T, rhovecL);
+        // Calculate the derivatives of the liquid phase
+        drhovecdT_liq = linsolve(A, b);
 
-        drhodT_liq = linsolve(A, R);
-        drhodT_vap = linsolve(Hvap, ((Hliq*drhodT_liq).array() - deltas.array()).eval());
+        // Calculate the derivatives of the vapor phase
+        drhovecdT_vap = linsolve(Hvap, ((Hliq*drhovecdT_liq).array() - DELTAdmu_dT.array()).eval());
     }
-    else {
-        std::invalid_argument("Infinite dilution not yet supported");
+    else{
+        // Special treatment for infinite dilution
+        auto murL = id::build_Psir_gradient_autodiff(model, T, rhovecL);
+        auto murV = id::build_Psir_gradient_autodiff(model, T, rhovecV);
+        auto RL = model.R(rhovecL / rhovecL.sum());
+        auto RV = model.R(rhovecV / rhovecV.sum());
+
+        // The dot product contains terms of the type:
+        // rho'_i (R ln(rho"_i /rho'_i) + d mu ^ r"_i/d T - d mu^r'_i/d T)
+
+        // Residual contribution to the difference in temperature derivative of chemical potential
+        // It should be fine to evaluate in with zero densities:
+        auto DELTAdmu_dT_res = (id::build_d2PsirdTdrhoi_autodiff(model, T, rhovecV) - id::build_d2PsirdTdrhoi_autodiff(model, T, rhovecL)).eval();
+        // Now the ideal-gas part causes trouble, so multiply by the rhovec, once with liquid, another with vapor
+        // Start off with the assumption that the rhovec is all positive (fix elements later)
+        auto DELTAdmu_dT_rhoV_ideal = (rhovecV*(RV*log(rhovecV/rhovecL))).eval();
+        auto DELTAdmu_dT_rhoL_ideal = (rhovecL*(RV*log(rhovecV/rhovecL))).eval();
+        // Zero out contributions where a concentration is zero
+        for (auto i = 0; i < rhovecV.size(); ++i) {
+            if (rhovecV[i] == 0) {
+                DELTAdmu_dT_rhoV_ideal(i) = 0;
+                DELTAdmu_dT_rhoL_ideal(i) = 0;
+            }
+        }
+        double DELTAdmu_dT_rhoV = rhovecV.matrix().dot(DELTAdmu_dT_res.matrix()) + DELTAdmu_dT_rhoV_ideal.sum();
+        double DELTAdmu_dT_rhoL = rhovecL.matrix().dot(DELTAdmu_dT_res.matrix()) + DELTAdmu_dT_rhoL_ideal.sum();
+        b(0) = DELTAdmu_dT_rhoV - id::get_dpdT_constrhovec(model, T, rhovecV);
+        b(1) = -id::get_dpdT_constrhovec(model, T, rhovecL);
+
+        // First, for the liquid part
+        for (auto i = 0; i < N; ++i) {
+            // A throwaway boolean for clarity
+            bool is_liq = (i == 1);
+            for (auto j = 0; j < N; ++j) {
+                auto rhovec = (is_liq) ? rhovecL : rhovecV;
+                // Initial values
+                auto Aij = (Hliq.row(j).array().cwiseProduct(rhovec.array().transpose())).eval(); // coefficient - wise product
+                // Only rows in H that have a divergent entry in a column need to be adjusted
+                if (!(Hliq.row(j)).array().isFinite().all()) { 
+                    // A correction is needed in the entry in Aij corresponding to entry for zero concentration
+                    if (rhovec[j] == 0) {
+                        Aij[j] = RL*T; // Note not as given in Deiters
+                    }
+                }
+                
+                // Fill in entry
+                A(i, j) = Aij.sum();
+            }
+        }
+        drhovecdT_liq = linsolve(A, b);
+
+        // Then, for the vapor part, also requiring special treatment
+        // Left-multiplication of both sides of equation by diagonal matrix with 
+        // liquid concentrations along diagonal, all others zero
+        auto diagrhovecL = rhovecL.matrix().asDiagonal();
+        auto Hvapstar = (diagrhovecL*Hvap).eval();
+        auto Hliqstar = (diagrhovecL*Hliq).eval();
+        for (auto j = 0; j < N; ++j) {
+            if (rhovecL[j] == 0) {
+                Hliqstar(j, j) = RL*T;
+                Hvapstar(j, j) = RV*T/exp(-(murV[j] - murL[j]) / (RV * T));
+            }
+        }
+        drhovecdT_vap = linsolve(Hvapstar.eval(), ((Hliqstar*drhovecdT_liq).array() - DELTAdmu_dT_rhoL).eval());
     }
-    return std::make_tuple(drhodT_liq, drhodT_vap);
+    return std::make_tuple(drhovecdT_liq, drhovecdT_vap);
 }
 
 /***
-- 
GitLab