From bbac7dc5e1c2d10cc5f79054c8154e70f17d03f2 Mon Sep 17 00:00:00 2001 From: Ian Bell <ian.bell@nist.gov> Date: Tue, 1 Nov 2022 16:14:07 -0400 Subject: [PATCH] Transition more things into the AbstractModel framework --- include/teqp/algorithms/critical_tracing.hpp | 25 ++----------- .../algorithms/critical_tracing_types.hpp | 27 ++++++++++++++ include/teqp/models/fwd.hpp | 6 ++-- interface/CPP/teqpcpp.cpp | 36 ++++++++++++++++--- interface/CPP/teqpcpp.hpp | 22 ++++++++++-- interface/CPP/test_teqpcpp.cpp | 12 ++++++- 6 files changed, 95 insertions(+), 33 deletions(-) create mode 100644 include/teqp/algorithms/critical_tracing_types.hpp diff --git a/include/teqp/algorithms/critical_tracing.hpp b/include/teqp/algorithms/critical_tracing.hpp index 3cdf044..72c4e33 100644 --- a/include/teqp/algorithms/critical_tracing.hpp +++ b/include/teqp/algorithms/critical_tracing.hpp @@ -8,6 +8,7 @@ #include <Eigen/Dense> #include "teqp/algorithms/rootfinding.hpp" #include "teqp/algorithms/critical_pure.hpp" +#include "teqp/algorithms/critical_tracing_types.hpp" #include "teqp/exceptions.hpp" // Imports from boost @@ -16,28 +17,6 @@ #include <boost/numeric/odeint/stepper/euler.hpp> namespace teqp { -// This has to be outside the critical tracing struct so that the pybind11 wrapper doesn't fight with the types -struct TCABOptions { - double abs_err = 1.0e-6, - rel_err = 1.0e-6, - init_dt = 10, ///< The initial step size - max_dt = 10000000000, - T_tol = 1e-6, ///< The tolerance on temperature to indicate that it is converged - init_c = 1.0; ///< The c parameter which controls the initial search direction for the first step. Choices are 1 or -1 - int small_T_count = 5; ///< How many small temperature steps indicates convergence - int integration_order = 5; ///< The order of integration, either 1 for simple Euler or 5 for adaptive RK45 - int max_step_count = 1000; ///< Maximum number of steps allowed - int skip_dircheck_count = 1; ///< Only start checking the direction dot product after this many steps - bool polish = false; ///< If true, polish the solution at every step - double polish_reltol_T = 0.01; ///< The maximum allowed change in temperature when polishing - double polish_reltol_rho = 0.05; ///< The maximum allowed change in any molar concentration when polishing - bool terminate_negative_density = true; ///< Stop the tracing if the density is negative - bool calc_stability = false; ///< Calculate the local stability with the method of Deiters and Bell - double stability_rel_drho = 0.001; ///< The relative size of the step (relative to the sum of the molar concentration vector) to be used when taking the step in the direction of \f$\sigma_1\f$ when assessing local stability - int verbosity = 0; ///< The greater the verbosity, the more output you will get, especially about polishing failures - bool polish_exception_on_fail = false; ///< If true, when polishing fails, throw an exception, otherwise, terminate tracing - bool pure_endpoint_polish = false; ///< If true, if the last step crossed into negative concentrations, try to interpolate to find the pure fluid endpoint hiding in the data -}; template<typename Model, typename Scalar = double, typename VecType = Eigen::ArrayXd> struct CriticalTracing { @@ -776,4 +755,4 @@ struct CriticalTracing { }; // namespace VecType -}; // namespace teqp \ No newline at end of file +}; // namespace teqp diff --git a/include/teqp/algorithms/critical_tracing_types.hpp b/include/teqp/algorithms/critical_tracing_types.hpp new file mode 100644 index 0000000..48703d7 --- /dev/null +++ b/include/teqp/algorithms/critical_tracing_types.hpp @@ -0,0 +1,27 @@ +# pragma once + +namespace teqp { + +struct TCABOptions { + double abs_err = 1.0e-6, + rel_err = 1.0e-6, + init_dt = 10, ///< The initial step size + max_dt = 10000000000, + T_tol = 1e-6, ///< The tolerance on temperature to indicate that it is converged + init_c = 1.0; ///< The c parameter which controls the initial search direction for the first step. Choices are 1 or -1 + int small_T_count = 5; ///< How many small temperature steps indicates convergence + int integration_order = 5; ///< The order of integration, either 1 for simple Euler or 5 for adaptive RK45 + int max_step_count = 1000; ///< Maximum number of steps allowed + int skip_dircheck_count = 1; ///< Only start checking the direction dot product after this many steps + bool polish = false; ///< If true, polish the solution at every step + double polish_reltol_T = 0.01; ///< The maximum allowed change in temperature when polishing + double polish_reltol_rho = 0.05; ///< The maximum allowed change in any molar concentration when polishing + bool terminate_negative_density = true; ///< Stop the tracing if the density is negative + bool calc_stability = false; ///< Calculate the local stability with the method of Deiters and Bell + double stability_rel_drho = 0.001; ///< The relative size of the step (relative to the sum of the molar concentration vector) to be used when taking the step in the direction of \f$\sigma_1\f$ when assessing local stability + int verbosity = 0; ///< The greater the verbosity, the more output you will get, especially about polishing failures + bool polish_exception_on_fail = false; ///< If true, when polishing fails, throw an exception, otherwise, terminate tracing + bool pure_endpoint_polish = false; ///< If true, if the last step crossed into negative concentrations, try to interpolate to find the pure fluid endpoint hiding in the data +}; + +} diff --git a/include/teqp/models/fwd.hpp b/include/teqp/models/fwd.hpp index 9a65966..1894738 100644 --- a/include/teqp/models/fwd.hpp +++ b/include/teqp/models/fwd.hpp @@ -21,12 +21,14 @@ namespace teqp { using vad = std::valarray<double>; + using PCSAFTType = decltype(PCSAFT::PCSAFTfactory(nlohmann::json{})); + // Define the EOS types by interrogating the types returned by the respective factory function using AllowedModels = std::variant< vdWEOS1, decltype(canonical_PR(vad{}, vad{}, vad{})), decltype(CPA::CPAfactory(nlohmann::json{})), - decltype(PCSAFT::PCSAFTfactory(nlohmann::json{})), + PCSAFTType, decltype(multifluidfactory(nlohmann::json{})) >; -} \ No newline at end of file +} diff --git a/interface/CPP/teqpcpp.cpp b/interface/CPP/teqpcpp.cpp index 2e309a9..ecac905 100644 --- a/interface/CPP/teqpcpp.cpp +++ b/interface/CPP/teqpcpp.cpp @@ -2,6 +2,7 @@ #include "teqp/derivs.hpp" #include "teqp/json_builder.hpp" #include "teqp/algorithms/critical_tracing.hpp" +#include "teqp/algorithms/VLE.hpp" using namespace teqp; using namespace teqp::cppinterface; @@ -12,21 +13,48 @@ namespace teqp { class ModelImplementer : public AbstractModel { protected: const AllowedModels m_model; + public: ModelImplementer(AllowedModels&& model) : m_model(model) {}; - double get_Arxy(const int NT, const int ND, const double T, const double rho, const Eigen::ArrayXd& molefracs) const override { + double get_Arxy(const int NT, const int ND, const double T, const double rho, const EArrayd& molefracs) const override { return std::visit([&](const auto& model) { - using tdx = teqp::TDXDerivatives<decltype(model), double, Eigen::ArrayXd>; + using tdx = teqp::TDXDerivatives<decltype(model), double, EArrayd>; return tdx::template get_Ar(NT, ND, model, T, rho, molefracs); }, m_model); } - nlohmann::json trace_critical_arclength_binary(const double T0, const Eigen::ArrayXd& rhovec0) const override { + nlohmann::json trace_critical_arclength_binary(const double T0, const EArrayd& rhovec0, const std::optional<std::string>& filename_, const std::optional<TCABOptions> &options_) const override { return std::visit([&](const auto& model) { using crit = teqp::CriticalTracing<decltype(model), double, std::decay_t<decltype(rhovec0)>>; return crit::trace_critical_arclength_binary(model, T0, rhovec0, ""); }, m_model); } + EArray2 pure_VLE_T(const double T, const double rhoL, const double rhoV, int maxiter) const override { + return std::visit([&](const auto& model) { + return teqp::pure_VLE_T(model, T, rhoL, rhoV, maxiter); + }, m_model); + } + EArrayd get_fugacity_coefficients(const double T, const EArrayd& rhovec) const override { + return std::visit([&](const auto& model) { + using id = IsochoricDerivatives<decltype(model), double, EArrayd>; + return id::get_fugacity_coefficients(model, T, rhovec); + }, m_model); + } + + + // Methods only available for PC-SAFT + EArrayd get_m() const override { + return std::get<PCSAFTType>(m_model).get_m(); + } + EArrayd get_sigma_Angstrom() const override { + return std::get<PCSAFTType>(m_model).get_sigma_Angstrom(); + } + EArrayd get_epsilon_over_k_K() const override { + return std::get<PCSAFTType>(m_model).get_m(); + } + double max_rhoN(const double T, const EArrayd& z) const override { + return std::get<PCSAFTType>(m_model).max_rhoN(T, z); + } }; std::unique_ptr<AbstractModel> make_model(const nlohmann::json& j) { @@ -37,4 +65,4 @@ namespace teqp { return std::make_unique<ModelImplementer>(build_multifluid_model(components, coolprop_root, BIPcollectionpath, flags, departurepath)); } } -} \ No newline at end of file +} diff --git a/interface/CPP/teqpcpp.hpp b/interface/CPP/teqpcpp.hpp index cd8b3e7..b86440f 100644 --- a/interface/CPP/teqpcpp.hpp +++ b/interface/CPP/teqpcpp.hpp @@ -4,13 +4,29 @@ #include <Eigen/Dense> #include "nlohmann/json.hpp" +// The only headers that can be included here are +// ones that define and use POD (plain ole' data) types +#include "teqp/algorithms/critical_tracing_types.hpp" + +using EArray2 = Eigen::Array<double, 2, 1>; +using EArrayd = Eigen::ArrayX<double>; + namespace teqp { namespace cppinterface { class AbstractModel { public: - virtual double get_Arxy(const int, const int, const double, const double, const Eigen::ArrayXd&) const = 0; - virtual nlohmann::json trace_critical_arclength_binary(const double T0, const Eigen::ArrayXd& rhovec0) const = 0; + virtual double get_Arxy(const int, const int, const double, const double, const EArrayd&) const = 0; + virtual nlohmann::json trace_critical_arclength_binary(const double T0, const EArrayd& rhovec0, const std::optional<std::string>&, const std::optional<TCABOptions> &) const = 0; + virtual EArray2 pure_VLE_T(const double T, const double rhoL, const double rhoV, int maxiter) const = 0; + virtual EArrayd get_fugacity_coefficients(const double T, const EArrayd& rhovec) const = 0; + + // Methods only available for PCSAFT + virtual EArrayd get_m() const = 0; + virtual EArrayd get_sigma_Angstrom() const = 0; + virtual EArrayd get_epsilon_over_k_K() const = 0; + virtual double max_rhoN(const double, const EArrayd&) const = 0; + virtual ~AbstractModel() = default; }; @@ -27,4 +43,4 @@ namespace teqp { const std::string& departurepath = {} ); } -} \ No newline at end of file +} diff --git a/interface/CPP/test_teqpcpp.cpp b/interface/CPP/test_teqpcpp.cpp index 801c4e1..7ddb9ba 100644 --- a/interface/CPP/test_teqpcpp.cpp +++ b/interface/CPP/test_teqpcpp.cpp @@ -26,4 +26,14 @@ int main() { auto z = (Eigen::ArrayXd(2) << 0.5, 0.5).finished(); double Ar01 = am->get_Arxy(0, 1, 300, 3, z); std::cout << Ar01 << std::endl; -} \ No newline at end of file + + auto fugcoeff = am->get_fugacity_coefficients(300.0, z*300.0); + std::cout << fugcoeff << std::endl; + + try{ + std::cout << am->get_m() << std::endl; + } + catch(...){ + std::cout << "This fails, as it should because the model is not a PCSAFT one" << std::endl; + } +} -- GitLab