Skip to content
Snippets Groups Projects
cubics.hpp 13.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • Ian Bell's avatar
    Ian Bell committed
    Implementations of the canonical cubic equations of state
    
    */
    
    #include <vector>
    #include <variant>
    #include <valarray>
    
    Ian Bell's avatar
    Ian Bell committed
    #include <optional>
    
    Ian Bell's avatar
    Ian Bell committed
    #include "teqp/types.hpp"
    
    #include "teqp/constants.hpp"
    
    Ian Bell's avatar
    Ian Bell committed
    #include "teqp/exceptions.hpp"
    
    #include "cubicsuperancillary.hpp"
    
    #include "teqp/json_tools.hpp"
    
    #include "nlohmann/json.hpp"
    
    
    Ian Bell's avatar
    Ian Bell committed
    #include <Eigen/Dense>
    
    
    namespace teqp {
    
    
     * \brief The standard alpha function used by Peng-Robinson and SRK
     */
    
    template<typename NumType>
    class BasicAlphaFunction {
    private:
        NumType Tci, ///< The critical temperature
    
        mi;  ///< The "m" parameter
    
    public:
        BasicAlphaFunction(NumType Tci, NumType mi) : Tci(Tci), mi(mi) {};
    
        template<typename TType>
        auto operator () (const TType& T) const {
    
    Ian Bell's avatar
    Ian Bell committed
            return forceeval(pow2(forceeval(1.0 + mi * (1.0 - sqrt(T / Tci)))));
    
    Ian Bell's avatar
    Ian Bell committed
    /**
     * \brief The Twu alpha function used by Peng-Robinson and SRK
     * \f[
     * \alpha_i = \left(\frac{T}{T_{ci}\right)^{c_2(c_1-1)}\exp\left[c_0(1-\left(\frac{T}{T_{ci}\right)^{c_1c_2})\right]
     * \f]
     */
    template<typename NumType>
    class TwuAlphaFunction {
    private:
        NumType Tci; ///< The critical temperature
        Eigen::Array3d c;
    public:
        TwuAlphaFunction(NumType Tci, const Eigen::Array3d &c) : Tci(Tci), c(c) {
            if (c.size()!= 3){
                throw teqp::InvalidArgument("coefficients c for Twu alpha function must have length 3");
            }
        };
        template<typename TType>
        auto operator () (const TType& T) const {
            return forceeval(pow(T/Tci,c[2]*(c[1]-1))*exp(c[0]*(1.0-pow(T/Tci, c[1]*c[2]))));
        }
    };
    
    using AlphaFunctionOptions = std::variant<BasicAlphaFunction<double>, TwuAlphaFunction<double>>;
    
    
    template <typename NumType, typename AlphaFunctions>
    class GenericCubic {
    protected:
        std::valarray<NumType> ai, bi;
        const NumType Delta1, Delta2, OmegaA, OmegaB;
    
        int superanc_index;
    
        const AlphaFunctions alphas;
    
    Ian Bell's avatar
    Ian Bell committed
        Eigen::ArrayXXd kmat;
    
        nlohmann::json meta;
    
        template<typename TType, typename IndexType>
        auto get_ai(TType T, IndexType i) const { return ai[i]; }
    
        template<typename TType, typename IndexType>
        auto get_bi(TType T, IndexType i) const { return bi[i]; }
    
        template<typename IndexType>
        void check_kmat(IndexType N) {
    
    Ian Bell's avatar
    Ian Bell committed
            if (kmat.cols() != kmat.rows()) {
                throw teqp::InvalidArgument("kmat rows [" + std::to_string(kmat.rows()) + "] and columns [" + std::to_string(kmat.cols()) + "] are not identical");
            }
            if (kmat.cols() == 0) {
                kmat.resize(N, N); kmat.setZero();
            }
            else if (kmat.cols() != N) {
                throw teqp::InvalidArgument("kmat needs to be a square matrix the same size as the number of components [" + std::to_string(N) + "]");
            }
        };
    
    Ian Bell's avatar
    Ian Bell committed
        GenericCubic(NumType Delta1, NumType Delta2, NumType OmegaA, NumType OmegaB, int superanc_index, const std::valarray<NumType>& Tc_K, const std::valarray<NumType>& pc_Pa, const AlphaFunctions& alphas, const Eigen::ArrayXXd& kmat)
    
        : Delta1(Delta1), Delta2(Delta2), OmegaA(OmegaA), OmegaB(OmegaB), superanc_index(superanc_index), alphas(alphas), kmat(kmat)
    
        {
            ai.resize(Tc_K.size());
            bi.resize(Tc_K.size());
            for (auto i = 0; i < Tc_K.size(); ++i) {
    
    Ian Bell's avatar
    Ian Bell committed
                ai[i] = OmegaA * pow2(Ru * Tc_K[i]) / pc_Pa[i];
    
                bi[i] = OmegaB * Ru * Tc_K[i] / pc_Pa[i];
            }
    
    Ian Bell's avatar
    Ian Bell committed
            check_kmat(ai.size());
    
        void set_meta(const nlohmann::json& j) { meta = j; }
        auto get_meta() const { return meta; }
    
        auto get_kmat() const { return kmat; }
    
        /// Return a tuple of saturated liquid and vapor densities for the EOS given the temperature
    
        /// Uses the superancillary equations from Bell and Deiters:
    
        auto superanc_rhoLV(double T) const {
            if (ai.size() != 1) {
                throw std::invalid_argument("function only available for pure species");
            }
            const std::valarray<double> z = { 1.0 };
            auto b = get_b(T, z);
            auto Ttilde = R(z)*T*b/get_a(T,z);
            return std::make_tuple(
    
                                   CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOL_CODE, Ttilde)/b,
                                   CubicSuperAncillary::supercubic(superanc_index, CubicSuperAncillary::RHOV_CODE, Ttilde)/b
                                   );
    
        const NumType Ru = get_R_gas<double>(); /// Universal gas constant, exact number
    
        template<class VecType>
        auto R(const VecType& molefrac) const {
            return Ru;
        }
    
        template<typename TType, typename CompType>
        auto get_a(TType T, const CompType& molefracs) const {
            std::common_type_t<TType, decltype(molefracs[0])> a_ = 0.0;
            auto ai = this->ai;
            for (auto i = 0; i < molefracs.size(); ++i) {
                auto alphai = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[i]));
                auto ai_ = forceeval(ai[i] * alphai);
                for (auto j = 0; j < molefracs.size(); ++j) {
                    auto alphaj = forceeval(std::visit([&](auto& t) { return t(T); }, alphas[j]));
                    auto aj_ = ai[j] * alphaj;
    
    Ian Bell's avatar
    Ian Bell committed
                    auto aij = forceeval((1 - kmat(i,j)) * sqrt(ai_ * aj_));
    
                    a_ = a_ + molefracs[i] * molefracs[j] * aij;
                }
            }
            return forceeval(a_);
        }
    
        template<typename TType, typename CompType>
    
        auto get_b(TType /*T*/, const CompType& molefracs) const {
    
            std::common_type_t<TType, decltype(molefracs[0])> b_ = 0.0;
            for (auto i = 0; i < molefracs.size(); ++i) {
                b_ = b_ + molefracs[i] * bi[i];
            }
            return forceeval(b_);
        }
    
        template<typename TType, typename RhoType, typename MoleFracType>
        auto alphar(const TType& T,
    
                    const RhoType& rho,
                    const MoleFracType& molefrac) const
    
            if (molefrac.size() != alphas.size()) {
                throw std::invalid_argument("Sizes do not match");
            }
    
            auto b = get_b(T, molefrac);
    
            auto Psiminus = -log(1.0 - b * rho);
    
            auto Psiplus = log((Delta1 * b * rho + 1.0) / (Delta2 * b * rho + 1.0)) / (b * (Delta1 - Delta2));
    
            auto val = Psiminus - get_a(T, molefrac) / (Ru * T) * Psiplus;
            return forceeval(val);
        }
    };
    
    template <typename TCType, typename PCType, typename AcentricType>
    
    auto canonical_SRK(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt) {
    
        double Delta1 = 1;
        double Delta2 = 0;
    
        AcentricType m = 0.48 + 1.574 * acentric - 0.176 * acentric * acentric;
    
        std::vector<AlphaFunctionOptions> alphas;
        for (auto i = 0; i < Tc_K.size(); ++i) {
            alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
        }
    
        // See https://doi.org/10.1021/acs.iecr.1c00847
        double OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
        double OmegaB = (cbrt(2) - 1) / 3;
    
        nlohmann::json meta = {
            {"Delta1", Delta1},
            {"Delta2", Delta2},
            {"OmegaA", OmegaA},
            {"OmegaB", OmegaB},
            {"kind", "Soave-Redlich-Kwong"}
        };
    
        const std::size_t N = m.size();
        auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, CubicSuperAncillary::SRK_CODE, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)));
    
        cub.set_meta(meta);
        return cub;
    
    /// A JSON-based factory function for the canonical SRK model
    inline auto make_canonicalSRK(const nlohmann::json& spec){
        std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
        Eigen::ArrayXXd kmat(0, 0);
        if (spec.contains("kmat")){
            kmat = build_square_matrix(spec.at("kmat"));
        }
        return canonical_SRK(Tc_K, pc_Pa, acentric, kmat);
    }
    
    
    template <typename TCType, typename PCType, typename AcentricType>
    
    auto canonical_PR(TCType Tc_K, PCType pc_Pa, AcentricType acentric, const std::optional<Eigen::ArrayXXd>& kmat = std::nullopt) {
    
        double Delta1 = 1+sqrt(2.0);
        double Delta2 = 1-sqrt(2.0);
    
        AcentricType m = acentric*0.0;
    
        std::vector<AlphaFunctionOptions> alphas; 
        for (auto i = 0; i < Tc_K.size(); ++i) {
            if (acentric[i] < 0.491) {
    
    Ian Bell's avatar
    Ian Bell committed
                m[i] = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
    
    Ian Bell's avatar
    Ian Bell committed
                m[i] = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
    
            }
            alphas.emplace_back(BasicAlphaFunction(Tc_K[i], m[i]));
        }
    
        // See https://doi.org/10.1021/acs.iecr.1c00847
        double OmegaA = 0.45723552892138218938;
        double OmegaB = 0.077796073903888455972;
    
    
        nlohmann::json meta = {
            {"Delta1", Delta1},
            {"Delta2", Delta2},
            {"OmegaA", OmegaA},
            {"OmegaB", OmegaB},
            {"kind", "Peng-Robinson"}
        };
    
        
        const std::size_t N = m.size();
        auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, CubicSuperAncillary::PR_CODE, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)));
    
        cub.set_meta(meta);
        return cub;
    
    /// A JSON-based factory function for the canonical SRK model
    inline auto make_canonicalPR(const nlohmann::json& spec){
        std::valarray<double> Tc_K = spec.at("Tcrit / K"), pc_Pa = spec.at("pcrit / Pa"), acentric = spec.at("acentric");
        Eigen::ArrayXXd kmat(0, 0);
        if (spec.contains("kmat")){
            kmat = build_square_matrix(spec.at("kmat"));
        }
        return canonical_PR(Tc_K, pc_Pa, acentric, kmat);
    }
    
    
    Ian Bell's avatar
    Ian Bell committed
    /// A JSON-based factory function for the generalized cubic + alpha
    inline auto make_generalizedcubic(const nlohmann::json& spec){
        // Tc, pc, and acentric factor must always be provided
        std::valarray<double> Tc_K = spec.at("Tcrit / K"),
        pc_Pa = spec.at("pcrit / Pa"),
        acentric = spec.at("acentric");
        
        // If kmat is provided, then collect it
        std::optional<Eigen::ArrayXXd> kmat;
        if (spec.contains("kmat")){
            kmat = build_square_matrix(spec.at("kmat"));
        }
        
        int superanc_code = CubicSuperAncillary::UNKNOWN_CODE;
        
        // Build the list of alpha functions, one per component
        std::vector<AlphaFunctionOptions> alphas;
        
        double Delta1, Delta2, OmegaA, OmegaB;
        std::string kind = "custom";
        
        auto add_alphas = [&](const nlohmann::json& jalphas){
            std::size_t i = 0;
            for (auto alpha : jalphas){
                std::string type = alpha.at("type");
                std::valarray<double> c_ = alpha.at("c");
                Eigen::Array3d c = Eigen::Map<Eigen::Array3d>(&(c_[0]), c_.size());
                if (type == "Twu"){
                    alphas.emplace_back(TwuAlphaFunction(Tc_K[i], c));
                }
                else{
                    throw teqp::InvalidArgument("alpha type is not understood: "+type);
                }
                i++;
            }
        };
        
        if (spec.at("type") == "PR" ){
            Delta1 = 1+sqrt(2.0);
            Delta2 = 1-sqrt(2.0);
            // See https://doi.org/10.1021/acs.iecr.1c00847
            OmegaA = 0.45723552892138218938;
            OmegaB = 0.077796073903888455972;
            superanc_code = CubicSuperAncillary::PR_CODE;
            kind = "Peng-Robinson";
            
            if (!spec.contains("alpha")){
                for (auto i = 0; i < Tc_K.size(); ++i) {
                    double mi;
                    if (acentric[i] < 0.491) {
                        mi = 0.37464 + 1.54226*acentric[i] - 0.26992*pow2(acentric[i]);
                    }
                    else {
                        mi = 0.379642 + 1.48503*acentric[i] -0.164423*pow2(acentric[i]) + 0.016666*pow3(acentric[i]);
                    }
                    alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
                }
            }
            else{
                if (!spec["alpha"].is_array()){
                    throw teqp::InvalidArgument("alpha must be array of objects");
                }
                add_alphas(spec.at("alpha"));
            }
        }
        else if (spec.at("type") == "SRK"){
            Delta1 = 1;
            Delta2 = 0;
            if (!spec.contains("alpha")){
                for (auto i = 0; i < Tc_K.size(); ++i) {
                    double mi = 0.48 + 1.574 * acentric[i] - 0.176 * acentric[i] * acentric[i];
                    alphas.emplace_back(BasicAlphaFunction(Tc_K[i], mi));
                }
            }
            else{
                if (!spec["alpha"].is_array()){
                    throw teqp::InvalidArgument("alpha must be array of objects");
                }
                add_alphas(spec.at("alpha"));
            }
            // See https://doi.org/10.1021/acs.iecr.1c00847
            OmegaA = 1.0 / (9.0 * (cbrt(2) - 1));
            OmegaB = (cbrt(2) - 1) / 3;
            superanc_code = CubicSuperAncillary::SRK_CODE;
            kind = "Soave-Redlich-Kwong";
        }
        else{
            // Generalized handling of generic cubics (not yet implemented)
            throw teqp::InvalidArgument("Generic cubic EOS are not yet supported (open an issue on github if you want this)");
        }
        
        const std::size_t N = Tc_K.size();
        nlohmann::json meta = {
            {"Delta1", Delta1},
            {"Delta2", Delta2},
            {"OmegaA", OmegaA},
            {"OmegaB", OmegaB},
            {"kind", kind}
        };
        if (spec.contains("alpha")){
            meta["alpha"] = spec.at("alpha");
        }
        
        auto cub = GenericCubic(Delta1, Delta2, OmegaA, OmegaB, superanc_code, Tc_K, pc_Pa, std::move(alphas), kmat.value_or(Eigen::ArrayXXd::Zero(N,N)));
        cub.set_meta(meta);
        return cub;
    }
    
    
    
    
    }; // namespace teqp