Skip to content
Snippets Groups Projects
multifluid.hpp 35.1 KiB
Newer Older
  • Learn to ignore specific revisions
  • #include "nlohmann/json.hpp"
    
    
    Ian Bell's avatar
    Ian Bell committed
    #include <Eigen/Dense>
    
    #include <cmath>
    
    Ian Bell's avatar
    Ian Bell committed
    #include "teqp/constants.hpp"
    
    #include "teqp/json_tools.hpp"
    
    #include "MultiComplex/MultiComplex.hpp"
    
    #include "multifluid_eosterms.hpp"
    
    #include "multifluid_reducing.hpp"
    
    
    #include <boost/algorithm/string/join.hpp>
    
    Ian Bell's avatar
    Ian Bell committed
    // See https://eigen.tuxfamily.org/dox/TopicCustomizing_CustomScalar.html
    namespace Eigen {
    
        template<typename TN> struct NumTraits<mcx::MultiComplex<TN>> : NumTraits<double> // permits to get the epsilon, dummy_precision, lowest, highest functions
    
    Ian Bell's avatar
    Ian Bell committed
        {
            enum {
                IsComplex = 1,
                IsInteger = 0,
                IsSigned = 1,
                RequireInitialization = 1,
                ReadCost = 1,
                AddCost = 3,
                MulCost = 3
            };
        };
    }
    
    
    namespace teqp{
    
    
    template<typename EOSCollection>
    class CorrespondingStatesContribution {
    
    private:
        const EOSCollection EOSs;
    public:
        CorrespondingStatesContribution(EOSCollection&& EOSs) : EOSs(EOSs) {};
    
        
        auto size() const { return EOSs.size(); }
    
    
        template<typename TauType, typename DeltaType, typename MoleFractions>
        auto alphar(const TauType& tau, const DeltaType& delta, const MoleFractions& molefracs) const {
    
            using resulttype = std::common_type_t<decltype(tau), decltype(molefracs[0]), decltype(delta)>; // Type promotion, without the const-ness
    
            resulttype alphar = 0.0;
            auto N = molefracs.size();
            for (auto i = 0; i < N; ++i) {
                alphar = alphar + molefracs[i] * EOSs[i].alphar(tau, delta);
            }
    
    Ian Bell's avatar
    Ian Bell committed
            return forceeval(alphar);
    
    
        template<typename TauType, typename DeltaType>
        auto alphari(const TauType& tau, const DeltaType& delta, std::size_t i) const {
            return EOSs[i].alphar(tau, delta);
        }
    
        auto get_EOS(std::size_t i) const{
            return EOSs[i];
        }
    
    };
    
    template<typename FCollection, typename DepartureFunctionCollection>
    class DepartureContribution {
    
    private:
        const FCollection F;
        const DepartureFunctionCollection funcs;
    public:
        DepartureContribution(FCollection&& F, DepartureFunctionCollection&& funcs) : F(F), funcs(funcs) {};
    
        template<typename TauType, typename DeltaType, typename MoleFractions>
        auto alphar(const TauType& tau, const DeltaType& delta, const MoleFractions& molefracs) const {
    
            using resulttype = std::common_type_t<decltype(tau), decltype(molefracs[0]), decltype(delta)>; // Type promotion, without the const-ness
    
            resulttype alphar = 0.0;
            auto N = molefracs.size();
            for (auto i = 0; i < N; ++i) {
    
    Ian Bell's avatar
    Ian Bell committed
                    alphar = alphar + molefracs[i] * molefracs[j] * F(i, j) * funcs[i][j].alphar(tau, delta);
    
    Ian Bell's avatar
    Ian Bell committed
            return forceeval(alphar);
    
    
        /// Call a single departure term at i,j 
        template<typename TauType, typename DeltaType>
        auto get_alpharij(const int i, const int j,     const TauType& tau, const DeltaType& delta) const {
            int N = funcs.size();
            if (i < 0 || j < 0){
                throw teqp::InvalidArgument("i or j is negative");
            }
            if (i >= N || j >= N){
                throw teqp::InvalidArgument("i or j is invalid; size is " + std::to_string(N));
            }
            return forceeval(funcs[i][j].alphar(tau, delta));
        }
    
    template<typename CorrespondingTerm, typename DepartureTerm>
    
    class MultiFluid {  
    
    
    private:
        std::string meta = ""; ///< A string that can be used to store arbitrary metadata as needed
    
    public:
    
        const ReducingFunctions redfunc;
    
        const CorrespondingTerm corr;
        const DepartureTerm dep;
    
    
        template<class VecType>
        auto R(const VecType& molefrac) const {
            return get_R_gas<decltype(molefrac[0])>();
        }
    
        /// Store some sort of metadata in string form (perhaps a JSON representation of the model?)
        void set_meta(const std::string& m) { meta = m; }
        /// Get the metadata stored in string form
        auto get_meta() const { return meta; }
    
    
        MultiFluid(ReducingFunctions&& redfunc, CorrespondingTerm&& corr, DepartureTerm&& dep) : redfunc(redfunc), corr(corr), dep(dep) {};
    
    
        template<typename TType, typename RhoType>
        auto alphar(TType T,
            const RhoType& rhovec,
            const std::optional<typename RhoType::value_type> rhotot = std::nullopt) const
        {
    
    Ian Bell's avatar
    Ian Bell committed
            typename RhoType::value_type rhotot_ = (rhotot.has_value()) ? rhotot.value() : std::accumulate(std::begin(rhovec), std::end(rhovec), (decltype(rhovec[0]))0.0);
    
            return alphar(T, rhotot_, molefrac);
        }
    
        template<typename TType, typename RhoType, typename MoleFracType>
    
        auto alphar(const TType &T,
            const RhoType &rho,
    
            const MoleFracType& molefrac) const
        {
    
            if (molefrac.size() != corr.size()){
                throw teqp::InvalidArgument("Wrong size of mole fractions; "+std::to_string(corr.size()) + " are loaded but "+std::to_string(molefrac.size()) + " were provided");
            }
    
            auto Tred = forceeval(redfunc.get_Tr(molefrac));
            auto rhored = forceeval(redfunc.get_rhor(molefrac));
    
            auto delta = forceeval(rho / rhored);
            auto tau = forceeval(Tred / T);
            auto val = corr.alphar(tau, delta, molefrac) + dep.alphar(tau, delta, molefrac);
    
            return forceeval(val);
    
    /***
    * \brief Get the JSON data structure for a given departure function
    * \param name The name (or alias) of the departure function to be looked up
    * \parm path The root path to the fluid data, or alternatively, the path to the json file directly
    */
    inline auto get_departure_json(const std::string& name, const std::string& path) {
        std::string filepath = std::filesystem::is_regular_file(path) ? path : path + "/dev/mixtures/mixture_departure_functions.json";
        nlohmann::json j = load_a_JSON_file(filepath);
        std::string js = j.dump(2);
        // First pass, direct name lookup
        for (auto& el : j) {
            if (el.at("Name") == name) {
                return el;
            }
        }
        // Second pass, iterate over aliases
        for (auto& el : j) {
            for (auto &alias : el.at("aliases")) {
                if (alias == name) {
                    return el;
                }
            }
        }
        throw std::invalid_argument("Could not match the name: " + name + "when looking up departure function");
    }
        
    
    inline auto build_departure_function(const nlohmann::json& j) {
    
        auto build_power = [&](auto term, auto& dep) {
    
            std::size_t N = term["n"].size();
    
    
            // Don't add a departure function if there are no coefficients provided
            if (N == 0) {
                return;
            }
    
    
            PowerEOSTerm eos;
    
            auto eigorzero = [&term, &N](const std::string& name) -> Eigen::ArrayXd {
                if (!term[name].empty()) {
                    return toeig(term[name]);
                }
                else {
                    return Eigen::ArrayXd::Zero(N);
                }
            };
    
    
            eos.n = eigorzero("n");
            eos.t = eigorzero("t");
            eos.d = eigorzero("d");
    
            Eigen::ArrayXd c(N), l(N); c.setZero();
    
            if (term["l"].empty()) {
                // exponential part not included
                l.setZero();
    
                if (!all_same_length(term, { "n","t","d" })) {
    
                    throw std::invalid_argument("Lengths are not all identical in polynomial-like term");
    
                if (!all_same_length(term, { "n","t","d","l"})) {
                    throw std::invalid_argument("Lengths are not all identical in exponential term");
                }
    
                l = toeig(term["l"]);
                // l is included, use it to build c; c_i = 1 if l_i > 0, zero otherwise
                for (auto i = 0; i < c.size(); ++i) {
                    if (l[i] > 0) {
                        c[i] = 1.0;
                    }
                }
    
    
                // See how many of the first entries have zero values for l_i
                contiguous_lzero = (l[0] == 0);
                for (auto i = 0; i < c.size(); ++i) {
                    if (l[i] == 0) {
                        Nlzero++;
                    }
                }
            }
    
            Nlnonzero = static_cast<int>(l.size()) - Nlzero;
    
    
            if ((l[0] != 0) && (l[l.size() - 1] == 0)) {
                throw std::invalid_argument("If l_i has zero and non-zero values, the zero values need to come first");
    
            eos.c = c;
            eos.l = l;
    
            eos.l_i = eos.l.cast<int>();
    
            if (Nlzero + Nlnonzero != l.size()) {
                throw std::invalid_argument("Somehow the l lengths don't add up");
            }
    
    
    
            if (((eos.l_i.cast<double>() - eos.l).cwiseAbs() > 0.0).any()) {
                throw std::invalid_argument("Non-integer entry in l found");
            }
    
            // If a contiguous portion of the terms have values of l_i that are zero
            // it is computationally advantageous to break up the evaluation into 
            // part that has just the n_i*tau^t_i*delta^d_i and the part with the
            // exponential term exp(-delta^l_i)
            if (l.sum() == 0) {
                // No l term at all, just polynomial
                JustPowerEOSTerm poly;
                poly.n = eos.n;
                poly.t = eos.t;
                poly.d = eos.d;
                dep.add_term(poly);
            }
            else if (l.sum() > 0 && contiguous_lzero){
                JustPowerEOSTerm poly; 
                poly.n = eos.n.head(Nlzero);
                poly.t = eos.t.head(Nlzero);
                poly.d = eos.d.head(Nlzero);
                dep.add_term(poly);
    
                PowerEOSTerm e;
                e.n = eos.n.tail(Nlnonzero);
                e.t = eos.t.tail(Nlnonzero);
                e.d = eos.d.tail(Nlnonzero);
                e.c = eos.c.tail(Nlnonzero);
                e.l = eos.l.tail(Nlnonzero);
    
                e.l_i = eos.l_i.tail(Nlnonzero);
    
                dep.add_term(e);
            }
            else {
                // Don't try to get too clever, just add the departure term
                dep.add_term(eos);
            }
    
    Ian Bell's avatar
    Ian Bell committed
        auto build_doubleexponential = [&](auto& term, auto& dep) {
            if (!all_same_length(term, { "n","t","d","ld","gd","lt","gt" })) {
                throw std::invalid_argument("Lengths are not all identical in double exponential term");
            }
            DoubleExponentialEOSTerm eos;
            eos.n = toeig(term.at("n"));
            eos.t = toeig(term.at("t"));
            eos.d = toeig(term.at("d"));
            eos.ld = toeig(term.at("ld"));
            eos.gd = toeig(term.at("gd"));
            eos.lt = toeig(term.at("lt"));
            eos.gt = toeig(term.at("gt"));
            eos.ld_i = eos.ld.cast<int>();
            dep.add_term(eos);
        }; 
    
        auto build_Chebyshev2D = [&](auto& term, auto& dep) {
            Chebyshev2DEOSTerm eos;
            int Ntau = term.at("Ntau"); // Degree in tau (there will be Ntau+1 coefficients in the tau direction)
            int Ndelta = term.at("Ndelta"); // Degree in delta (there will be Ndelta+1 coefficients in the delta direction)
            Eigen::ArrayXd c = toeig(term.at("a"));
            if ((Ntau + 1)*(Ndelta + 1) != c.size()){
                throw std::invalid_argument("Provided length [" + std::to_string(c.size()) + "] is not equal to (Ntau+1)*(Ndelta+1)");
            }
            eos.a = c.reshaped(Ntau+1, Ndelta+1).eval(); // All in one long array, then reshaped
            eos.taumin = term.at("taumin");
            eos.taumax = term.at("taumax");
            eos.deltamin = term.at("deltamin");
            eos.deltamax = term.at("deltamax");
            dep.add_term(eos);
        };
    
        //auto build_gaussian = [&](auto& term) {
        //    GaussianEOSTerm eos;
        //    eos.n = toeig(term["n"]);
        //    eos.t = toeig(term["t"]);
        //    eos.d = toeig(term["d"]);
        //    eos.eta = toeig(term["eta"]);
        //    eos.beta = toeig(term["beta"]);
        //    eos.gamma = toeig(term["gamma"]);
        //    eos.epsilon = toeig(term["epsilon"]);
        //    if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
        //        throw std::invalid_argument("Lengths are not all identical in Gaussian term");
        //    }
        //    return eos;
        //};
    
        auto build_GERG2004 = [&](const auto& term, auto& dep) {
    
            if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
    
    Ian Bell's avatar
    Ian Bell committed
                throw std::invalid_argument("Lengths are not all identical in GERG term");
    
            int Npower = term["Npower"];
            auto NGERG = static_cast<int>(term["n"].size()) - Npower;
    
            PowerEOSTerm eos;
            eos.n = toeig(term["n"]).head(Npower);
            eos.t = toeig(term["t"]).head(Npower);
            eos.d = toeig(term["d"]).head(Npower);
            if (term.contains("l")) {
                eos.l = toeig(term["l"]).head(Npower);
            }
            else {
                eos.l = 0.0 * eos.n;
            }
            eos.c = (eos.l > 0).cast<int>().cast<double>();
            eos.l_i = eos.l.cast<int>();
            dep.add_term(eos);
    
            GERG2004EOSTerm e;
            e.n = toeig(term["n"]).tail(NGERG);
            e.t = toeig(term["t"]).tail(NGERG);
            e.d = toeig(term["d"]).tail(NGERG);
            e.eta = toeig(term["eta"]).tail(NGERG);
            e.beta = toeig(term["beta"]).tail(NGERG);
            e.gamma = toeig(term["gamma"]).tail(NGERG);
            e.epsilon = toeig(term["epsilon"]).tail(NGERG);
            dep.add_term(e);
        };
    
        auto build_GaussianExponential = [&](const auto& term, auto& dep) {
            if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
                throw std::invalid_argument("Lengths are not all identical in Gaussian+Exponential term");
            }
            int Npower = term["Npower"];
            auto NGauss = static_cast<int>(term["n"].size()) - Npower;
    
            PowerEOSTerm eos;
            eos.n = toeig(term["n"]).head(Npower);
            eos.t = toeig(term["t"]).head(Npower);
            eos.d = toeig(term["d"]).head(Npower);
            if (term.contains("l")) {
                eos.l = toeig(term["l"]).head(Npower);
            }
            else {
                eos.l = 0.0 * eos.n;
            }
            eos.c = (eos.l > 0).cast<int>().cast<double>();
            eos.l_i = eos.l.cast<int>();
            dep.add_term(eos);
    
            GaussianEOSTerm e;
            e.n = toeig(term["n"]).tail(NGauss);
            e.t = toeig(term["t"]).tail(NGauss);
            e.d = toeig(term["d"]).tail(NGauss);
            e.eta = toeig(term["eta"]).tail(NGauss);
            e.beta = toeig(term["beta"]).tail(NGauss);
            e.gamma = toeig(term["gamma"]).tail(NGauss);
            e.epsilon = toeig(term["epsilon"]).tail(NGauss);
            dep.add_term(e);
        };
    
        std::string type = j.at("type");
    
        DepartureTerms dep;
        if (type == "Exponential") {
    
    Ian Bell's avatar
    Ian Bell committed
        else if (type == "DoubleExponential") {
            build_doubleexponential(j, dep);
        }
    
        else if (type == "GERG-2004" || type == "GERG-2008") {
            build_GERG2004(j, dep);
        }
        else if (type == "Gaussian+Exponential") {
            build_GaussianExponential(j, dep);
        }
    
        else if (type == "Chebyshev2D") {
            build_Chebyshev2D(j, dep);
        }
    
        else if (type == "none") {
            dep.add_term(NullEOSTerm());
        }
        else {
    
            std::vector<std::string> options = { "Exponential","GERG-2004","GERG-2008","Gaussian+Exponential", "none", "DoubleExponential","Chebyshev2D"};
    
            throw std::invalid_argument("Bad departure term type: " + type + ". Options are {" + boost::algorithm::join(options, ",") + "}");
    
    inline auto get_departure_function_matrix(const nlohmann::json& depcollection, const nlohmann::json& BIPcollection, const std::vector<std::string>& components, const nlohmann::json& flags) {
    
    
        // Allocate the matrix with default models
        std::vector<std::vector<DepartureTerms>> funcs(components.size()); for (auto i = 0; i < funcs.size(); ++i) { funcs[i].resize(funcs.size()); }
    
        // Load the collection of data on departure functions
    
        auto get_departure_json = [&depcollection](const std::string& Name) {
            for (auto& el : depcollection) {
                if (el["Name"] == Name) { return el; }
    
            throw std::invalid_argument("Bad departure function name: "+Name);
    
        auto funcsmeta = nlohmann::json::object();
    
    
            std::string istr = std::to_string(i);
            if (funcsmeta.contains(istr)) { funcsmeta[istr] = {}; }
    
            for (auto j = i + 1; j < funcs.size(); ++j) {
    
                std::string jstr = std::to_string(j);
    
                auto [BIP, swap_needed] = reducing::get_BIPdep(BIPcollection, { components[i], components[j] }, flags);
    
                std::string funcname = BIP.contains("function") ? BIP["function"] : "";
    
                if (!funcname.empty()) {
    
                    auto jj = get_departure_json(funcname);
    
                    funcsmeta[istr][jstr] = { {"departure", jj}, {"BIP", BIP} };
                    funcsmeta[istr][jstr]["BIP"]["swap_needed"] = swap_needed;
    
                    funcs[i][j] = build_departure_function(jj);
                    funcs[j][i] = build_departure_function(jj);
    
                else {
    
                    funcs[i][j].add_term(NullEOSTerm());
                    funcs[j][i].add_term(NullEOSTerm());
    
        return std::make_tuple(funcs, funcsmeta);
    
    inline auto get_EOS_terms(const nlohmann::json& j)
    
        const std::vector<std::string> allowed_types = { "ResidualHelmholtzPower", "ResidualHelmholtzGaussian", "ResidualHelmholtzNonAnalytic","ResidualHelmholtzGaoB", "ResidualHelmholtzLemmon2005", "ResidualHelmholtzExponential" };
    
        auto isallowed = [&](const auto& conventional_types, const std::string& name) {
            for (auto& a : conventional_types) { if (name == a) { return true; }; } return false;
    
    
        for (auto& term : alphar) {
            std::string type = term["type"];
    
            if (!isallowed(allowed_types, type)) {
                std::string a = allowed_types[0]; for (auto i = 1; i < allowed_types.size(); ++i) { a += "," + allowed_types[i]; }
                throw std::invalid_argument("Bad type:" + type + "; allowed types are: {" + a + "}");
    
        auto build_power = [&](auto term, auto & container) {
    
            std::size_t N = term["n"].size();
    
            PowerEOSTerm eos;
    
    
            auto eigorzero = [&term, &N](const std::string& name) -> Eigen::ArrayXd {
    
                if (!term[name].empty()) {
                    return toeig(term[name]);
                }
                else {
                    return Eigen::ArrayXd::Zero(N);
                }
    
    
            eos.n = eigorzero("n");
            eos.t = eigorzero("t");
            eos.d = eigorzero("d");
    
            int Nlzero = 0, Nlnonzero = 0;
            bool contiguous_lzero;
    
            if (term["l"].empty()) {
                // exponential part not included
                l.setZero();
            }
            else {
                l = toeig(term["l"]);
                // l is included, use it to build c; c_i = 1 if l_i > 0, zero otherwise
                for (auto i = 0; i < c.size(); ++i) {
                    if (l[i] > 0) {
                        c[i] = 1.0;
                    }
                }
    
    
                // See how many of the first entries have zero values for l_i
                contiguous_lzero = (l[0] == 0);
                for (auto i = 0; i < c.size(); ++i) {
                    if (l[i] == 0) {
                        Nlzero++;
                    }
                }
    
            Nlnonzero = static_cast<int>(l.size()) - Nlzero;
    
            if (Nlzero + Nlnonzero != l.size()) {
                throw std::invalid_argument("Somehow the l lengths don't add up");
            }
    
    
            if (((eos.l_i.cast<double>() - eos.l).cwiseAbs() > 0.0).any()) {
                throw std::invalid_argument("Non-integer entry in l found");
            }
            
    
            // If a contiguous portion of the terms have values of l_i that are zero
            // it is computationally advantageous to break up the evaluation into 
            // part that has just the n_i*tau^t_i*delta^d_i and the part with the
            // exponential term exp(-delta^l_i)
            if (l.sum() == 0) {
                // No l term at all, just polynomial
                JustPowerEOSTerm poly;
                poly.n = eos.n;
                poly.t = eos.t;
                poly.d = eos.d;
                container.add_term(poly);
            }
            else if (l.sum() > 0 && contiguous_lzero) {
                JustPowerEOSTerm poly;
                poly.n = eos.n.head(Nlzero);
                poly.t = eos.t.head(Nlzero);
                poly.d = eos.d.head(Nlzero);
                container.add_term(poly);
    
                PowerEOSTerm e;
                e.n = eos.n.tail(Nlnonzero);
                e.t = eos.t.tail(Nlnonzero);
                e.d = eos.d.tail(Nlnonzero);
                e.c = eos.c.tail(Nlnonzero);
                e.l = eos.l.tail(Nlnonzero);
    
                e.l_i = eos.l_i.tail(Nlnonzero);
    
                container.add_term(e);
            }
            else {
                // Don't try to get too clever, just add the term
                container.add_term(eos);
            }
    
    Ian Bell's avatar
    Ian Bell committed
        auto build_Lemmon2005 = [&](auto term) {
            Lemmon2005EOSTerm eos;
    
            eos.n = toeig(term["n"]);
            eos.t = toeig(term["t"]);
            eos.d = toeig(term["d"]);
            eos.m = toeig(term["m"]);
            eos.l = toeig(term["l"]);
    
    Ian Bell's avatar
    Ian Bell committed
            eos.l_i = eos.l.cast<int>();
    
            if (!all_same_length(term, { "n","t","d","m","l" })) {
                throw std::invalid_argument("Lengths are not all identical in Lemmon2005 term");
            }
    
    Ian Bell's avatar
    Ian Bell committed
            if (((eos.l_i.cast<double>() - eos.l).cwiseAbs() > 0.0).any()) {
                throw std::invalid_argument("Non-integer entry in l found");
            }
            return eos;
        };
    
    
        auto build_gaussian = [&](auto term) {
            GaussianEOSTerm eos;
    
            eos.n = toeig(term["n"]);
            eos.t = toeig(term["t"]);
            eos.d = toeig(term["d"]);
            eos.eta = toeig(term["eta"]);
            eos.beta = toeig(term["beta"]);
            eos.gamma = toeig(term["gamma"]);
            eos.epsilon = toeig(term["epsilon"]);
            if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon" })) {
                throw std::invalid_argument("Lengths are not all identical in Gaussian term");
            }
    
        auto build_exponential = [&](auto term) {
            ExponentialEOSTerm eos;
            eos.n = toeig(term["n"]);
            eos.t = toeig(term["t"]);
            eos.d = toeig(term["d"]);
            eos.g = toeig(term["g"]);
            eos.l = toeig(term["l"]);
            eos.l_i = eos.l.cast<int>();
            if (!all_same_length(term, { "n","t","d","g","l" })) {
                throw std::invalid_argument("Lengths are not all identical in exponential term");
            }
            return eos;
        };
    
    
    Ian Bell's avatar
    Ian Bell committed
        auto build_GaoB = [&](auto term) {
            GaoBEOSTerm eos;
    
            eos.n = toeig(term["n"]);
            eos.t = toeig(term["t"]);
            eos.d = toeig(term["d"]);
            eos.eta = -toeig(term["eta"]); // Watch out for this sign flip!!
            eos.beta = toeig(term["beta"]);
            eos.gamma = toeig(term["gamma"]);
            eos.epsilon = toeig(term["epsilon"]);
            eos.b = toeig(term["b"]);
    
            if (!all_same_length(term, { "n","t","d","eta","beta","gamma","epsilon","b" })) {
                throw std::invalid_argument("Lengths are not all identical in GaoB term");
            }
    
    Ian Bell's avatar
    Ian Bell committed
            return eos;
        };
    
    
        /// lambda function for adding non-analytic terms
    
        auto build_na = [&](auto& term) {
    
            eos.n = toeig(term["n"]);
            eos.A = toeig(term["A"]);
            eos.B = toeig(term["B"]);
            eos.C = toeig(term["C"]);
            eos.D = toeig(term["D"]);
            eos.a = toeig(term["a"]);
            eos.b = toeig(term["b"]);
            eos.beta = toeig(term["beta"]);
            if (!all_same_length(term, { "n","A","B","C","D","a","b","beta" })) {
                throw std::invalid_argument("Lengths are not all identical in nonanalytic term");
            }
    
            std::string type = term["type"];
    
            if (type == "ResidualHelmholtzPower") {
    
            }
            else if (type == "ResidualHelmholtzGaussian") {
                container.add_term(build_gaussian(term));
            }
            else if (type == "ResidualHelmholtzNonAnalytic") {
                container.add_term(build_na(term));
            }
    
    Ian Bell's avatar
    Ian Bell committed
            else if (type == "ResidualHelmholtzLemmon2005") {
                container.add_term(build_Lemmon2005(term));
            }
            else if (type == "ResidualHelmholtzGaoB") {
                container.add_term(build_GaoB(term));
            }
    
            else if (type == "ResidualHelmholtzExponential") {
                container.add_term(build_exponential(term));
            }
    
                throw std::invalid_argument("Bad term type: "+type);
    
    inline auto get_EOSs(const std::vector<nlohmann::json>& pureJSON) {
    
        for (auto& j : pureJSON) {
            auto term = get_EOS_terms(j);
    
    inline auto collect_component_json(const std::vector<std::string>& components, const std::string& root) 
    {
        std::vector<nlohmann::json> out;
        for (auto c : components) {
    
            // First we try to lookup the name as a path, which can be on the filesystem, or relative to the root for default name lookup
    
            std::vector<std::filesystem::path> candidates = { c, root + "/dev/fluids/" + c + ".json" };
            std::filesystem::path selected_path = "";
            for (auto candidate : candidates) {
    
                if (std::filesystem::is_regular_file(candidate)) {
    
                    selected_path = candidate;
                    break;
                }
            }
            if (selected_path != "") {
    
                out.push_back(load_a_JSON_file(selected_path.string()));
    
                throw std::invalid_argument("Could not load any of the candidates:" + c);
    
    inline auto collect_identifiers(const std::vector<nlohmann::json>& pureJSON)
    {
    
        std::vector<std::string> CAS, Name, REFPROP;
    
        for (auto j : pureJSON) {
    
            Name.push_back(j.at("INFO").at("NAME"));
            CAS.push_back(j.at("INFO").at("CAS"));
            REFPROP.push_back(j.at("INFO").at("REFPROP_NAME"));
    
        return std::map<std::string, std::vector<std::string>>{
            {"CAS", CAS},
            {"Name", Name},
            {"REFPROP", REFPROP}
        };
    
    /// Iterate over the possible options for identifiers to determine which one will satisfy all the binary pairs
    template<typename mapvecstring>
    inline auto select_identifier(const nlohmann::json& BIPcollection, const mapvecstring& identifierset, const nlohmann::json& flags){
        for (const auto &ident: identifierset){
            std::string key; std::vector<std::string> identifiers;
            std::tie(key, identifiers) = ident;
            try{
                for (auto i = 0; i < identifiers.size(); ++i){
                    for (auto j = i+1; j < identifiers.size(); ++j){
                        const std::vector<std::string> pair = {identifiers[i], identifiers[j]};
    
                        reducing::get_BIPdep(BIPcollection, pair, flags);
    
                    }
                }
                return key;
            }
            catch(...){
                
            }
        }
        throw std::invalid_argument("Unable to match any of the identifier options");
    }
    
    /// Build a reverse-lookup map for finding a fluid JSON structure given a backup identifier
    inline auto build_alias_map(const std::string& root) {
        std::map<std::string, std::string> aliasmap;
        for (auto path : get_files_in_folder(root + "/dev/fluids", ".json")) {
            auto j = load_a_JSON_file(path.string());
    
            std::string REFPROP_name = j.at("INFO").at("REFPROP_NAME"); 
            std::string name = j.at("INFO").at("NAME");
    
            for (std::string k : {"NAME", "CAS", "REFPROP_NAME"}) {
    
                std::string val = j.at("INFO").at(k);
                // Skip REFPROP names that match the fluid itself
                if (k == "REFPROP_NAME" && val == name) {
                    continue;
                }
                // Skip invalid REFPROP names
                if (k == "REFPROP_NAME" && val == "N/A") {
                    continue;
                }
    
                    throw std::invalid_argument("Duplicated reverse lookup identifier ["+k+"] found in file:" + path.string());
    
                }
                else {
                    aliasmap[val] = std::filesystem::absolute(path).string();
                }
            }
            std::vector<std::string> aliases = j.at("INFO").at("ALIASES");
    
                if (alias != REFPROP_name && alias != name) { // Don't add REFPROP name or base name, were already above to list of aliases
                    if (aliasmap.count(alias) > 0) {
                        throw std::invalid_argument("Duplicated alias [" + alias + "] found in file:" + path.string());
                    }
                    else {
                        aliasmap[alias] = std::filesystem::absolute(path).string();
                    }
    
    /// Internal method for actually constructing the model with the provided JSON data structures
    inline auto _build_multifluid_model(const std::vector<nlohmann::json> &pureJSON, const nlohmann::json& BIPcollection, const nlohmann::json& depcollection, const nlohmann::json& flags = {}) {
    
    
        auto [Tc, vc] = reducing::get_Tcvc(pureJSON);
    
        auto EOSs = get_EOSs(pureJSON);
    
        // Extract the set of possible identifiers to be used to match parameters
        auto identifierset = collect_identifiers(pureJSON);
        // Decide which identifier is to be used (Name, CAS, REFPROP name)
        auto identifiers = identifierset[select_identifier(BIPcollection, identifierset, flags)];
    
        // Things related to the mixture
    
        auto F = reducing::get_F_matrix(BIPcollection, identifiers, flags);
    
        auto [funcs, funcsmeta] = get_departure_function_matrix(depcollection, BIPcollection, identifiers, flags);
    
        auto [betaT, gammaT, betaV, gammaV] = reducing::get_BIP_matrices(BIPcollection, identifiers, flags, Tc, vc);
    
        nlohmann::json meta = {
            {"pures", pureJSON},
            {"mix", funcsmeta},
        };
    
    
        auto redfunc = ReducingFunctions(std::move(MultiFluidReducingFunction(betaT, gammaT, betaV, gammaV, Tc, vc)));
    
        auto model = MultiFluid(
    
            CorrespondingStatesContribution(std::move(EOSs)),
            DepartureContribution(std::move(F), std::move(funcs))
    
        model.set_meta(meta.dump(1));
        return model;
    
    }
    
    /// A builder function where the JSON-formatted strings are provided explicitly rather than file paths
    inline auto build_multifluid_JSONstr(const std::vector<std::string>& componentJSON, const std::string& BIPJSON, const std::string& departureJSON, const nlohmann::json& flags = {}) {
    
        // Mixture things
        const auto BIPcollection = nlohmann::json::parse(BIPJSON);
        const auto depcollection = nlohmann::json::parse(departureJSON);
    
        // Pure fluids
        std::vector<nlohmann::json> pureJSON;
        for (auto& c : componentJSON) {
            pureJSON.emplace_back(nlohmann::json::parse(c));
        }
        return _build_multifluid_model(pureJSON, BIPcollection, depcollection, flags);
    }
    
    
    inline auto build_multifluid_model(const std::vector<std::string>& components, const std::string& coolprop_root, const std::string& BIPcollectionpath = {}, const nlohmann::json& flags = {}, const std::string& departurepath = {}) {
    
        std::string BIPpath = (BIPcollectionpath.empty()) ? coolprop_root + "/dev/mixtures/mixture_binary_pairs.json" : BIPcollectionpath;
        const auto BIPcollection = load_a_JSON_file(BIPpath);
    
        std::string deppath = (departurepath.empty()) ? coolprop_root + "/dev/mixtures/mixture_departure_functions.json" : departurepath;
    
        const auto depcollection = load_a_JSON_file(deppath);
    
        // Pure fluids
    
        std::vector<nlohmann::json> pureJSON;
        try {
            // Try the normal lookup, matching component name to a file in dev/fluids (case sensitive match on linux!)
            pureJSON = collect_component_json(components, coolprop_root);
        }
        catch(...){
            // Lookup the absolute paths for each component
            auto aliasmap = build_alias_map(coolprop_root);
            std::vector<std::string> abspaths;
            for (auto c : components) {
    
                // Allow matching of absolute paths first
    
                if (std::filesystem::is_regular_file(c)) {
    
                    abspaths.push_back(c);
                }
                else {
                    abspaths.push_back(aliasmap[c]);
                }
    
            }
            // Backup lookup with absolute paths resolved for each component
            pureJSON = collect_component_json(abspaths, coolprop_root);
        }
    
        return _build_multifluid_model(pureJSON, BIPcollection, depcollection, flags);
    }
    
    /**
    * \brief Load a model from a JSON data structure
    * 
    * Required fields are: components, BIP, departure
    * 
    * BIP and departure can be either the data in JSON format, or a path to file with those contents
    * components is an array, which either contains the paths to the JSON data, or the file path
    */
    inline auto multifluidfactory(const nlohmann::json& spec) {
        
        auto JSON_from_path_or_contents = [](const nlohmann::json &path_or_contents) -> nlohmann::json {
            if (path_or_contents.is_string()) {
                return load_a_JSON_file(path_or_contents.get<std::string>());
            }
            else {
                return path_or_contents;
            }
        };
    
        auto components = spec.at("components"); 
        auto depcollection = JSON_from_path_or_contents(spec.at("departure"));
        auto BIPcollection = JSON_from_path_or_contents(spec.at("BIP"));
        nlohmann::json flags = (spec.contains("flags")) ? spec.at("flags") : nlohmann::json();
    
        // Pure components
        std::vector<nlohmann::json> pureJSON;
        for (auto c : components) {
            pureJSON.push_back(JSON_from_path_or_contents(c));
        }
    
        return _build_multifluid_model(pureJSON, BIPcollection, depcollection, flags);
    }
    /// An overload of multifluidfactory that takes in a string
    inline auto multifluidfactory(const std::string& specstring) {
        return multifluidfactory(nlohmann::json::parse(specstring));
    }
    
    //class DummyEOS {
    //public:
    //    template<typename TType, typename RhoType> auto alphar(TType tau, const RhoType& delta) const { return tau * delta; }
    //};
    //class DummyReducingFunction {
    //public:
    //    template<typename MoleFractions> auto get_Tr(const MoleFractions& molefracs) const { return molefracs[0]; }
    //    template<typename MoleFractions> auto get_rhor(const MoleFractions& molefracs) const { return molefracs[0]; }
    //};
    //inline auto build_dummy_multifluid_model(const std::vector<std::string>& components) {
    //    std::vector<DummyEOS> EOSs(2);
    //    std::vector<std::vector<DummyEOS>> funcs(2); for (auto i = 0; i < funcs.size(); ++i) { funcs[i].resize(funcs.size()); }
    //    std::vector<std::vector<double>> F(2); for (auto i = 0; i < F.size(); ++i) { F[i].resize(F.size()); }
    //
    //    struct Fwrapper {
    //    private: 
    //        const std::vector<std::vector<double>> F_;
    //    public:
    //        Fwrapper(const std::vector<std::vector<double>> &F) : F_(F){};
    //        auto operator ()(std::size_t i, std::size_t j) const{ return F_[i][j]; }
    //    };
    //    auto ff = Fwrapper(F);
    //    auto redfunc = DummyReducingFunction();
    //    return MultiFluid(std::move(redfunc), std::move(CorrespondingStatesContribution(std::move(EOSs))), std::move(DepartureContribution(std::move(ff), std::move(funcs))));
    //}
    //inline void test_dummy() {
    //    auto model = build_dummy_multifluid_model({ "A", "B" });
    //    std::valarray<double> rhovec = { 1.0, 2.0 };
    //    auto alphar = model.alphar(300.0, rhovec);
    //}
    
    
    }; // namespace teqp