From b594419848b92ddcc7dbc309e93840ffa6f00e6f Mon Sep 17 00:00:00 2001 From: Hassan Date: Fri, 29 Nov 2024 14:14:17 +0900 Subject: [PATCH 1/3] Hassan-Update --- src/README.pdf | Bin 0 -> 62792 bytes src/bindings.cpp | 21 +- src/breakthrough.cpp | 1683 +++++++++++++++++++++-------------- src/breakthrough.h | 388 +++----- src/clean.sh | 1 + src/component.cpp | 29 +- src/component.h | 65 +- src/fitting.cpp | 519 ++++++----- src/fitting.h | 274 ++---- src/hash_combine.h | 14 +- src/inputreader.cpp | 163 ++-- src/inputreader.h | 95 +- src/isotherm.cpp | 110 +-- src/isotherm.h | 270 ++---- src/main.cpp | 21 +- src/makefile | 4 +- src/mixture_prediction.cpp | 748 ++++++++-------- src/mixture_prediction.h | 507 +++-------- src/multi_site_isotherm.cpp | 72 +- src/multi_site_isotherm.h | 214 +---- src/random_numbers.h | 71 +- src/simulation.input | 40 + src/special_functions.cpp | 239 ++--- src/special_functions.h | 11 +- 24 files changed, 2578 insertions(+), 2981 deletions(-) create mode 100644 src/README.pdf create mode 100644 src/clean.sh create mode 100644 src/simulation.input diff --git a/src/README.pdf b/src/README.pdf new file mode 100644 index 0000000000000000000000000000000000000000..87d1d69921dc2d616093cf7d0d1e3b4f262dcf51 GIT binary patch literal 62792 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

!cOZNiQnJ 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, "Component") .def(py::init, double, double, double, bool>()) - .def_readonly("gasPhaseMolFraction", &Component::Yi0) - .def_readonly("name", &Component::name) + .def_readonly("GasPhaseMolFraction", &Component::Yi0) + .def_readonly("MoleculeName", &Component::name) .def("__repr__", &Component::repr); py::class_(m, "Isotherm") - .def(py::init, size_t>()) + .def(py::init, size_t>()) .def("__repr__", &Isotherm::repr); py::class_(m, "MixturePrediction") .def(py::init, size_t, size_t, double, double, double, size_t, size_t, size_t, size_t>()) - .def("getComponentsParameters", &MixturePrediction::getComponentsParameters) - .def("setComponentsParameters", &MixturePrediction::setComponentsParameters) - .def("setPressure", &MixturePrediction::setPressure) - .def("compute", &MixturePrediction::compute) - .def("__repr__", &MixturePrediction::repr); + .def("__repr__", &MixturePrediction::repr) + .def("compute", &MixturePrediction::compute); py::class_(m, "Breakthrough") .def(py::init, size_t, size_t, size_t, size_t, double, double, double, double, double, double, double, double, size_t, bool, bool, double, const MixturePrediction>()) - .def("getComponentsParameters", &Breakthrough::getComponentsParameters) - .def("setComponentsParameters", &Breakthrough::setComponentsParameters) - .def("compute", &Breakthrough::compute) - .def("__repr__", &Breakthrough::repr); + .def("__repr__", &Breakthrough::repr) + .def("compute", &Breakthrough::compute); py::class_(m, "Fitting") - .def(py::init, std::vector>, size_t>()) + .def(py::init, size_t>()) .def("evaluate", &Fitting::evaluate) .def("compute", &Fitting::compute); } diff --git a/src/breakthrough.cpp b/src/breakthrough.cpp index b492059..15e8229 100644 --- a/src/breakthrough.cpp +++ b/src/breakthrough.cpp @@ -1,103 +1,102 @@ -#include #include -#include +#include #include +#include #include +#include #include #include -#include #if __cplusplus >= 201703L && __has_include() -#include + #include #elif __cplusplus >= 201703L && __has_include() -#include + #include #else -#include + #include #endif #include "breakthrough.h" -#include "mixture_prediction.h" -#ifdef PYBUILD -#include -#include -namespace py = pybind11; -#endif // PYBUILD - -const double R = 8.31446261815324; +const double R=8.31446261815324; inline double maxVectorDifference(const std::vector &v, const std::vector &w) { - if (v.empty() || w.empty()) return 0.0; - if (v.size() != w.size()) throw std::runtime_error("Error: unequal vector size\n"); + if(v.empty() || w.empty()) return 0.0; + if(v.size() != w.size()) throw std::runtime_error("Error: unequal vector size\n"); double max = std::abs(v[0] - w[0]); - for (size_t i = 1; i < v.size(); ++i) + for(size_t i = 1; i < v.size(); ++i) { double temp = std::abs(v[i] - w[i]); - if (temp > max) max = temp; + if(temp > max) max = temp; } return max; } + // allow std::pairs to be added -template -std::pair operator+(const std::pair &l, const std::pair &r) -{ - return {l.first + r.first, l.second + r.second}; +template +std::pair operator+(const std::pair & l,const std::pair & r) { + return {l.first+r.first,l.second+r.second}; } template -std::pair &operator+=(std::pair &l, const std::pair &r) -{ - l.first += r.first; - l.second += r.second; - return l; +std::pair &operator+=(std::pair & l, const std::pair & r) { + l.first += r.first; + l.second += r.second; + return l; } - -Breakthrough::Breakthrough(const InputReader &inputReader) - : displayName(inputReader.displayName), - components(inputReader.components), - carrierGasComponent(inputReader.carrierGasComponent), - Ncomp(components.size()), - Ngrid(inputReader.numberOfGridPoints), - printEvery(inputReader.printEvery), - writeEvery(inputReader.writeEvery), - T(inputReader.temperature), - p_total(inputReader.totalPressure), - dptdx(inputReader.pressureGradient), - epsilon(inputReader.columnVoidFraction), - rho_p(inputReader.particleDensity), - v_in(inputReader.columnEntranceVelocity), - L(inputReader.columnLength), - dx(L / static_cast(Ngrid)), - dt(inputReader.timeStep), - Nsteps(inputReader.numberOfTimeSteps), - autoSteps(inputReader.autoNumberOfTimeSteps), - pulse(inputReader.pulseBreakthrough), - tpulse(inputReader.pulseTime), - mixture(inputReader), - maxIsothermTerms(inputReader.maxIsothermTerms), - prefactor(Ncomp), - Yi(Ncomp), - Xi(Ncomp), - Ni(Ncomp), - V(Ngrid + 1), - Vnew(Ngrid + 1), - Pt(Ngrid + 1), - P((Ngrid + 1) * Ncomp), - Pnew((Ngrid + 1) * Ncomp), - Q((Ngrid + 1) * Ncomp), - Qnew((Ngrid + 1) * Ncomp), - Qeq((Ngrid + 1) * Ncomp), - Qeqnew((Ngrid + 1) * Ncomp), - Dpdt((Ngrid + 1) * Ncomp), - Dpdtnew((Ngrid + 1) * Ncomp), - Dqdt((Ngrid + 1) * Ncomp), - Dqdtnew((Ngrid + 1) * Ncomp), - cachedP0((Ngrid + 1) * Ncomp * maxIsothermTerms), - cachedPsi((Ngrid + 1) * maxIsothermTerms) +// Constructor declaration with initializer list +Breakthrough::Breakthrough(const InputReader &inputReader): + displayName(inputReader.displayName), + components(inputReader.components), + carrierGasComponent(inputReader.carrierGasComponent), + Ncomp(components.size()), + Ngrid(inputReader.numberOfGridPoints), + printEvery(inputReader.printEvery), + writeEvery(inputReader.writeEvery), + T_gas(inputReader.temperature), + p_total(inputReader.totalPressure), + dptdx(inputReader.pressureGradient), + epsilon(inputReader.columnVoidFraction), + rho_p(inputReader.particleDensity), + v_in(inputReader.columnEntranceVelocity), + L(inputReader.columnLength), + dx(L / static_cast(Ngrid)), + dt(inputReader.timeStep), + Nsteps(inputReader.numberOfTimeSteps), + autoSteps(inputReader.autoNumberOfTimeSteps), + pulse(inputReader.pulseBreakthrough), + tpulse(inputReader.pulseTime), + mixture(inputReader), + maxIsothermTerms(inputReader.maxIsothermTerms), + prefactor(Ncomp), + Yi(Ncomp), + Xi(Ncomp), + Ni(Ncomp), + V(Ngrid+1), + Vnew(Ngrid+1), + Pt(Ngrid+1), + T(Ngrid + 1), + Tnew(Ngrid + 1), + DTdt(Ngrid + 1), + DTdtnew(Ngrid + 1), + P(Ngrid + 1), + Pnew(Ngrid + 1), + DPdt(Ngrid + 1), + DPdtnew(Ngrid + 1), + y((Ngrid + 1) * Ncomp), + ynew((Ngrid + 1) * Ncomp), + Dydt((Ngrid + 1) * Ncomp), + Dydtnew((Ngrid + 1) * Ncomp), + Q((Ngrid + 1) * Ncomp), + Qnew((Ngrid + 1) * Ncomp), + Qeq((Ngrid + 1) * Ncomp), + Qeqnew((Ngrid + 1) * Ncomp), + Dqdt((Ngrid + 1) * Ncomp), + Dqdtnew((Ngrid + 1) * Ncomp), + cachedP0((Ngrid + 1) * Ncomp * maxIsothermTerms), + cachedPsi((Ngrid + 1) * maxIsothermTerms) { } - Breakthrough::Breakthrough(std::string _displayName, std::vector _components, size_t _carrierGasComponent, size_t _numberOfGridPoints, size_t _printEvery, size_t _writeEvery, double _temperature, double _p_total, double _columnVoidFraction, double _pressureGradient, @@ -105,13 +104,13 @@ Breakthrough::Breakthrough(std::string _displayName, std::vector _com double _timeStep, size_t _numberOfTimeSteps, bool _autoSteps, bool _pulse, double _pulseTime, const MixturePrediction _mixture) : displayName(_displayName), - components(_components), + components(normalize_molfracs(_components)), carrierGasComponent(_carrierGasComponent), Ncomp(_components.size()), Ngrid(_numberOfGridPoints), printEvery(_printEvery), writeEvery(_writeEvery), - T(_temperature), + T_gas(_temperature), p_total(_p_total), dptdx(_pressureGradient), epsilon(_columnVoidFraction), @@ -125,7 +124,7 @@ Breakthrough::Breakthrough(std::string _displayName, std::vector _com pulse(_pulse), tpulse(_pulseTime), mixture(_mixture), - maxIsothermTerms(mixture.getMaxIsothermTerms()), + maxIsothermTerms(mixture.maxIsothermTerms), prefactor(Ncomp), Yi(Ncomp), Xi(Ncomp), @@ -133,99 +132,156 @@ Breakthrough::Breakthrough(std::string _displayName, std::vector _com V(Ngrid + 1), Vnew(Ngrid + 1), Pt(Ngrid + 1), - P((Ngrid + 1) * Ncomp), - Pnew((Ngrid + 1) * Ncomp), + T(Ngrid + 1), + Tnew(Ngrid + 1), + DTdt(Ngrid + 1), + DTdtnew(Ngrid + 1), + P(Ngrid + 1), + Pnew(Ngrid + 1), + DPdt(Ngrid + 1), + DPdtnew(Ngrid + 1), + y((Ngrid + 1) * Ncomp), + ynew((Ngrid + 1) * Ncomp), + Dydt((Ngrid + 1) * Ncomp), + Dydtnew((Ngrid + 1) * Ncomp), Q((Ngrid + 1) * Ncomp), Qnew((Ngrid + 1) * Ncomp), Qeq((Ngrid + 1) * Ncomp), Qeqnew((Ngrid + 1) * Ncomp), - Dpdt((Ngrid + 1) * Ncomp), - Dpdtnew((Ngrid + 1) * Ncomp), Dqdt((Ngrid + 1) * Ncomp), Dqdtnew((Ngrid + 1) * Ncomp), cachedP0((Ngrid + 1) * Ncomp * maxIsothermTerms), cachedPsi((Ngrid + 1) * maxIsothermTerms) { + // normally ran in main.cpp, now run by default initialize(); } void Breakthrough::initialize() { + // Hassan Properties and Parameters + K_z = 0.09; + C_ps = 750.0; + C_pg = 35.8; + C_pa = 35.8; + mu = 1.13e-05; + r_p = 5.0e-03; + Q_s0 = 3.0; + MW = {0.004, 0.044, 0.028}; + + sat_q_b = {0.00, 2.74, 3.12}; + sat_q_d = {0.00, 3.11, 0.00}; + b_0 = {0.00, 2.00e-3, 4.87e-6}; + d_0 = {0.00, 2.70e-5, 0.00}; + del_H = {0.0, -38.87e3, -20.79e3}; + T_ref = 273.00; + + // Initialize nondimensional time step and grid + dt = dt * v_in/L; + dx = dx / L; // precomputed factor for mass transfer - for (size_t j = 0; j < Ncomp; ++j) - { - prefactor[j] = R * T * ((1.0 - epsilon) / epsilon) * rho_p * components[j].Kl; - } + // Hassan: Directly computed at runtime for every iteration because of variable temperature + // for(size_t j = 0; j < Ncomp; ++j) + // { + // prefactor[j] = R * T * ((1.0 - epsilon) / epsilon) * rho_p * components[j].Kl; + // } // set P and Q to zero - std::fill(P.begin(), P.end(), 0.0); + // std::fill(P.begin(), P.end(), 0.0); std::fill(Q.begin(), Q.end(), 0.0); + // Hassan: Initialize the temperature and mole fractions + std::fill(T.begin(), T.end(), T_gas/T_gas); // Equal to T_gas + std::fill(y.begin(), y.end(), 0.0); + // initial pressure along the column std::vector pt_init(Ngrid + 1); // set the initial total pressure along the column assuming the pressure gradient is constant - for (size_t i = 0; i < Ngrid + 1; ++i) + for(size_t i = 0; i < Ngrid + 1; ++i) { - pt_init[i] = p_total + dptdx * static_cast(i) * dx; + pt_init[i] = (p_total + dptdx * static_cast(i) * dx*L) / p_total; } // initialize the interstitial gas velocity in the column - for (size_t i = 0; i < Ngrid + 1; ++i) + for(size_t i = 0; i < Ngrid + 1; ++i) { - V[i] = v_in * p_total / pt_init[i]; + // V[i] = v_in * p_total / pt_init[i]; + // V[i] = (v_in * 1 / pt_init[i]) / v_in; + V[i] = 0.0; } + V[0] = v_in/v_in; - // set the partial pressure of the carrier gas to the total initial pressure + // set the molefraction of the carrier gas equal to 1.0, as column is initially filled with carrier gas only. // for the column except for the entrance (i=0) - for (size_t i = 1; i < Ngrid + 1; ++i) + for(size_t i = 1; i < Ngrid + 1; ++i) { - P[i * Ncomp + carrierGasComponent] = pt_init[i]; + y[i * Ncomp + carrierGasComponent] = pt_init[i]/pt_init[i]; } // at the column entrance, the mol-fractions of the components in the gas phase are fixed - // the partial pressures of the components at the entrance are the mol-fractions times the + // the partial pressures of the components at the entrance are the mol-fractions times the // total pressure - for (size_t j = 0; j < Ncomp; ++j) + for(size_t j = 0; j < Ncomp; ++j) { - P[0 * Ncomp + j] = p_total * components[j].Yi0; + // P[0 * Ncomp + j] = p_total * components[j].Yi0; + y[0 * Ncomp + j] = components[j].Yi0; } // at the entrance: mol-fractions Yi are the gas-phase mol-fractions // for the column: the initial mol-fraction of the carrier-gas is 1, and 0 for the other components // - // the K of the carrier gas is chosen as zero + // the K of the carrier gas is chosen as zero // so Qeq is zero for all components in the column after the entrance // only the values for Yi at the entrance are effected by adsorption - for (size_t i = 0; i < Ngrid + 1; ++i) + for(size_t i = 0; i < Ngrid + 1; ++i) { - double sum = 0.0; - for (size_t j = 0; j < Ncomp; ++j) + // double sum = 0.0; + // for(size_t j = 0; j < Ncomp; ++j) + // { + // Yi[j] = std::max(P[i * Ncomp + j] / pt_init[i], 0.0); + // sum += Yi[j]; + // } + for(size_t j = 0; j < Ncomp; ++j) { - Yi[j] = std::max(P[i * Ncomp + j] / pt_init[i], 0.0); - sum += Yi[j]; - } - for (size_t j = 0; j < Ncomp; ++j) - { - Yi[j] /= sum; + Yi[j] = std::max(y[i * Ncomp + j], 0.0); } - iastPerformance += mixture.predictMixture(Yi, pt_init[i], Xi, Ni, &cachedP0[i * Ncomp * maxIsothermTerms], - &cachedPsi[i * maxIsothermTerms]); + // iastPerformance += mixture.predictMixture(Yi, pt_init[i], Xi, Ni, + // &cachedP0[i * Ncomp * maxIsothermTerms], &cachedPsi[i * maxIsothermTerms]); - for (size_t j = 0; j < Ncomp; ++j) + + iastPerformance += mixture.predictMixture(Yi, pt_init[i]*p_total, Xi, Ni, + &cachedP0[i * Ncomp * maxIsothermTerms], &cachedPsi[i * maxIsothermTerms]); + + for(size_t j = 0; j < Ncomp; ++j) { Qeq[i * Ncomp + j] = Ni[j]; } } - for (size_t i = 0; i < Ngrid + 1; ++i) + for(size_t i = 0; i < Ngrid + 1; ++i) { - Pt[i] = 0.0; - for (size_t j = 0; j < Ncomp; ++j) - { - Pt[i] += std::max(0.0, P[i * Ncomp + j]); - } + // Pt[i] = 0.0; + // for(size_t j = 0; j < Ncomp; ++j) + // { + // Pt[i] += std::max(0.0, P[i * Ncomp + j]); + // } + // Initial pressure profile is same as pt_init + P[i] += std::max(pt_init[i], 0.0); + } + + // check the MW vector size, it should not be less or more than Ncomp + size_t length = MW.size(); + if (length != Ncomp) + { + throw std::runtime_error("Error: Mismatch in MW vector and Ncomp\n"); + } + + length = del_H.size(); + if (length != Ncomp) + { + throw std::runtime_error("Error: Mismatch in MW vector and Ncomp\n"); } } @@ -242,18 +298,21 @@ void Breakthrough::run() std::ofstream movieStream("column.data"); size_t column_nr = 1; - movieStream << "# column " << column_nr++ << ": z (column position)" << std::endl; - movieStream << "# column " << column_nr++ << ": V (velocity)" << std::endl; - movieStream << "# column " << column_nr++ << ": Pt (total pressure)" << std::endl; + movieStream << "# column " << column_nr++ << ": z (column position)\n"; + movieStream << "# column " << column_nr++ << ": V (velocity)\n"; + movieStream << "# column " << column_nr++ << ": P (total pressure)\n"; + movieStream << "# column " << column_nr++ << ": T (Gas Temperature)\n"; + movieStream << "# column " << column_nr++ << ": DPdt (derivative P with t)\n"; + movieStream << "# column " << column_nr++ << ": DTdt (derivative T with t)\n"; + for (size_t j = 0; j < Ncomp; ++j) { - movieStream << "# column " << column_nr++ << ": component " << j << " Q (loading) " << std::endl; - movieStream << "# column " << column_nr++ << ": component " << j << " Qeq (equilibrium loading)" << std::endl; - movieStream << "# column " << column_nr++ << ": component " << j << " P (partial pressure)" << std::endl; - movieStream << "# column " << column_nr++ << ": component " << j << " Pnorm (normalized partial pressure)" - << std::endl; - movieStream << "# column " << column_nr++ << ": component " << j << " Dpdt (derivative P with t)" << std::endl; - movieStream << "# column " << column_nr++ << ": component " << j << " Dqdt (derivative Q with t)" << std::endl; + movieStream << "# column " << column_nr++ << ": component " << j << " Q (loading) \n"; + movieStream << "# column " << column_nr++ << ": component " << j << " Qeq (equilibrium loading)\n"; + movieStream << "# column " << column_nr++ << ": component " << j << " y (mole fraction)\n"; + movieStream << "# column " << column_nr++ << ": component " << j << " ynorm (normalized mole fraction)\n"; + movieStream << "# column " << column_nr++ << ": component " << j << " Dydt (derivative y with t)\n"; + movieStream << "# column " << column_nr++ << ": component " << j << " Dqdt (derivative Q with tn\n"; } for (size_t step = 0; (step < Nsteps || autoSteps); ++step) @@ -268,23 +327,31 @@ void Breakthrough::run() // write breakthrough output to files // column 1: dimensionless time // column 2: time [minutes] - // column 3: normalized partial pressure + // column 3: normalized mole fraction for (size_t j = 0; j < Ncomp; ++j) { - streams[j] << t * v_in / L << " " << t / 60.0 << " " - << P[Ngrid * Ncomp + j] / ((p_total + dptdx * L) * components[j].Yi0) << std::endl; + // streams[j] << t * v_in / L << " " << t / 60.0 << " " + // << P[Ngrid * Ncomp + j] / ((p_total + dptdx * L) * components[j].Yi0) << std::endl; + // streams[j] << t * v_in / L << " " << t / 60.0 << " " + // << y[Ngrid * Ncomp + j] / components[j].Yi0 << std::endl; + streams[j] << t * L / v_in << " " << t * L / v_in / 60.0 << " " + << y[Ngrid * Ncomp + j] / components[j].Yi0 << std::endl; } for (size_t i = 0; i < Ngrid + 1; ++i) { - movieStream << static_cast(i) * dx << " "; - movieStream << V[i] << " "; - movieStream << Pt[i] << " "; + movieStream << static_cast(i) * dx*L << " "; + movieStream << V[i] * v_in << " "; + movieStream << P[i] * p_total<< " "; + movieStream << T[i] * T_gas<< " "; + movieStream << DPdt[i] * p_total*v_in/L<< " "; + movieStream << DTdt[i] * T_gas*v_in/L<< " "; + for (size_t j = 0; j < Ncomp; ++j) { - movieStream << Q[i * Ncomp + j] << " " << Qeq[i * Ncomp + j] << " " << P[i * Ncomp + j] << " " - << P[i * Ncomp + j] / (Pt[i] * components[j].Yi0) << " " << Dpdt[i * Ncomp + j] << " " - << Dqdt[i * Ncomp + j] << " "; + movieStream << Q[i * Ncomp + j] * Q_s0 << " " << Qeq[i * Ncomp + j] << " " << y[i * Ncomp + j] << " " + << y[i * Ncomp + j] / (components[j].Yi0) << " " << Dydt[i * Ncomp + j] * v_in/L<< " " + << Dqdt[i * Ncomp + j] * Q_s0*v_in/L << " "; } movieStream << "\n"; } @@ -293,7 +360,7 @@ void Breakthrough::run() if (step % printEvery == 0) { - std::cout << "Timestep " + std::to_string(step) + ", time: " + std::to_string(t) + " [s]" << std::endl; + std::cout << "Timestep " + std::to_string(step) + ", time: " + std::to_string(t) + " [s]\n"; std::cout << " Average number of mixture-prediction steps: " + std::to_string(static_cast(iastPerformance.first) / static_cast(iastPerformance.second)) @@ -302,108 +369,9 @@ void Breakthrough::run() } std::cout << "Final timestep " + std::to_string(Nsteps) + - ", time: " + std::to_string(dt * static_cast(Nsteps)) + " [s]" - << std::endl; + ", time: " + std::to_string(dt * static_cast(Nsteps)) + " [s]\n"; } -#ifdef PYBUILD - -py::array_t Breakthrough::compute() -{ - size_t colsize = 6 * Ncomp + 5; - std::vector>> brk; - - // loop can quit early if autoSteps - for (size_t step = 0; (step < Nsteps || autoSteps); ++step) - { - // check for error from python side (keyboard interrupt) - if (PyErr_CheckSignals() != 0) - { - throw py::error_already_set(); - } - - computeStep(step); - double t = static_cast(step) * dt; - if (step % writeEvery == 0) - { - std::vector> t_brk(Ngrid + 1, std::vector(colsize)); - for (size_t i = 0; i < Ngrid + 1; ++i) - { - t_brk[i][0] = t * v_in / L; - t_brk[i][1] = t / 60.0; - t_brk[i][2] = static_cast(i) * dx; - t_brk[i][3] = V[i]; - t_brk[i][4] = Pt[i]; - - for (size_t j = 0; j < Ncomp; ++j) - { - t_brk[i][5 + 6 * j] = Q[i * Ncomp + j]; - t_brk[i][6 + 6 * j] = Qeq[i * Ncomp + j]; - t_brk[i][7 + 6 * j] = P[i * Ncomp + j]; - t_brk[i][8 + 6 * j] = P[i * Ncomp + j] / (Pt[i] * components[j].Yi0); - t_brk[i][9 + 6 * j] = Dpdt[i * Ncomp + j]; - t_brk[i][10 + 6 * j] = Dqdt[i * Ncomp + j]; - } - } - brk.push_back(t_brk); - } - if (step % printEvery == 0) - { - std::cout << "Timestep " + std::to_string(step) + ", time: " + std::to_string(t) + " [s]" << std::endl; - std::cout << " Average number of mixture-prediction steps: " + - std::to_string(static_cast(iastPerformance.first) / - static_cast(iastPerformance.second)) - << std::endl; - } - } - std::cout << "Final timestep " + std::to_string(Nsteps) + - ", time: " + std::to_string(dt * static_cast(Nsteps)) + " [s]" - << std::endl; - - std::vector buffer; - buffer.reserve(brk.size() * (Ngrid + 1) * colsize); - for (const auto &vec1 : brk) - { - for (const auto &vec2 : vec1) - { - buffer.insert(buffer.end(), vec2.begin(), vec2.end()); - } - } - std::array shape{{brk.size(), Ngrid + 1, colsize}}; - py::array_t py_breakthrough(shape, buffer.data()); - py_breakthrough.resize(shape); - - return py_breakthrough; -} - -void Breakthrough::setComponentsParameters(std::vector molfracs, std::vector params) -{ - size_t index = 0; - for (size_t i = 0; i < Ncomp; ++i) - { - components[i].Yi0 = molfracs[i]; - size_t n_params = components[i].isotherm.numberOfParameters; - std::vector slicedVec(params.begin() + index, params.begin() + index + n_params); - index = index + n_params; - components[i].isotherm.setParameters(slicedVec); - } - - // also set for mixture - mixture.setComponentsParameters(molfracs, params); -} - -std::vector Breakthrough::getComponentsParameters() -{ - std::vector params; - for (size_t i = 0; i < Ncomp; ++i) - { - std::vector compParams = components[i].isotherm.getParameters(); - params.insert(params.end(), compParams.begin(), compParams.end()); - } - return params; -} -#endif // PYBUILD - void Breakthrough::computeStep(size_t step) { double t = static_cast(step) * dt; @@ -416,7 +384,7 @@ void Breakthrough::computeStep(size_t step) for (size_t j = 0; j < Ncomp; ++j) { tolerance = - std::max(tolerance, std::abs((P[Ngrid * Ncomp + j] / ((p_total + dptdx * L) * components[j].Yi0)) - 1.0)); + std::max(tolerance, std::abs((y[Ngrid * Ncomp + j] / (components[j].Yi0)) - 1.0)); } // consider 1% as being visibily indistinguishable from 'converged' @@ -433,7 +401,7 @@ void Breakthrough::computeStep(size_t step) // ====================================================================== // calculate the derivatives Dq/dt and Dp/dt based on Qeq, Q, V, and P - computeFirstDerivatives(Dqdt, Dpdt, Qeq, Q, V, P); + computeFirstDerivatives(Dqdt, DPdt, DTdt, Dydt, Qeq, Q, V, P, T, y); // Dqdt and Dpdt are calculated at old time step // make estimate for the new loadings and new gas phase partial pressures @@ -442,13 +410,33 @@ void Breakthrough::computeStep(size_t step) { for (size_t j = 0; j < Ncomp; ++j) { - Qnew[i * Ncomp + j] = Q[i * Ncomp + j] + dt * Dqdt[i * Ncomp + j]; - Pnew[i * Ncomp + j] = P[i * Ncomp + j] + dt * Dpdt[i * Ncomp + j]; + Qnew[i * Ncomp + j] = std::max(Q[i * Ncomp + j] + dt * Dqdt[i * Ncomp + j], 0.0); } + + // Loop for all components except Carrier gas + double sum_y = 0.0; + for (size_t j = 1; j < Ncomp; ++j) + { + ynew[i * Ncomp + j] = std::max(y[i * Ncomp + j] + dt * Dydt[i * Ncomp + j], 0.0); + ynew[i * Ncomp + j] = std::min(ynew[i * Ncomp + j], 1.0); + sum_y += ynew[i * Ncomp + j]; + } + + // Carrier gas mole fraction is computed using molefractions of other components + ynew[i * Ncomp + carrierGasComponent] = std::max((1.0 - sum_y), 0.0); + + // Add equation for Tnew and Pnew + Tnew[i] = std::max(T[i] + dt * DTdt[i], 0.0); + Pnew[i] = std::max(P[i] + dt * DPdt[i], 0.0); } computeEquilibriumLoadings(); - + for (size_t i; i tpulse) + if (t*L/v_in > tpulse) { for (size_t j = 0; j < Ncomp; ++j) { if (j == carrierGasComponent) { - P[0 * Ncomp + j] = p_total; + y[0 * Ncomp + j] = p_total/p_total; } else { - P[0 * Ncomp + j] = 0.0; + y[0 * Ncomp + j] = 0.0; } } } @@ -521,121 +550,408 @@ void Breakthrough::computeStep(size_t step) void Breakthrough::computeEquilibriumLoadings() { + // Hassan modification + // extended lagnmuir model instead of IAST + std::vector b(Ncomp); + std::vector d(Ncomp); + double den_b = 1.0; + double den_d = 1.0; + // calculate new equilibrium loadings Qeqnew corresponding to the new timestep - for (size_t i = 0; i < Ngrid + 1; ++i) + for(size_t i = 0; i < Ngrid + 1; ++i) { // estimation of total pressure Pt at each grid point from partial pressures - Pt[i] = 0.0; - for (size_t j = 0; j < Ncomp; ++j) - { - Pt[i] += std::max(0.0, Pnew[i * Ncomp + j]); - } + // Hassan: Pt is a dummy variable to be used only for mixture prediction + // Pt[i] = 0.0; + Pt[i] = std::max(0.0, Pnew[i]); + + // for(size_t j = 0; j < Ncomp; ++j) + // { + // Pt[i] += std::max(0.0, Pnew[i * Ncomp + j]); + // } // compute gas-phase mol-fractions // force the gas-phase mol-fractions to be positive and normalized - double sum = 0.0; - for (size_t j = 0; j < Ncomp; ++j) - { - Yi[j] = std::max(Pnew[i * Ncomp + j], 0.0); - sum += Yi[j]; - } - for (size_t j = 0; j < Ncomp; ++j) + // double sum = 0.0; + // for(size_t j = 0; j < Ncomp; ++j) + // { + // Yi[j] = std::max(Pnew[i * Ncomp + j], 0.0); + // sum += Yi[j]; + // } + + // for(size_t j = 0; j < Ncomp; ++j) + // { + // // Yi[j] /= sum; + // Yi[j] = std::max(ynew[i * Ncomp + j], 0.0); + // } + den_b = 1.0; + den_d = 1.0; + for(size_t j = 0; j < Ncomp; ++j) { - Yi[j] /= sum; + // Yi[j] /= sum; + b[j] = b_0[j] * std::exp(-del_H[j]/R * (1.0/Tnew[i]/T_gas - 1.0/T_ref)); + d[j] = d_0[j] * std::exp(-del_H[j]/R * (1.0/Tnew[i]/T_gas - 1.0/T_ref)); + + den_b += (b[j]*ynew[i * Ncomp + j]*Pt[i]*p_total); + den_d += (d[j]*ynew[i * Ncomp + j]*Pt[i]*p_total); } + // use Yi and Pt[i] to compute the loadings in the adsorption mixture via mixture prediction - iastPerformance += mixture.predictMixture(Yi, Pt[i], Xi, Ni, &cachedP0[i * Ncomp * maxIsothermTerms], - &cachedPsi[i * maxIsothermTerms]); + // iastPerformance += mixture.predictMixture(Yi, Pt[i]*p_total, Xi, Ni, + // &cachedP0[i * Ncomp * maxIsothermTerms], &cachedPsi[i * maxIsothermTerms]); - for (size_t j = 0; j < Ncomp; ++j) + for(size_t j = 0; j < Ncomp; ++j) { - Qeqnew[i * Ncomp + j] = Ni[j]; + // Qeqnew[i * Ncomp + j] = Ni[j]; + Qeqnew[i * Ncomp + j] = sat_q_b[j]*b[j] * ynew[i * Ncomp + j]*Pt[i]*p_total / den_b + + sat_q_d[j]*d[j] * ynew[i * Ncomp + j]*Pt[i]*p_total / den_d; } } // check the total pressure at the outlet, it should not be negative - if (Pt[0] + dptdx * L < 0.0) + if (Pt[Pt.size()-1] < 0.0) { - throw std::runtime_error("Error: pressure gradient is too large (negative outlet pressure)\n"); + throw std::runtime_error("Error: pressure gradient is too large/ Or some other problem (negative outlet pressure)\n"); } } + // calculate the derivatives Dq/dt and Dp/dt along the column -void Breakthrough::computeFirstDerivatives(std::vector &dqdt, std::vector &dpdt, - const std::vector &q_eq, const std::vector &q, - const std::vector &v, const std::vector &p) +void Breakthrough::computeFirstDerivatives(std::vector &dqdt, + std::vector &dpdt, + std::vector &dTdt, + std::vector &dydt, + const std::vector &q_eq, + const std::vector &q, + const std::vector &v, + const std::vector &p, + // const std::vector &T_arg, + // const std::vector &y_arg) + const std::vector &Temp, + const std::vector &y_vec) { double idx = 1.0 / dx; double idx2 = 1.0 / (dx * dx); - // first gridpoint - for (size_t j = 0; j < Ncomp; ++j) + // The following variables have already been initialized in the initialize function + // double K_z = 0.09; // Thermal conductivity of gas [J/mol/K] + // double C_ps = 750.0; // Heat capacity of adsorbent [J/kg/K] + // double C_pg = 35.8; // Heat capacity of gas [J/mol/K] + // double C_pa = 35.8; // Heat capacity of adsorbate [J/mol/K] + // double mu = 1.13e-05; // Viscoisty of gas [Pa.s] + // double r_p = 5.0e-03; // Radius of adsorbent particles [m] + + // %%%%%%%%%%%%%%%%%%%% Variables required for balances equations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + std::vector ro_g(Ngrid+1); + std::vector sink_term(Ngrid+1); + std::vector kinetic_term(Ngrid+1); + std::vector p_dum(Ngrid+1); + // std::vector Temp(Ngrid+1); + // std::vector y_vec(Ngrid+1); + + // Variables for finite differences + std::vector dTdx(Ngrid+1, 0.0); + std::vector d2Tdx2(Ngrid+1, 0.0); + std::vector dydx((Ngrid+1)*Ncomp, 0.0); + std::vector d2ydx2((Ngrid+1)*Ncomp, 0.0); + std::vector PvT(Ngrid+1, 0.0); + std::vector Pv(Ngrid+1, 0.0); + std::vector dPdx(Ngrid+1, 0.0); + + double phi = R*rho_p*Q_s0*T_gas*(1-epsilon)/epsilon/p_total; + double dTdt1, dTdt2, dTdt3; + double dPdt1, dPdt2, dPdt3; + double dydt1, dydt2, dydt3; + + std::copy(p.begin(), p.end(), p_dum.begin()); + // std::copy(y_arg.begin(), y_arg.end(), y_vec.begin()); + // std::copy(T_arg.begin(), T_arg.end(), Temp.begin()); + + // Calculation of variable properties + double sum_q; + for(size_t i = 0; i < Ngrid+1; i++) { - dqdt[0 * Ncomp + j] = components[j].Kl * (q_eq[0 * Ncomp + j] - q[0 * Ncomp + j]); - dpdt[0 * Ncomp + j] = 0.0; + sum_q = 0.0; + for(size_t j = 0; j < Ncomp; ++j) + { + sum_q += q[i * Ncomp + j]; // Sum of adsorbent loading + } + ro_g[i] = (p_dum[i]*p_total) / R / Temp[i] / T_gas; + sink_term[i] = (1 - epsilon) * (rho_p*C_ps + rho_p*sum_q*C_pa) + + (epsilon*ro_g[i]*C_pg); } - // middle gridpoints - for (size_t i = 1; i < Ngrid; i++) + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Inlet Boundary Pressure correction %%%%%%%%%%%%%% + + double vis_term = 150.0 * mu * std::pow((1-epsilon), 2) + / 4.0 / std::pow(r_p, 2) / std::pow(epsilon, 2); + + double sum = 0.0; + for(size_t j = 0; j < Ncomp; ++j) { - for (size_t j = 0; j < Ncomp; ++j) + sum += (MW[j] * y_vec[0 * Ncomp + j]); + } + + // Calculate Inlet pressure using inlet velocity and 2nd grid point pressure + // based on Ergun's equation + // p_dum[0] = p_dum[1] + (vis_term*v_in + kinetic_term[1]*std::pow(v_in, 2.0))*dx; + p_dum[0] = ((vis_term*v_in*dx*L/p_total) + p_dum[1]) + / (1.0 - (dx*L/R/Temp[0]/T_gas)*sum*((1.75*(1-epsilon))/2.0/r_p/epsilon)*std::pow(v_in, 2.0)); + if (p_dum[p_dum.size()-1] > p_dum[p_dum.size()-2]){ + p_dum[p_dum.size()-1] = p_dum[p_dum.size()-2]; + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% BC Check %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // BCs check (This check ensures that variables at the boundary, also implemented in MATLAB code) + // In order to do that I need modifiable copy of vectors y_vec and Temp. Unfotunately, if I am initializing those copies within this function,... + // I get the C++ error : free(): invalid pointer ... + // I am a new to C++ and trying to address this issue. + // Thanks + + // for(size_t j = 0; j < Ncomp; ++j) + // { + // y_vec[Ngrid * Ncomp + j] = y_vec[Ngrid-1 * Ncomp + j]; + // } + // Temp[Ngrid] = Temp[Ngrid-1]; + p_dum[Ngrid] = p_dum[Ngrid-1]; + + if (p_dum[Ngrid-1] >= 1.0){ + p_dum[Ngrid] = 1.0; + }else{ + p_dum[Ngrid] = p_dum[Ngrid-1]; + } + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Finite Difference Calculation %%%%%%%%%%%%%%%%%%% + // First order differences + for(size_t i = 1; i < Ngrid+1; i++) + { + for(size_t j = 0; j < Ncomp; ++j) { - dqdt[i * Ncomp + j] = components[j].Kl * (q_eq[i * Ncomp + j] - q[i * Ncomp + j]); - dpdt[i * Ncomp + j] = - (v[i - 1] * p[(i - 1) * Ncomp + j] - v[i] * p[i * Ncomp + j]) * idx + - components[j].D * (p[(i + 1) * Ncomp + j] - 2.0 * p[i * Ncomp + j] + p[(i - 1) * Ncomp + j]) * idx2 - - prefactor[j] * (q_eq[i * Ncomp + j] - q[i * Ncomp + j]); + dydx[i * Ncomp + j] = (y_vec[i * Ncomp + j] - y_vec[(i-1) * Ncomp + j]) * idx; } + dTdx[i] = (Temp[i] - Temp[i-1]) * idx; + dPdx[i] = (p_dum[i] - p_dum[i-1]) * idx; + + Pv[i] = (p_dum[i]*v[i] - p_dum[i-1]*v[i-1]) * idx; + PvT[i] = (p_dum[i]*v[i]/Temp[i] - p_dum[i-1]*v[i-1]/Temp[i-1]) * idx; } - // last gridpoint - for (size_t j = 0; j < Ncomp; ++j) + // Second order differences + for(size_t i = 1; i < Ngrid; i++) // For middle nodes + { + for(size_t j = 0; j < Ncomp; ++j) + { + d2ydx2[i * Ncomp + j] = (y_vec[(i+1) * Ncomp + j] - 2.0*y_vec[i * Ncomp + j] + y_vec[(i-1) * Ncomp + j]) * idx2; + } + + d2Tdx2[i] = (Temp[i+1] - 2.0*Temp[i] + Temp[i-1]) * idx2; + } + + // For last node + for(size_t j = 0; j < Ncomp; ++j) + { + d2ydx2[Ngrid * Ncomp + j] = (y_vec[(Ngrid-1) * Ncomp + j] - y_vec[Ngrid * Ncomp + j]) * idx2; + } + d2Tdx2[Ngrid] = (Temp[Ngrid-1] - Temp[Ngrid]) * idx2; + + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Balance equations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // first/inlet gridpoint, the conditions remains same + dpdt[0] = 0.0; + dTdt[0] = 0.0; + for(size_t j = 0; j < Ncomp; ++j) { - dqdt[Ngrid * Ncomp + j] = components[j].Kl * (q_eq[Ngrid * Ncomp + j] - q[Ngrid * Ncomp + j]); - dpdt[Ngrid * Ncomp + j] = (v[Ngrid - 1] * p[(Ngrid - 1) * Ncomp + j] - v[Ngrid] * p[Ngrid * Ncomp + j]) * idx + - components[j].D * (p[(Ngrid - 1) * Ncomp + j] - p[Ngrid * Ncomp + j]) * idx2 - - prefactor[j] * (q_eq[Ngrid * Ncomp + j] - q[Ngrid * Ncomp + j]); + dydt[0 * Ncomp + j] = 0.0; + dqdt[0 * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[0 * Ncomp + j]/Q_s0 - q[0 * Ncomp + j]); + } + + // middle gridpoints + for(size_t i = 1; i < Ngrid; i++) + { + // %%%%%%%%%%%%%%%%%%%%%%% Solid mass balance %%%%%%%%%%%%%%%%%%%%%%%%%% + for(size_t j = 0; j < Ncomp; ++j) + { + dqdt[i * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[i * Ncomp + j]/Q_s0 - q[i * Ncomp + j]); + } + + // %%%%%%%%%%%%%%%%%%%%%%%%% Temperature balance %%%%%%%%%%%%%%%%%%%%%%%% + dTdt1 = K_z/v_in/L * d2Tdx2[i] / sink_term[i]; + dTdt2 = -(epsilon*C_pg*p_total/R/T_gas) * (Pv[i] - Temp[i]*PvT[i]) / sink_term[i]; + + dTdt3 = 0.0; + for(size_t j = 0; j < Ncomp; ++j) + { + dTdt3 += -(1-epsilon) * rho_p * Q_s0 * dqdt[i * Ncomp + j] * del_H[j] / T_gas / sink_term[i]; + } + + // dTdt[i] = 0.0 * (dTdt1 + dTdt2 + dTdt3); + dTdt[i] = (dTdt1 + dTdt2 + dTdt3); + + // %%%%%%%%%%%%%%%%%%%%%%%%% Total Pressure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + dPdt1 = -Temp[i] * PvT[i]; + dPdt2 = p_dum[i] * dTdt[i] / Temp[i]; + + dPdt3 = 0.0; + for(size_t j = 0; j < Ncomp; ++j) + { + dPdt3 += -phi * Temp[i] * dqdt[i * Ncomp + j]; + } + + dpdt[i] = dPdt1 + dPdt2 + dPdt3; + + // %%%%%%%%%%%%%%%%%%%%%%%%%% Mole balance %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // For all components except carrier gas + for(size_t j = 1; j < Ncomp; ++j) + { + double dyPVT; + dyPVT = ((y_vec[i * Ncomp + j]*p_dum[i]*v[i]/Temp[i]) + - (y_vec[(i-1) * Ncomp + j]*p_dum[i-1]*v[i-1]/Temp[i-1])) * idx; + dydt1 = - (Temp[i]/p_dum[i]) * (dyPVT - y_vec[i * Ncomp + j]*PvT[i]); + + dydt2 = (components[j].D/L/v_in) * (d2ydx2[i * Ncomp + j] + + (dPdx[i]*dydx[i * Ncomp + j])/p_dum[i] + - (dTdx[i]*dydx[i * Ncomp + j])/Temp[i]); + double res_dqdt = 0.0; + for(size_t k = 0; k < Ncomp; ++k) + { + if (k != j) + { + res_dqdt += dqdt[i * Ncomp + k]; + } + } + + dydt3 = phi * Temp[i] / p_dum[i] + * ((y_vec[i * Ncomp + j] - 1)*dqdt[i * Ncomp + j] + + y_vec[i * Ncomp + j] * res_dqdt); + + dydt[i * Ncomp + j] = dydt1 + dydt2 + dydt3; + } + } + // Outlet gridpoints + // %%%%%%%%%%%%%%%%%%%%%%% Solid mass balance %%%%%%%%%%%%%%%%%%%%%%%%%% + for(size_t j = 0; j < Ncomp; ++j) + { + dqdt[Ngrid * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[Ngrid * Ncomp + j]/Q_s0 - q[Ngrid * Ncomp + j]); + } + + // %%%%%%%%%%%%%%%%%%%%%%%%% Temperature balance %%%%%%%%%%%%%%%%%%%%%%%% + dTdt[Ngrid] = dTdt[Ngrid-1]; + + // %%%%%%%%%%%%%%%%%%%%%%%%% Total Pressure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + dpdt[Ngrid] = dpdt[Ngrid-1]; + + // %%%%%%%%%%%%%%%%%%%%%%%%%% Mole balance %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + // For all components except carrier gas + for(size_t j = 1; j < Ncomp; ++j) + { + dydt[Ngrid * Ncomp + j] = dydt[(Ngrid-1) * Ncomp + j]; } } // calculate new velocity Vnew from Qnew, Qeqnew, Pnew, Pt void Breakthrough::computeVelocity() { - double idx2 = 1.0 / (dx * dx); - - // first grid point - Vnew[0] = v_in; - - // middle gridpoints - for (size_t i = 1; i < Ngrid; ++i) + double idx = 1.0 / dx; + // double mu = 1.13e-05; + // double r_p = 5.0e-03; + + double vis_term = 150.0 * mu * std::pow((1-epsilon), 2) + / 4.0 / std::pow(r_p, 2) / std::pow(epsilon, 2); + + std::vector ro_g(Ngrid+1); + std::vector kinetic_term(Ngrid+1); + std::vector dPdx(Ngrid+1); + std::vector p_dum(Ngrid+1); + std::copy(Pnew.begin(), Pnew.end(), p_dum.begin()); + + for (size_t i = 0; i < Ngrid+1; ++i) { - // sum = derivative at the actual gridpoint i double sum = 0.0; - for (size_t j = 0; j < Ncomp; ++j) + for(size_t j = 0; j < Ncomp; ++j) { - sum = - sum - prefactor[j] * (Qeqnew[i * Ncomp + j] - Qnew[i * Ncomp + j]) + - components[j].D * (Pnew[(i - 1) * Ncomp + j] - 2.0 * Pnew[i * Ncomp + j] + Pnew[(i + 1) * Ncomp + j]) * idx2; + sum += (MW[j] * ynew[i * Ncomp + j]); } - - // explicit version - Vnew[i] = Vnew[i - 1] + dx * (sum - Vnew[i - 1] * dptdx) / Pt[i]; + if (p_dum[i]<=0.0){ + throw std::runtime_error("Error: Pressure becoming zero\n"); + } + ro_g[i] = p_dum[i]*p_total / R / Tnew[i] / T_gas; + kinetic_term[i] = (ro_g[i] * sum) * (1.75*(1-epsilon)) / 2.0 / r_p / epsilon; } - // last grid point + // Calculate Inlet pressure using inlet velocity and 2nd grid point pressure + // based on Ergun's equation double sum = 0.0; - for (size_t j = 0; j < Ncomp; ++j) + for(size_t j = 0; j < Ncomp; ++j) { - sum = sum - prefactor[j] * (Qeqnew[Ngrid * Ncomp + j] - Qnew[Ngrid * Ncomp + j]) + - components[j].D * (Pnew[(Ngrid - 1) * Ncomp + j] - Pnew[Ngrid * Ncomp + j]) * idx2; + sum += (MW[j] * ynew[0 * Ncomp + j]); + } + + // p_dum[0] = p_dum[1] + (vis_term*v_in + kinetic_term[1]*std::pow(v_in, 2.0))*dx; + p_dum[0] = ((vis_term*v_in*dx*L/p_total) + p_dum[1]) + / (1.0 - (dx*L/R/Tnew[0]/T_gas)*sum*((1.75*(1-epsilon))/2.0/r_p/epsilon)*std::pow(v_in, 2.0)); + if (p_dum[p_dum.size()-1] > p_dum[p_dum.size()-2]){ + p_dum[p_dum.size()-1] = p_dum[p_dum.size()-2]; } - // explicit version - Vnew[Ngrid] = Vnew[Ngrid - 1] + dx * (sum - Vnew[Ngrid - 1] * dptdx) / Pt[Ngrid]; -} + // Pressure gradient + dPdx[0] = 0.0; // Inlet grid point + for(size_t i = 1; i < Ngrid+1; ++i) + { + dPdx[i] = (p_dum[i] - p_dum[i-1]) * idx; + } -void Breakthrough::print() const { std::cout << repr(); } + // Calculate Velocity based on Ergun's equation + Vnew[0] = v_in/v_in; // first grid point + + int v_sign; + for(size_t i = 1; i < Ngrid+1; ++i) + { + if (dPdx[i] <= 0.0) + { + v_sign = 1.0; + } + else + { + v_sign = -1.0; + } + + Vnew[i] = v_sign * (-vis_term + std::pow( + (std::abs(std::pow(vis_term, 2) + 4.0 * kinetic_term[i] * std::abs(dPdx[i]*p_total/L))), 0.5)) + / 2.0 / kinetic_term[i] / v_in; + if (std::isnan(Vnew[i])) + { + std::cout << "Nan encountered" << std::endl; + } + } + + // middle gridpoints + // for(size_t i = 1; i < Ngrid; ++i) + // { + + // // sum = derivative at the actual gridpoint i + // double sum = 0.0; + // for(size_t j = 0; j < Ncomp; ++j) + // { + // sum = sum - prefactor[j] * (Qeqnew[i * Ncomp + j] - Qnew[i * Ncomp + j]) + + // components[j].D * (Pnew[(i - 1) * Ncomp + j] - 2.0 * Pnew[i * Ncomp + j] + Pnew[(i + 1) * Ncomp + j]) * idx2; + // } + + // // explicit version + // Vnew[i] = Vnew[i - 1] + dx * (sum - Vnew[i - 1] * dptdx) / Pt[i]; + // } + + // last grid point + // double sum = 0.0; + // for(size_t j = 0; j < Ncomp; ++j) + // { + // sum = sum - prefactor[j] * (Qeqnew[Ngrid * Ncomp + j] - Qnew[Ngrid * Ncomp + j]) + + // components[j].D * (Pnew[(Ngrid - 1) * Ncomp + j] - Pnew[Ngrid * Ncomp + j]) * idx2; + // } + + // // explicit version + // Vnew[Ngrid] = Vnew[Ngrid-1] + dx * (sum - Vnew[Ngrid - 1] * dptdx) / Pt[Ngrid]; +} std::string Breakthrough::repr() const { @@ -643,7 +959,7 @@ std::string Breakthrough::repr() const s += "Column properties\n"; s += "=======================================================\n"; s += "Display-name: " + displayName + "\n"; - s += "Temperature: " + std::to_string(T) + " [K]\n"; + s += "Temperature: " + std::to_string(T_gas) + " [K]\n"; s += "Column length: " + std::to_string(L) + " [m]\n"; s += "Column void-fraction: " + std::to_string(epsilon) + " [-]\n"; s += "Particle density: " + std::to_string(rho_p) + " [kg/m^3]\n"; @@ -669,54 +985,49 @@ std::string Breakthrough::repr() const s += "Component data\n"; s += "=======================================================\n"; s += "maximum isotherm terms: " + std::to_string(maxIsothermTerms) + "\n"; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - s += components[i].repr(); - s += "\n"; + s += components[i].repr() + "\n"; } return s; } void Breakthrough::createPlotScript() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream stream_graphs("make_graphs.bat"); - stream_graphs << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program " - "Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; - stream_graphs << "gnuplot.exe plot_breakthrough\n"; -#else - std::ofstream stream_graphs("make_graphs"); - stream_graphs << "#!/bin/sh\n"; - stream_graphs << "cd -- \"$(dirname \"$0\")\"\n"; - stream_graphs << "gnuplot plot_breakthrough\n"; -#endif - -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_graphs"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_graphs", S_IRWXU); -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream stream_graphs("make_graphs.bat"); + stream_graphs << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; + stream_graphs << "gnuplot.exe plot_breakthrough\n"; + #else + std::ofstream stream_graphs("make_graphs"); + stream_graphs << "#!/bin/sh\n"; + stream_graphs << "export LC_ALL='en_US.UTF-8'\n"; + stream_graphs << "cd -- \"$(dirname \"$0\")\"\n"; + stream_graphs << "gnuplot plot_breakthrough\n"; + #endif + + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_graphs"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_graphs", S_IRWXU); + #endif std::ofstream stream("plot_breakthrough"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set xlabel 'Dimensionless time, {/Arial-Italic Ï„}={/Arial-Italic tv/L} / [-]' font \"Arial,14\"\n"; - stream << "set ylabel 'Concentration exit gas, {/Arial-Italic c}_i/{/Arial-Italic c}_{i,0} / [-]' offset 0.0,0 font " - "\"Arial,14\"\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set xlabel 'Dimensionless time, {/Helvetica-Italic Ï„}={/Helvetica-Italic tv/L} / [-]' font " - "\"Helvetica,18\"\n"; - stream << "set ylabel 'Concentration exit gas, {/Helvetica-Italic c}_i/{/Helvetica-Italic c}_{i,0} / [-]' offset " - "0.0,0 font \"Helvetica,18\"\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set xlabel 'Dimensionless time, {/Arial-Italic Ï„}={/Arial-Italic tv/L} / [-]' font \"Arial,14\"\n"; + stream << "set ylabel 'Concentration exit gas, {/Arial-Italic c}_i/{/Arial-Italic c}_{i,0} / [-]' offset 0.0,0 font \"Arial,14\"\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set xlabel 'Dimensionless time, {/Helvetica-Italic Ï„}={/Helvetica-Italic tv/L} / [-]' font \"Helvetica,18\"\n"; + stream << "set ylabel 'Concentration exit gas, {/Helvetica-Italic c}_i/{/Helvetica-Italic c}_{i,0} / [-]' offset 0.0,0 font \"Helvetica,18\"\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif stream << "set bmargin 4\n"; stream << "set yrange[0:]\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "set output 'breakthrough_dimensionless.pdf'\n"; stream << "set term pdf color solid\n"; @@ -740,58 +1051,58 @@ void Breakthrough::createPlotScript() for (size_t i = 0; i < Ncomp; i++) { std::string fileName = "component_" + std::to_string(i) + "_" + components[i].name + ".data"; - stream << " " << "\"" << fileName << "\"" << " us ($1):($3) every ev" << " title \"" << components[i].name - << " (y_i=" << components[i].Yi0 << ")\"" - << " with li lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + stream << " " << "\"" << fileName << "\"" << " us ($1):($3) every ev" << " title \"" + << components[i].name << " (y_i=" << components[i].Yi0 << ")\"" + << " with li lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "set output 'breakthrough.pdf'\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set xlabel 'Time, {/Arial-Italic t} / [min.]' font \"Arial,14\"\n"; -#else - stream << "set xlabel 'Time, {/Helvetica-Italic t} / [min.]' font \"Helvetica,18\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set xlabel 'Time, {/Arial-Italic t} / [min.]' font \"Arial,14\"\n"; + #else + stream << "set xlabel 'Time, {/Helvetica-Italic t} / [min.]' font \"Helvetica,18\"\n"; + #endif stream << "plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { std::string fileName = "component_" + std::to_string(i) + "_" + components[i].name + ".data"; - stream << " " << "\"" << fileName << "\"" << " us ($2):($3) every ev" << " title \"" << components[i].name - << " (y_i=" << components[i].Yi0 << ")\"" - << " with li lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + stream << " " << "\"" << fileName << "\"" << " us ($2):($3) every ev" << " title \"" + << components[i].name << " (y_i=" << components[i].Yi0 << ")\"" + << " with li lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } } void Breakthrough::createMovieScripts() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movies.bat"); - makeMovieStream << "CALL make_movie_V.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_Pt.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_Q.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_Qeq.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_P.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_Pnorm.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_Dpdt.bat %1 %2 %3 %4\n"; - makeMovieStream << "CALL make_movie_Dqdt.bat %1 %2 %3 %4\n"; -#else - std::ofstream makeMovieStream("make_movies"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; - makeMovieStream << "./make_movie_V \"$@\"\n"; - makeMovieStream << "./make_movie_Pt \"$@\"\n"; - makeMovieStream << "./make_movie_Q \"$@\"\n"; - makeMovieStream << "./make_movie_Qeq \"$@\"\n"; - makeMovieStream << "./make_movie_P \"$@\"\n"; - makeMovieStream << "./make_movie_Pnorm \"$@\"\n"; - makeMovieStream << "./make_movie_Dpdt \"$@\"\n"; - makeMovieStream << "./make_movie_Dqdt \"$@\"\n"; -#endif - -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movies"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movies", S_IRWXU); -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movies.bat"); + makeMovieStream << "CALL make_movie_V.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_Pt.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_Q.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_Qeq.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_P.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_Pnorm.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_Dpdt.bat %1 %2 %3 %4\n"; + makeMovieStream << "CALL make_movie_Dqdt.bat %1 %2 %3 %4\n"; + #else + std::ofstream makeMovieStream("make_movies"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + makeMovieStream << "./make_movie_V \"$@\"\n"; + makeMovieStream << "./make_movie_Pt \"$@\"\n"; + makeMovieStream << "./make_movie_Q \"$@\"\n"; + makeMovieStream << "./make_movie_Qeq \"$@\"\n"; + makeMovieStream << "./make_movie_P \"$@\"\n"; + makeMovieStream << "./make_movie_Pnorm \"$@\"\n"; + makeMovieStream << "./make_movie_Dpdt \"$@\"\n"; + makeMovieStream << "./make_movie_Dqdt \"$@\"\n"; + #endif + + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movies"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movies", S_IRWXU); + #endif createMovieScriptColumnV(); createMovieScriptColumnPt(); @@ -803,88 +1114,81 @@ void Breakthrough::createMovieScripts() createMovieScriptColumnPnormalized(); } -// -crf 18: the range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, +// -crf 18: the range of the CRF scale is 0–51, where 0 is lossless, 23 is the default, // and 51 is worst quality possible; 18 is visually lossless or nearly so. // -pix_fmt yuv420p: needed on apple devices std::string movieScriptTemplate(std::string s) { std::ostringstream stream; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "del column_movie_" << s << ".mp4\n"; - stream << "set /A argVec[1]=1\n"; - stream << "set /A argVec[2]=1200\n"; - stream << "set /A argVec[3]=800\n"; - stream << "set /A argVec[4]=18\n"; - stream << "setlocal enabledelayedexpansion\n"; - stream << "set argCount=0\n"; - stream << "for %%x in (%*) do (\n"; - stream << " set /A argCount+=1\n"; - stream << " set \"argVec[!argCount!]=%%~x\"'n"; - stream << ")\n"; - stream << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program " - "Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; - stream << "gnuplot.exe -c plot_column_" << s - << " %argVec[1]% %argVec[2]% %argVec[3]% | ffmpeg.exe -f png_pipe -s:v \"%argVec[2]%,%argVec[3]%\" -i pipe: " - "-c:v libx264 -pix_fmt yuv420p -crf %argVec[4]% -c:a aac column_movie_" - << s + ".mp4\n"; -#else - stream << "rm -f " << "column_movie_" << s << ".mp4\n"; - stream << "every=1\n"; - stream << "format=\"-c:v libx265 -tag:v hvc1\"\n"; - stream << "width=1200\n"; - stream << "height=800\n"; - stream << "quality=18\n"; - stream << "while getopts e:w:h:q:l flag\n"; - stream << "do\n"; - stream << " case \"${flag}\" in\n"; - stream << " e) every=${OPTARG};;\n"; - stream << " w) width=${OPTARG};;\n"; - stream << " h) height=${OPTARG};;\n"; - stream << " q) quality=${OPTARG};;\n"; - stream << " l) format=\"-c:v libx264\";;\n"; - stream << " esac\n"; - stream << "done\n"; - stream << "gnuplot -c plot_column_" << s - << " $every $width $height | ffmpeg -f png_pipe -s:v \"${width},${height}\" -i pipe: $format -pix_fmt yuv420p " - "-crf $quality -c:a aac column_movie_" - << s + ".mp4\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "del column_movie_" << s << ".mp4\n"; + stream << "set /A argVec[1]=1\n"; + stream << "set /A argVec[2]=1200\n"; + stream << "set /A argVec[3]=800\n"; + stream << "set /A argVec[4]=18\n"; + stream << "setlocal enabledelayedexpansion\n"; + stream << "set argCount=0\n"; + stream << "for %%x in (%*) do (\n"; + stream << " set /A argCount+=1\n"; + stream << " set \"argVec[!argCount!]=%%~x\"'n"; + stream << ")\n"; + stream << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; + stream << "gnuplot.exe -c plot_column_" << s << " %argVec[1]% %argVec[2]% %argVec[3]% | ffmpeg.exe -f png_pipe -s:v \"%argVec[2]%,%argVec[3]%\" -i pipe: -c:v libx264 -pix_fmt yuv420p -crf %argVec[4]% -c:a aac column_movie_" << s + ".mp4\n"; + #else + stream << "rm -f " << "column_movie_" << s << ".mp4\n"; + stream << "every=1\n"; + stream << "format=\"-c:v libx265 -tag:v hvc1\"\n"; + stream << "width=1200\n"; + stream << "height=800\n"; + stream << "quality=18\n"; + stream << "while getopts e:w:h:q:l flag\n"; + stream << "do\n"; + stream << " case \"${flag}\" in\n"; + stream << " e) every=${OPTARG};;\n"; + stream << " w) width=${OPTARG};;\n"; + stream << " h) height=${OPTARG};;\n"; + stream << " q) quality=${OPTARG};;\n"; + stream << " l) format=\"-c:v libx264\";;\n"; + stream << " esac\n"; + stream << "done\n"; + stream << "gnuplot -c plot_column_" << s << " $every $width $height | ffmpeg -f png_pipe -s:v \"${width},${height}\" -i pipe: $format -pix_fmt yuv420p -crf $quality -c:a aac column_movie_" << s + ".mp4\n"; + #endif return stream.str(); } void Breakthrough::createMovieScriptColumnV() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_V.bat"); -#else - std::ofstream makeMovieStream("make_movie_V"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_V.bat"); + #else + std::ofstream makeMovieStream("make_movie_V"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("V"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_V"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_V", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_V"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_V", S_IRWXU); + #endif std::ofstream stream("plot_column_V"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Interstitial velocity, {/Arial-Italic v} / [m/s]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Interstitial velocity, {/Helvetica-Italic v} / [m/s]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Interstitial velocity, {/Arial-Italic v} / [m/s]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Interstitial velocity, {/Helvetica-Italic v} / [m/s]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -901,8 +1205,7 @@ void Breakthrough::createMovieScriptColumnV() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' us 2 nooutput\n"; stream << "max=STATS_max\n"; stream << "stats 'column.data' us 1 nooutput\n"; @@ -916,38 +1219,39 @@ void Breakthrough::createMovieScriptColumnV() stream << "}\n"; } + void Breakthrough::createMovieScriptColumnPt() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_Pt.bat"); -#else - std::ofstream makeMovieStream("make_movie_Pt"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_Pt.bat"); + #else + std::ofstream makeMovieStream("make_movie_Pt"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("Pt"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_Pt"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_Pt", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_Pt"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_Pt", S_IRWXU); + #endif std::ofstream stream("plot_column_Pt"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Total Pressure, {/Arial-Italic p_t} / [Pa]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Total Pressure, {/Helvetica-Italic p_t} / [Pa]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Total Pressure, {/Arial-Italic p_t} / [Pa]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Total Pressure, {/Helvetica-Italic p_t} / [Pa]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -964,8 +1268,7 @@ void Breakthrough::createMovieScriptColumnPt() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' us 3 nooutput\n"; stream << "max=STATS_max\n"; stream << "stats 'column.data' us 1 nooutput\n"; @@ -981,36 +1284,36 @@ void Breakthrough::createMovieScriptColumnPt() void Breakthrough::createMovieScriptColumnQ() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_Q.bat"); -#else - std::ofstream makeMovieStream("make_movie_Q"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_Q.bat"); + #else + std::ofstream makeMovieStream("make_movie_Q"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("Q"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_Q"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_Q", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_Q"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_Q", S_IRWXU); + #endif std::ofstream stream("plot_column_Q"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Concentration, {/Arial-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Concentration, {/Helvetica-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Concentration, {/Arial-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Concentration, {/Helvetica-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -1027,8 +1330,7 @@ void Breakthrough::createMovieScriptColumnQ() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' nooutput\n"; stream << "max = 0.0;\n"; stream << "do for [i=4:STATS_columns:6] {\n"; @@ -1045,51 +1347,51 @@ void Breakthrough::createMovieScriptColumnQ() stream << " plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { - stream << " " << "'column.data'" << " us 1:" << std::to_string(4 + i * 6) << " index ev*i notitle " - << " with li lt " << i + 1 << ",\\\n"; + stream << " " << "'column.data'" << " us 1:" << std::to_string(4 + i * 6) << " index ev*i notitle " + << " with li lt " << i+1 << ",\\\n"; } for (size_t i = 0; i < Ncomp; i++) { stream << " " << "'column.data'" << " us 1:" << std::to_string(4 + i * 6) << " index ev*i title '" << components[i].name << " (y_i=" << components[i].Yi0 << ")'" - << " with po lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + << " with po lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "}\n"; } void Breakthrough::createMovieScriptColumnQeq() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_Qeq.bat"); -#else - std::ofstream makeMovieStream("make_movie_Qeq"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_Qeq.bat"); + #else + std::ofstream makeMovieStream("make_movie_Qeq"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("Qeq"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_Qeq"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_Qeq", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_Qeq"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_Qeq", S_IRWXU); + #endif std::ofstream stream("plot_column_Qeq"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Concentration, {/Arial-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Concentration, {/Helvetica-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif - + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Concentration, {/Arial-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Concentration, {/Helvetica-Italic c}_i / [mol/kg]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif + // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; stream << "set linetype 2 pt 7 ps 1 lw 4 lc rgb '0x008b00'\n"; @@ -1105,8 +1407,7 @@ void Breakthrough::createMovieScriptColumnQeq() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' nooutput\n"; stream << "max = 0.0;\n"; stream << "do for [i=5:STATS_columns:6] {\n"; @@ -1123,50 +1424,50 @@ void Breakthrough::createMovieScriptColumnQeq() stream << " plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { - stream << " " << "'column.data'" << " us 1:" << std::to_string(5 + i * 6) << " index ev*i notitle " - << " with li lt " << i + 1 << ",\\\n"; + stream << " " << "'column.data'" << " us 1:" << std::to_string(5 + i * 6) << " index ev*i notitle " + << " with li lt " << i+1 << ",\\\n"; } for (size_t i = 0; i < Ncomp; i++) { stream << " " << "'column.data'" << " us 1:" << std::to_string(5 + i * 6) << " index ev*i title '" << components[i].name << " (y_i=" << components[i].Yi0 << ")'" - << " with po lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + << " with po lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "}\n"; } void Breakthrough::createMovieScriptColumnP() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_P.bat"); -#else - std::ofstream makeMovieStream("make_movie_P"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_P.bat"); + #else + std::ofstream makeMovieStream("make_movie_P"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("P"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_P"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_P", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_P"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_P", S_IRWXU); + #endif std::ofstream stream("plot_column_P"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Partial pressure, {/Arial-Italic p}_i / [Pa]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Partial pressure, {/Helvetica-Italic p}_i / [Pa]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Partial pressure, {/Arial-Italic p}_i / [Pa]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Partial pressure, {/Helvetica-Italic p}_i / [Pa]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -1183,8 +1484,7 @@ void Breakthrough::createMovieScriptColumnP() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' nooutput\n"; stream << "max = 0.0;\n"; stream << "do for [i=6:STATS_columns:6] {\n"; @@ -1201,50 +1501,50 @@ void Breakthrough::createMovieScriptColumnP() stream << " plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { - stream << " " << "'column.data'" << " us 1:" << std::to_string(6 + i * 6) << " index ev*i notitle " - << " with li lt " << i + 1 << ",\\\n"; + stream << " " << "'column.data'" << " us 1:" << std::to_string(6 + i * 6) << " index ev*i notitle " + << " with li lt " << i+1 << ",\\\n"; } for (size_t i = 0; i < Ncomp; i++) { stream << " " << "'column.data'" << " us 1:" << std::to_string(6 + i * 6) << " index ev*i title '" << components[i].name << " (y_i=" << components[i].Yi0 << ")'" - << " with po lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + << " with po lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "}\n"; } void Breakthrough::createMovieScriptColumnPnormalized() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_Pnorm.bat"); -#else - std::ofstream makeMovieStream("make_movie_Pnorm"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_Pnorm.bat"); + #else + std::ofstream makeMovieStream("make_movie_Pnorm"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("Pnorm"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_Pnorm"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_Pnorm", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_Pnorm"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_Pnorm", S_IRWXU); + #endif std::ofstream stream("plot_column_Pnorm"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Partial pressure, {/Arial-Italic p}_i / [-]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Partial pressure, {/Helvetica-Italic p}_i / [-]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Partial pressure, {/Arial-Italic p}_i / [-]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Partial pressure, {/Helvetica-Italic p}_i / [-]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -1261,8 +1561,7 @@ void Breakthrough::createMovieScriptColumnPnormalized() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' nooutput\n"; stream << "max = 0.0;\n"; stream << "do for [i=7:STATS_columns:6] {\n"; @@ -1279,50 +1578,50 @@ void Breakthrough::createMovieScriptColumnPnormalized() stream << " plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { - stream << " " << "'column.data'" << " us 1:" << std::to_string(7 + i * 6) << " index ev*i notitle " - << " with li lt " << i + 1 << ",\\\n"; + stream << " " << "'column.data'" << " us 1:" << std::to_string(7 + i * 6) << " index ev*i notitle " + << " with li lt " << i+1 << ",\\\n"; } for (size_t i = 0; i < Ncomp; i++) { stream << " " << "'column.data'" << " us 1:" << std::to_string(7 + i * 6) << " index ev*i title '" << components[i].name << " (y_i=" << components[i].Yi0 << ")'" - << " with po lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + << " with po lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "}\n"; } void Breakthrough::createMovieScriptColumnDpdt() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_Dpdt.bat"); -#else - std::ofstream makeMovieStream("make_movie_Dpdt"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_Dpdt.bat"); + #else + std::ofstream makeMovieStream("make_movie_Dpdt"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("Dpdt"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_Dpdt"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_Dpdt", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_Dpdt"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_Dpdt", S_IRWXU); + #endif std::ofstream stream("plot_column_Dpdt"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Pressure derivative, {/Arial-Italic dp_/dt} / [Pa/s]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream << "set ylabel 'Pressure derivative, {/Helvetica-Italic dp_/dt} / [Pa/s]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Pressure derivative, {/Arial-Italic dp_/dt} / [Pa/s]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Pressure derivative, {/Helvetica-Italic dp_/dt} / [Pa/s]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -1339,8 +1638,7 @@ void Breakthrough::createMovieScriptColumnDpdt() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' nooutput\n"; stream << "max = -1e10;\n"; stream << "min = 1e10;\n"; @@ -1361,51 +1659,50 @@ void Breakthrough::createMovieScriptColumnDpdt() stream << " plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { - stream << " " << "'column.data'" << " us 1:" << std::to_string(8 + i * 6) << " index ev*i notitle " - << " with li lt " << i + 1 << ",\\\n"; + stream << " " << "'column.data'" << " us 1:" << std::to_string(8 + i * 6) << " index ev*i notitle " + << " with li lt " << i+1 << ",\\\n"; } for (size_t i = 0; i < Ncomp; i++) { stream << " " << "'column.data'" << " us 1:" << std::to_string(8 + i * 6) << " index ev*i title '" << components[i].name << " (y_i=" << components[i].Yi0 << ")'" - << " with po lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + << " with po lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "}\n"; } void Breakthrough::createMovieScriptColumnDqdt() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream makeMovieStream("make_movie_Dqdt.bat"); -#else - std::ofstream makeMovieStream("make_movie_Dqdt"); - makeMovieStream << "#!/bin/sh\n"; - makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream makeMovieStream("make_movie_Dqdt.bat"); + #else + std::ofstream makeMovieStream("make_movie_Dqdt"); + makeMovieStream << "#!/bin/sh\n"; + makeMovieStream << "cd -- \"$(dirname \"$0\")\"\n"; + #endif makeMovieStream << movieScriptTemplate("Dqdt"); -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_movie_Dqdt"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_movie_Dqdt", S_IRWXU); -#endif + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_movie_Dqdt"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_movie_Dqdt", S_IRWXU); + #endif std::ofstream stream("plot_column_Dqdt"); stream << "set encoding utf8\n"; -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; - stream << "set ylabel 'Loading derivative, {/Arial-Italic dq_i/dt} / [mol/kg/s]' offset 0.0,0 font 'Arial,14'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; -#else - stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; - stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; - stream - << "set ylabel 'Loading derivative, {/Helvetica-Italic dq_i/dt} / [mol/kg/s]' offset 0.0,0 font 'Helvetica,18'\n"; - stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Arial,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Arial,14'\n"; + stream << "set ylabel 'Loading derivative, {/Arial-Italic dq_i/dt} / [mol/kg/s]' offset 0.0,0 font 'Arial,14'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Arial, 10'\n"; + #else + stream << "set terminal pngcairo size ARG2,ARG3 enhanced font 'Helvetica,10'\n"; + stream << "set xlabel 'Adsorber position / [m]' font 'Helvetica,18'\n"; + stream << "set ylabel 'Loading derivative, {/Helvetica-Italic dq_i/dt} / [mol/kg/s]' offset 0.0,0 font 'Helvetica,18'\n"; + stream << "set key outside top center horizontal samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; + #endif // colorscheme from book 'gnuplot in action', listing 12.7 stream << "set linetype 1 pt 5 ps 1 lw 4 lc rgb '0xee0000'\n"; @@ -1422,8 +1719,7 @@ void Breakthrough::createMovieScriptColumnDqdt() stream << "set linetype 12 pt 14 ps 1 lw 4 lc rgb '0x000000'\n"; stream << "set bmargin 4\n"; - stream << "set key title '" << displayName << " {/:Italic T}=" << T << " K, {/:Italic p_t}=" << p_total * 1e-3 - << " kPa'\n"; + stream << "set key title '" << displayName << " {/:Italic T}=" << T_gas << " K, {/:Italic p_t}=" << p_total*1e-3 << " kPa'\n"; stream << "stats 'column.data' nooutput\n"; stream << "max = -1e10;\n"; stream << "min = 1e10;\n"; @@ -1445,14 +1741,83 @@ void Breakthrough::createMovieScriptColumnDqdt() stream << " plot \\\n"; for (size_t i = 0; i < Ncomp; i++) { - stream << " " << "'column.data'" << " us 1:" << std::to_string(9 + i * 6) << " index ev*i notitle " - << " with li lt " << i + 1 << ",\\\n"; + stream << " " << "'column.data'" << " us 1:" << std::to_string(9 + i * 6) << " index ev*i notitle " + << " with li lt " << i+1 << ",\\\n"; } for (size_t i = 0; i < Ncomp; i++) { stream << " " << "'column.data'" << " us 1:" << std::to_string(9 + i * 6) << " index ev*i title '" << components[i].name << " (y_i=" << components[i].Yi0 << ")'" - << " with po lt " << i + 1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + << " with po lt " << i+1 << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } stream << "}\n"; } + +#ifdef PYBUILD +py::array_t Breakthrough::compute() +{ + size_t colsize = 6 * Ncomp + 5; + std::vector>> brk; + + // loop can quit early if autoSteps + for (size_t step = 0; (step < Nsteps || autoSteps); ++step) + { + // check for error from python side (keyboard interrupt) + if (PyErr_CheckSignals() != 0) + { + throw py::error_already_set(); + } + + computeStep(step); + double t = static_cast(step) * dt; + if (step % writeEvery == 0) + { + std::vector> t_brk(Ngrid + 1, std::vector(colsize)); + for (size_t i = 0; i < Ngrid + 1; ++i) + { + t_brk[i][0] = t * v_in / L; + t_brk[i][1] = t / 60.0; + t_brk[i][2] = static_cast(i) * dx; + t_brk[i][3] = V[i]; + t_brk[i][4] = Pt[i]; + + for (size_t j = 0; j < Ncomp; ++j) + { + t_brk[i][5 + 6 * j] = Q[i * Ncomp + j]; + t_brk[i][6 + 6 * j] = Qeq[i * Ncomp + j]; + t_brk[i][7 + 6 * j] = P[i * Ncomp + j]; + t_brk[i][8 + 6 * j] = P[i * Ncomp + j] / (Pt[i] * components[j].Yi0); + t_brk[i][9 + 6 * j] = Dpdt[i * Ncomp + j]; + t_brk[i][10 + 6 * j] = Dqdt[i * Ncomp + j]; + } + } + brk.push_back(t_brk); + } + if (step % printEvery == 0) + { + std::cout << "Timestep " + std::to_string(step) + ", time: " + std::to_string(t) + " [s]\n"; + std::cout << " Average number of mixture-prediction steps: " + + std::to_string(static_cast(iastPerformance.first) / + static_cast(iastPerformance.second)) + << "\n"; + } + } + std::cout << "Final timestep " + std::to_string(Nsteps) + + ", time: " + std::to_string(dt * static_cast(Nsteps)) + " [s]\n"; + + std::vector buffer; + buffer.reserve(brk.size() * (Ngrid + 1) * colsize); + for (const auto &vec1 : brk) + { + for (const auto &vec2 : vec1) + { + buffer.insert(buffer.end(), vec2.begin(), vec2.end()); + } + } + std::array shape{{brk.size(), Ngrid + 1, colsize}}; + py::array_t py_breakthrough(shape, buffer.data()); + py_breakthrough.resize(shape); + + return py_breakthrough; +} +#endif // PYBUILD diff --git a/src/breakthrough.h b/src/breakthrough.h index b939e05..677c1ea 100644 --- a/src/breakthrough.h +++ b/src/breakthrough.h @@ -1,6 +1,6 @@ #include -#include #include +#include #include "component.h" #include "inputreader.h" @@ -12,262 +12,144 @@ namespace py = pybind11; #endif // PYBUILD -/** - * \brief Simulates a breakthrough process in an adsorption column. - * - * The Breakthrough struct encapsulates the parameters and methods required to simulate - * the breakthrough of gases in an adsorption column. It handles the initialization of - * simulation parameters, computation of time steps, and generation of output scripts - * for plotting and visualization. - */ struct Breakthrough { - public: - /** - * \brief Constructs a Breakthrough simulation using an InputReader. - * - * Initializes a Breakthrough object with parameters specified in the InputReader. - * - * \param inputreader Reference to an InputReader containing simulation parameters. - */ - Breakthrough(const InputReader &inputreader); - - /** - * \brief Constructs a Breakthrough simulation with specified parameters. - * - * Initializes a Breakthrough object with the provided simulation parameters. - * - * \param _displayName Name of the simulation for display purposes. - * \param _components Vector of components involved in the simulation. - * \param _carrierGasComponent Index of the carrier gas component. - * \param _numberOfGridPoints Number of grid points in the column. - * \param _printEvery Frequency of printing time steps to the screen. - * \param _writeEvery Frequency of writing data to files. - * \param _temperature Simulation temperature in Kelvin. - * \param _p_total Total pressure in the column in Pascals. - * \param _columnVoidFraction Void fraction of the column. - * \param _pressureGradient Pressure gradient in the column. - * \param _particleDensity Particle density in kg/m³. - * \param _columnEntranceVelocity Interstitial velocity at the beginning of the column in m/s. - * \param _columnLength Length of the column in meters. - * \param _timeStep Time step for the simulation. - * \param _numberOfTimeSteps Total number of time steps. - * \param _autoSteps Flag to use automatic number of steps. - * \param _pulse Flag to indicate pulsed inlet condition. - * \param _pulseTime Pulse time. - * \param _mixture MixturePrediction object for mixture predictions. - */ - Breakthrough(std::string _displayName, std::vector _components, size_t _carrierGasComponent, - size_t _numberOfGridPoints, size_t _printEvery, size_t _writeEvery, double _temperature, double _p_total, - double _columnVoidFraction, double _pressureGradient, double _particleDensity, - double _columnEntranceVelocity, double _columnLength, double _timeStep, size_t _numberOfTimeSteps, - bool _autoSteps, bool _pulse, double _pulseTime, const MixturePrediction _mixture); - - /** - * \brief Prints the representation of the Breakthrough object to the console. - */ - void print() const; - - /** - * \brief Returns a string representation of the Breakthrough object. - * - * \return A string representing the Breakthrough object. - */ - std::string repr() const; - - /** - * \brief Initializes the Breakthrough simulation. - * - * Sets up initial conditions and precomputes factors required for the simulation. - */ - void initialize(); - - /** - * \brief Runs the Breakthrough simulation. - * - * Executes the simulation over the specified number of time steps. - */ - void run(); - - /** - * \brief Creates a Gnuplot script for plotting breakthrough curves. - * - * Generates a Gnuplot script to visualize the simulation results. - */ - void createPlotScript(); - - /** - * \brief Creates scripts for generating movies of the simulation. - * - * Generates scripts to create movies visualizing the simulation over time. - */ - void createMovieScripts(); + public: + // Constructor overloading, Breakthrough object can be created using either fo the two following definitions, + // compiler will decide constructor at runtime based on the number and types of input arguments + Breakthrough(const InputReader &inputreader); + Breakthrough(std::string _displayName, std::vector _components, size_t _carrierGasComponent, + size_t _numberOfGridPoints, size_t _printEvery, size_t _writeEvery, double _temperature, + double _p_total, double _columnVoidFraction, double _pressureGradient, double _particleDensity, + double _columnEntranceVelocity, double _columnLength, double _timeStep, size_t _numberOfTimeSteps, + bool _autoSteps, bool _pulse, double _pulseTime, const MixturePrediction _mixture); + + std::string repr() const; + void initialize(); + void run(); + void computeStep(size_t step); + + void createPlotScript(); + void createMovieScripts(); #ifdef PYBUILD - /** - * \brief Computes the Breakthrough simulation and returns the results. - * - * Executes the simulation and returns a NumPy array containing the simulation data. - * - * \return A NumPy array of simulation results. - */ - py::array_t compute(); - - /** - * \brief Sets the component parameters for the simulation. - * - * Updates the mole fractions and isotherm parameters for each component. - * - * \param molfracs Vector of mole fractions for each component. - * \param params Vector of isotherm parameters for the components. - */ - void setComponentsParameters(std::vector molfracs, std::vector params); - - /** - * \brief Retrieves the component parameters used in the simulation. - * - * Returns the isotherm parameters for each component. - * - * \return A vector containing the isotherm parameters for the components. - */ - std::vector getComponentsParameters(); + py::array_t compute(); #endif // PYBUILD - private: - const std::string displayName; ///< Name of the simulation for display purposes. - std::vector components; ///< Vector of components involved in the simulation. - size_t carrierGasComponent{0}; ///< Index of the carrier gas component. - size_t Ncomp; ///< Number of components. - size_t Ngrid; ///< Number of grid points. - - size_t printEvery; ///< Frequency of printing time steps to the screen. - size_t writeEvery; ///< Frequency of writing data to files. - - double T; ///< Absolute temperature in Kelvin. - double p_total; ///< Total pressure column [Pa]. - double dptdx; ///< Pressure gradient [N/m³]. - double epsilon; ///< Void-fraction of the column [-]. - double rho_p; ///< Particle density [kg/m³]. - double v_in; ///< Interstitial velocity at the beginning of the column [m/s]. - - double L; ///< Length of the column. - double dx; ///< Spacing in spatial direction. - double dt; ///< Time step for integration. - size_t Nsteps; ///< Total number of steps. - bool autoSteps; ///< Flag to use automatic number of steps. - bool pulse; ///< Pulsed inlet condition for breakthrough. - double tpulse; ///< Pulse time. - MixturePrediction mixture; ///< MixturePrediction object for mixture predictions. - size_t maxIsothermTerms; ///< Maximum number of isotherm terms. - std::pair iastPerformance{0, 0}; ///< Performance metrics for IAST calculations. - - // vector of size 'Ncomp' - std::vector prefactor; ///< Precomputed factors for mass transfer. - std::vector Yi; ///< Ideal gas mole fractions for each component. - std::vector Xi; ///< Adsorbed mole fractions for each component. - std::vector Ni; ///< Number of molecules for each component. - - // vector of size '(Ngrid + 1)' - std::vector V; ///< Interstitial gas velocity along the column. - std::vector Vnew; ///< Updated interstitial gas velocities. - std::vector Pt; ///< Total pressure along the column. - - // vector of size '(Ngrid + 1) * Ncomp', for each grid point, data per component (contiguous) - std::vector P; ///< Partial pressure at every grid point for each component. - std::vector Pnew; ///< Updated partial pressures. - std::vector Q; ///< Volume-averaged adsorption amount at every grid point for each component. - std::vector Qnew; ///< Updated adsorption amounts. - std::vector Qeq; ///< Equilibrium adsorption amount at every grid point for each component. - std::vector Qeqnew; ///< Updated equilibrium adsorption amounts. - std::vector Dpdt; ///< Derivative of P with respect to time. - std::vector Dpdtnew; ///< Updated derivative of P with respect to time. - std::vector Dqdt; ///< Derivative of Q with respect to time. - std::vector Dqdtnew; ///< Updated derivative of Q with respect to time. - std::vector cachedP0; ///< Cached hypothetical pressure. - std::vector cachedPsi; ///< Cached reduced grand potential over the column. - - enum class IntegrationScheme - { - SSP_RK = 0, ///< Strong Stability Preserving Runge-Kutta method. - Iterative = 1 ///< Iterative integration scheme. - }; - - /** - * \brief Computes the first derivatives of concentrations and pressures. - * - * Calculates the derivatives Dq/dt and Dp/dt along the column. - * - * \param dqdt Output vector for the derivatives of Q with respect to time. - * \param dpdt Output vector for the derivatives of P with respect to time. - * \param q_eq Equilibrium adsorption amounts. - * \param q Current adsorption amounts. - * \param v Interstitial gas velocities. - * \param p Partial pressures. - */ - void computeFirstDerivatives(std::vector &dqdt, std::vector &dpdt, const std::vector &q_eq, - const std::vector &q, const std::vector &v, - const std::vector &p); - - /** - * \brief Computes a single simulation step. - * - * Advances the simulation by one time step. - * - * \param step The current time step index. - */ - void computeStep(size_t step); - - /** - * \brief Computes the equilibrium loadings for the current time step. - * - * Calculates the equilibrium adsorption amounts based on current pressures. - */ - void computeEquilibriumLoadings(); - - /** - * \brief Computes the interstitial gas velocities along the column. - * - * Updates the velocities based on current pressures and adsorption amounts. - */ - void computeVelocity(); - - /** - * \brief Creates a script to generate a movie for the interstitial gas velocity. - */ - void createMovieScriptColumnV(); - - /** - * \brief Creates a script to generate a movie for the total pressure along the column. - */ - void createMovieScriptColumnPt(); - - /** - * \brief Creates a script to generate a movie for the adsorption amounts Q. - */ - void createMovieScriptColumnQ(); - - /** - * \brief Creates a script to generate a movie for the equilibrium adsorption amounts Qeq. - */ - void createMovieScriptColumnQeq(); - - /** - * \brief Creates a script to generate a movie for the partial pressures P. - */ - void createMovieScriptColumnP(); - - /** - * \brief Creates a script to generate a movie for the derivatives of pressure Dp/dt. - */ - void createMovieScriptColumnDpdt(); - - /** - * \brief Creates a script to generate a movie for the derivatives of adsorption amounts Dq/dt. - */ - void createMovieScriptColumnDqdt(); - - /** - * \brief Creates a script to generate a movie for the normalized partial pressures. - */ - void createMovieScriptColumnPnormalized(); + private: + const std::string displayName; + const std::vector components; + size_t carrierGasComponent{ 0 }; + size_t Ncomp; // number of components + size_t Ngrid; // number of grid points + + size_t printEvery; // print time step to the screen every printEvery steps + size_t writeEvery; // write data to files every writeEvery steps + + double T_gas; // absolute temperature [K] + double p_total; // total pressure column [Pa] + double dptdx; // pressure gradient [N/m3] + double epsilon; // void-fraction of the column [-] + double rho_p; // particle density [kg/m3] + double v_in; // interstitial velocity at the begin of the column [m/s] + + double L; // length of the column + double dx; // spacing in spatial direction + double dt; // timestep integration + size_t Nsteps; // total number of steps + bool autoSteps; // use automatic number of steps + bool pulse; // pulsed inlet condition for breakthrough + double tpulse; // pulse time + + + MixturePrediction mixture; + size_t maxIsothermTerms; + std::pair iastPerformance{ 0, 0 }; + + // vector of size 'Ncomp' + std::vector prefactor; + std::vector Yi; // ideal gas mol-fraction for each component + std::vector Xi; // adsorbed mol-fraction for each component + std::vector Ni; // number of molecules for each component + + // vector of size '(Ngrid + 1)' + std::vector V; // interstitial gas velocity along the column + std::vector Vnew; + std::vector Pt; // total pressure along the column + + // Hassan Modification , vectors of size '(Ngrid + 1)' + std::vector T; // Temperature along the column + std::vector Tnew; + std::vector DTdt; // Temperature gradient along the column + std::vector DTdtnew; + std::vector P; // Total pressure at every grid point + std::vector Pnew; + std::vector DPdt; // derivative of P with respect to time + std::vector DPdtnew; + + + // vector of size '(Ngrid + 1) * Ncomp', for each grid point, data per component (contiguous) + std::vector y; // mole fraction at every grid point for each component + std::vector ynew; + std::vector Dydt; // derivative of mole fraction at every grid point for each component + std::vector Dydtnew; + std::vector Q; // volume-averaged adsorption amount at every grid point for each component + std::vector Qnew; + std::vector Qeq; // equilibrium adsorption amount at every grid point for each component + std::vector Qeqnew; + std::vector Dqdt; // derivative of Q with respect to time + std::vector Dqdtnew; + std::vector cachedP0; // cached hypothetical pressure + std::vector cachedPsi; // cached reduced grand potential over the column + + // Properties and Parameters + double K_z; // Thermal conductivity of gas [J/mol/K] + double C_ps; // Heat capacity of adsorbent [J/kg/K] + double C_pg; // Heat capacity of gas [J/mol/K] + double C_pa; // Heat capacity of adsorbate [J/mol/K] + double mu; // Viscoisty of gas [Pa.s] + double r_p; // Radius of adsorbent particles [m] + double Q_s0; // Scaling factor for adsorption loading [mol/kg] + + std::vector MW; // Molecular weights of components [kg/mol] + + // Isotherm Parameters + std::vector sat_q_b; + std::vector sat_q_d; + std::vector b_0; + std::vector d_0 ; + std::vector del_H; // Delta Heat of components [mol/kg] + double T_ref; + + enum class IntegrationScheme + { + SSP_RK = 0, + Iterative = 1 + }; + + void computeFirstDerivatives(std::vector &dqdt, + std::vector &dpdt, + std::vector &dTdt, + std::vector &dydt, + const std::vector &q_eq, + const std::vector &q, + const std::vector &v, + const std::vector &p, + const std::vector &Temp, + const std::vector &y_vec); + // const std::vector &T_arg, + // const std::vector &y_arg); + + void computeEquilibriumLoadings(); + + void computeVelocity(); + + void createMovieScriptColumnV(); + void createMovieScriptColumnPt(); + void createMovieScriptColumnQ(); + void createMovieScriptColumnQeq(); + void createMovieScriptColumnP(); + void createMovieScriptColumnDpdt(); + void createMovieScriptColumnDqdt(); + void createMovieScriptColumnPnormalized(); }; diff --git a/src/clean.sh b/src/clean.sh new file mode 100644 index 0000000..10bcef7 --- /dev/null +++ b/src/clean.sh @@ -0,0 +1 @@ +rm plot_* *.o make_* *.data ruptura diff --git a/src/component.cpp b/src/component.cpp index 5b51a80..10a688b 100644 --- a/src/component.cpp +++ b/src/component.cpp @@ -1,6 +1,5 @@ #include "component.h" -#include #include #include "isotherm.h" @@ -16,8 +15,6 @@ Component::Component(size_t _id, std::string _name, std::vector _isoth } } -void Component::print() const { std::cout << repr(); } - std::string Component::repr() const { std::string s; @@ -27,12 +24,26 @@ std::string Component::repr() const s += " carrier-gas\n"; s += isotherm.repr(); } - s += " mol-fraction in the gas: " + std::to_string(Yi0) + " [-]\n"; - if (!isCarrierGas) + s += " mol-fraction in the gas: " + std::to_string(Yi0) + " [-]\n"; + if (!isCarrierGas) + { + s += " mass-transfer coefficient: " + std::to_string(Kl) + " [1/s]\n"; + s += " diffusion coefficient: " + std::to_string(D) + " [m^2/s]\n"; + s += isotherm.repr(); + } + return s; +} + +std::vector& normalize_molfracs(std::vector& components) +{ + double total = 0; + for (auto comp : components) { - s += " mas-transfer coefficient: " + std::to_string(Kl) + " [1/s]\n"; - s += " diffusion coefficient: " + std::to_string(D) + " [m^2/s]\n"; - s += isotherm.repr(); + total += comp.Yi0; + } + for (auto comp : components) + { + comp.Yi0 /= total; } - return s; + return components; } diff --git a/src/component.h b/src/component.h index d6f8575..38915d7 100644 --- a/src/component.h +++ b/src/component.h @@ -1,65 +1,26 @@ #pragma once #include +#include #include "multi_site_isotherm.h" -/** - * \brief Represents a chemical component in the simulation. - * - * The Component struct encapsulates the properties and behaviors of a chemical component within the system. - * It includes identifiers, names, isotherm data, and parameters related to mass transfer and diffusion. - */ + struct Component { - /** - * \brief Constructs a Component with an id and name. - * - * Initializes a Component using the provided identifier and name. - * - * \param i Identifier for the component. - * \param n Name of the component. - */ - Component(size_t i, std::string n) : id(i), name(n) {} - - /** - * \brief Constructs a Component with specified parameters. - * - * Initializes a Component with the provided id, name, isotherms, initial mol-fraction, mass transfer coefficient, - * diffusion coefficient, and an optional flag indicating if it is a carrier gas. - * - * \param _id Identifier for the component. - * \param _name Name of the component. - * \param _isotherms Vector of Isotherm objects associated with the component. - * \param _Yi0 Initial gas phase mol-fraction [-]. - * \param _Kl Mass transfer coefficient [1/s]. - * \param _D Axial dispersion coefficient [m^2/s]. - * \param _isCarrierGas Optional flag indicating if this is the carrier gas (default is false). - */ + Component(size_t i, std::string n): id(i), name(n) {} Component(size_t _id, std::string _name, std::vector _isotherms, double _Yi0, double _Kl, double _D, bool _isCarrierGas = false); - size_t id; ///< Identifier of the component. - std::string name{}; ///< Name of the component. - std::string filename{}; ///< Filename associated with the component data. - MultiSiteIsotherm isotherm; ///< Isotherm information for the component. - double Yi0; ///< Gas phase mol-fraction [-]. - double Kl; ///< Mass transfer coefficient [1/s]. - double D; ///< Axial dispersion coefficient [m^2/s]. - bool isCarrierGas{false}; ///< Flag indicating if this is the carrier gas. + size_t id; + std::string name{}; // name of the component + std::string filename{}; + MultiSiteIsotherm isotherm; // isotherm information + double Yi0; // gas phase mol-fraction [-] + double Kl; // masstransfer coefficient [1/s] + double D; // axial dispersion coefficient [m^2/s] + bool isCarrierGas{ false }; // whether or not this is the carrier-gas - /** - * \brief Prints the component information to the console. - * - * Outputs a string representation of the component to the standard output. - */ - void print() const; - - /** - * \brief Returns a string representation of the Component. - * - * Generates a string that includes the component's properties and parameters. - * - * \return A string representing the Component. - */ std::string repr() const; }; + +std::vector& normalize_molfracs(std::vector& components); diff --git a/src/fitting.cpp b/src/fitting.cpp index 05531fa..9ac19e7 100644 --- a/src/fitting.cpp +++ b/src/fitting.cpp @@ -1,41 +1,38 @@ #include "fitting.h" +#include "special_functions.h" +#include "random_numbers.h" +#include +#include +#include +#include #include -#include -#include +#include #include #include +#include #include -#include -#include -#include -#include -#include +#include #include - -#include "random_numbers.h" -#include "special_functions.h" #if __cplusplus >= 201703L && __has_include() -#include + #include #elif __cplusplus >= 201703L && __has_include() -#include + #include #else -#include + #include #endif #ifdef PYBUILD #include #include namespace py = pybind11; -#endif // PYBUILD +#endif // PYBUILDa Fitting::Fitting(const InputReader &inputreader) : Ncomp(inputreader.components.size()), - components(inputreader.components), displayName(inputreader.displayName), - componentName(Ncomp), + components(inputreader.components), filename(Ncomp), - isotherms(Ncomp), columnPressure(inputreader.columnPressure - 1), columnLoading(inputreader.columnLoading - 1), columnError(inputreader.columnError - 1), @@ -52,17 +49,34 @@ Fitting::Fitting(const InputReader &inputreader) parents(popAlpha), children(popBeta) { - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0 ; i < Ncomp; ++i) { - componentName[i] = inputreader.components[i].name; filename[i] = inputreader.components[i].filename; - isotherms[i] = inputreader.components[i].isotherm; } } +Fitting::Fitting(std::string _displayName, std::vector _components, size_t _pressureScale) + : Ncomp(_components.size()), + displayName(_displayName), + components(_components), + pressureScale(PressureScale(_pressureScale)), + GA_Size(static_cast(std::pow(2.0, 12.0))), + GA_MutationRate(1.0 / 3.0), + GA_EliteRate(0.15), + GA_MotleyCrowdRate(0.25), + GA_DisasterRate(0.001), + GA_Elitists(static_cast(static_cast(GA_Size) * GA_EliteRate)), + GA_Motleists(static_cast(static_cast(GA_Size) * (1.0 - GA_MotleyCrowdRate))), + popAlpha(static_cast(std::pow(2.0, 12.0))), + popBeta(static_cast(std::pow(2.0, 12.0))), + parents(popAlpha), + children(popBeta) +{ +} + void Fitting::readData(size_t ID) { - std::ifstream fileInput{filename[ID]}; + std::ifstream fileInput{ filename[ID] }; std::string errorOpeningFile = "File '" + filename[ID] + "' exists, but error opening file"; if (!fileInput) throw std::runtime_error(errorOpeningFile); @@ -75,15 +89,16 @@ void Fitting::readData(size_t ID) while (std::getline(fileInput, line)) { std::string trimmedLine = trim(line); - if (!startsWith(trimmedLine, "#")) + if(!startsWith(trimmedLine, "#")) { if (!line.empty()) { std::istringstream iss(line); std::vector results((std::istream_iterator(iss)), - std::istream_iterator()); - if (columnPressure < results.size() && columnLoading < results.size()) + std::istream_iterator()); + if(columnPressure < results.size() && + columnLoading < results.size()) { double pressure; double loading; @@ -91,7 +106,7 @@ void Fitting::readData(size_t ID) s >> pressure; std::istringstream t(results[columnLoading]); t >> loading; - if (loading > maximumLoading) + if(loading > maximumLoading) { maximumLoading = loading; } @@ -101,7 +116,7 @@ void Fitting::readData(size_t ID) } } - if (rawData.empty()) + if(rawData.empty()) { throw std::runtime_error("Error: no pressure points found"); } @@ -113,7 +128,7 @@ void Fitting::readData(size_t ID) logPressureRange = std::make_pair(std::log(pressureRange.first), std::log(pressureRange.second)); std::cout << "Found " << rawData.size() << " data points\n"; - for (const std::pair &data : rawData) + for(const std::pair &data : rawData) { std::cout << data.first << " " << data.second << std::endl; } @@ -123,143 +138,38 @@ void Fitting::readData(size_t ID) std::cout << "Log lowest pressure: " << logPressureRange.first << std::endl; std::cout << "Log highest pressure: " << logPressureRange.second << std::endl; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - std::cout << "Number of isotherm parameters: " << isotherms[i].numberOfParameters << std::endl; - isotherms[i].print(); + std::cout << "Number of isotherm parameters: " << components[i].isotherm.numberOfParameters << std::endl; + std::cout << components[i].isotherm.repr(); } } void Fitting::run() { std::cout << "STARTING FITTING\n"; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { readData(i); const DNA bestCitizen = fit(i); const DNA optimizedBestCitizen = simplex(bestCitizen, 1.0); - optimizedBestCitizen.phenotype.print(); + std::cout << optimizedBestCitizen.phenotype.repr(); createPlotScripts(optimizedBestCitizen, i); } - // createPlotScript(); + createPlotScript(); } -#ifdef PYBUILD -Fitting::Fitting(std::string _displayName, std::vector _components, - std::vector> _fullData, size_t _pressureScale) - : Ncomp(_components.size()), - components(_components), - displayName(_displayName), - componentName(Ncomp), - isotherms(Ncomp), - pressureScale(PressureScale(_pressureScale)), - fullData(_fullData), - GA_Size(static_cast(std::pow(2.0, 12.0))), - GA_MutationRate(1.0 / 3.0), - GA_EliteRate(0.15), - GA_MotleyCrowdRate(0.25), - GA_DisasterRate(0.001), - GA_Elitists(static_cast(static_cast(GA_Size) * GA_EliteRate)), - GA_Motleists(static_cast(static_cast(GA_Size) * (1.0 - GA_MotleyCrowdRate))), - popAlpha(static_cast(std::pow(2.0, 12.0))), - popBeta(static_cast(std::pow(2.0, 12.0))), - parents(popAlpha), - children(popBeta) -{ - for (size_t i = 0; i < Ncomp; ++i) - { - componentName[i] = _components[i].name; - isotherms[i] = _components[i].isotherm; - } -} - -void Fitting::sliceData(size_t ID) -{ - // data shaped as (Npress, Ncomp) - rawData.clear(); - for (const auto &pressPoint : fullData) - { - rawData.push_back(std::pair(pressPoint[0], pressPoint[ID + 1])); - } - - // get pressure range - std::sort(rawData.begin(), rawData.end()); - pressureRange = std::make_pair(rawData.front().first, rawData.back().first); - logPressureRange = std::make_pair(std::log(pressureRange.first), std::log(pressureRange.second)); - - maximumLoading = 0.0; - for (const auto &pair : rawData) - { - if (pair.second > maximumLoading) - { - maximumLoading = pair.second; - } - } -} - -std::vector Fitting::compute() -{ - std::vector output; - std::cout << "STARTING FITTING\n"; - for (size_t i = 0; i < Ncomp; ++i) - { - // check for error from python side (keyboard interrupt) - if (PyErr_CheckSignals() != 0) - { - throw py::error_already_set(); - } - - // run algorithm - sliceData(i); - for (const auto &pair : rawData) - { - std::cout << "(" << pair.first << ", " << pair.second << ") "; - } - - const DNA bestCitizen = fit(i); - DNA optimizedBestCitizen = simplex(bestCitizen, 1.0); - - // save optimized params to component, insert in output array - std::vector compParams = optimizedBestCitizen.phenotype.getParameters(); - components[i].isotherm.setParameters(compParams); - output.insert(output.end(), compParams.begin(), compParams.end()); - - optimizedBestCitizen.phenotype.print(); - } - return output; -} - -py::array_t Fitting::evaluate() -{ - // initialize numpy array - size_t Npress = rawData.size(); - std::array shape{{Npress, Ncomp}}; - py::array_t output(shape); - double *data = output.mutable_data(); - - // add datapoints - for (size_t i = 0; i < Npress; i++) - { - for (size_t j = 0; j < Ncomp; j++) - { - data[i * Ncomp + j] = components[j].isotherm.value(rawData[i].first); - } - } - return output; -} - -#endif // PYBUILD - // create a new citizen in the Ensemble Fitting::DNA Fitting::newCitizen(size_t ID) { DNA citizen; - citizen.phenotype = isotherms[ID].randomized(maximumLoading); + citizen.phenotype = components[ID].isotherm.randomized(maximumLoading); citizen.genotype.clear(); - citizen.genotype.reserve((sizeof(double) * CHAR_BIT) * citizen.phenotype.numberOfParameters); - for (size_t i = 0; i < citizen.phenotype.numberOfParameters; ++i) + citizen.genotype.reserve((sizeof(double) * CHAR_BIT) * + citizen.phenotype.numberOfParameters); + for(size_t i = 0; i < citizen.phenotype.numberOfParameters; ++i) { // convert from double to bitset uint64_t p; @@ -276,26 +186,26 @@ Fitting::DNA Fitting::newCitizen(size_t ID) return citizen; } -void Fitting::updateCitizen(DNA &citizen) { citizen.fitness = fitness(citizen.phenotype); } +void Fitting::updateCitizen(DNA &citizen) +{ + citizen.fitness = fitness(citizen.phenotype); +} -inline bool my_isnan(double val) +inline bool my_isnan(double val) { - union - { - double f; - uint64_t x; - } u = {val}; + union { double f; uint64_t x; } u = { val }; return (u.x << 1) > (0x7ff0000000000000u << 1); } + double Fitting::fitness(const MultiSiteIsotherm &phenotype) // For evaluating isotherm goodness-of-fit: // Residual Root Mean Square Error (RMSE) { double fitnessValue = phenotype.fitness(); - size_t m = rawData.size(); // number of observations - size_t p = phenotype.numberOfParameters; // number of adjustable parameters - for (std::pair dataPoint : rawData) + size_t m = rawData.size(); // number of observations + size_t p = phenotype.numberOfParameters; // number of adjustable parameters + for(std::pair dataPoint: rawData) { double pressure = dataPoint.first; double loading = dataPoint.second; @@ -306,8 +216,8 @@ double Fitting::fitness(const MultiSiteIsotherm &phenotype) } fitnessValue = sqrt(fitnessValue / static_cast(m - p)); - if (my_isnan(fitnessValue)) fitnessValue = 99999999.999999; - if (fitnessValue == 0.0000000000) fitnessValue = 99999999.999999; + if(my_isnan(fitnessValue)) fitnessValue = 99999999.999999; + if(fitnessValue==0.0000000000) fitnessValue = 99999999.999999; return fitnessValue; } @@ -322,7 +232,7 @@ double Fitting::RCorrelation(const MultiSiteIsotherm &phenotype) double tmp2 = 0.0; double tmp3 = 0.0; - for (std::pair dataPoint : rawData) + for(std::pair dataPoint: rawData) { double pressure = dataPoint.first; double loading = dataPoint.second; @@ -330,37 +240,37 @@ double Fitting::RCorrelation(const MultiSiteIsotherm &phenotype) loading_avg_e += phenotype.value(pressure) / static_cast(m); } - for (std::pair dataPoint : rawData) + for(std::pair dataPoint: rawData) { double pressure = dataPoint.first; - double loading = dataPoint.second; - tmp1 += (loading - loading_avg_o) * (phenotype.value(pressure) - loading_avg_e); - tmp2 += (loading - loading_avg_o) * (loading - loading_avg_o); - tmp3 += (phenotype.value(pressure) - loading_avg_e) * (phenotype.value(pressure) - loading_avg_e); + double loading = dataPoint.second; + tmp1 += (loading-loading_avg_o)*(phenotype.value(pressure)-loading_avg_e); + tmp2 += (loading-loading_avg_o)*(loading-loading_avg_o); + tmp3 += (phenotype.value(pressure)-loading_avg_e)*(phenotype.value(pressure)-loading_avg_e); } - RCorrelationValue = tmp1 / sqrt(tmp2 * tmp3); - + RCorrelationValue = tmp1/sqrt(tmp2*tmp3); + return RCorrelationValue; } size_t Fitting::biodiversity(const std::vector &citizens) { std::map counts; - for (const DNA &dna : citizens) + for(const DNA &dna: citizens) { - if (counts.find(dna.hash) != counts.end()) + if(counts.find(dna.hash) != counts.end()) { ++counts[dna.hash]; } - else + else { counts[dna.hash] = 1; } } size_t biodiversity = 0; - for (const std::pair value : counts) + for(const std::pair value: counts) { - if (value.second > 1) + if(value.second > 1) { biodiversity += value.second; } @@ -371,7 +281,7 @@ size_t Fitting::biodiversity(const std::vector &citizens) void Fitting::nuclearDisaster(size_t ID) { - for (size_t i = 1; i < children.size(); ++i) + for(size_t i = 1; i < children.size(); ++i) { children[i] = newCitizen(ID); } @@ -379,15 +289,14 @@ void Fitting::nuclearDisaster(size_t ID) void Fitting::elitism() { - std::copy(parents.begin(), parents.begin() + static_cast::difference_type>(GA_Elitists), - children.begin()); + std::copy(parents.begin(), parents.begin() + static_cast::difference_type>(GA_Elitists), children.begin()); } void Fitting::mutate(DNA &mutant) { mutant.genotype.clear(); mutant.genotype.reserve((sizeof(double) * CHAR_BIT) * mutant.phenotype.numberOfParameters); - for (size_t i = 0; i < mutant.phenotype.numberOfParameters; ++i) + for(size_t i = 0; i < mutant.phenotype.numberOfParameters; ++i) { // convert from double to bitset uint64_t p; @@ -424,27 +333,27 @@ void Fitting::mutate(DNA &mutant) // * * * * * * // 00|0000|00 11|1111|11 -> 00|1111|00 //---------------------------------------------- -void Fitting::crossover(size_t ID, size_t s1, size_t s2, size_t i1, size_t i2, size_t j1, size_t j2) +void Fitting::crossover(size_t ID, size_t s1,size_t s2, size_t i1, size_t i2, size_t j1, size_t j2) { - size_t k1, k2; - double tmp1; - for (size_t i = s1; i < s2; ++i) + size_t k1,k2; + double tmp1; + for(size_t i = s1; i < s2; ++i) { chooseRandomly(i1, i2, j1, j2, k1, k2); tmp1 = RandomNumber::Uniform(); // choose between single cross-over using bit-strings or random parameter-swap - if (tmp1 < 0.490) - // One-point crossover: - // -------------------- + if(tmp1 < 0.490) + // One-point crossover: + // -------------------- { // remove the extreme values 0 and 32*Npar - 1 (they are not valid for crossover) - size_t bitStringSize = (sizeof(double) * CHAR_BIT) * isotherms[ID].numberOfParameters; + size_t bitStringSize = (sizeof(double) * CHAR_BIT) * components[ID].isotherm.numberOfParameters; size_t spos = RandomNumber::Integer(1, bitStringSize - 2); - children[i].genotype = - parents[k1].genotype.substr(0, spos) + parents[k2].genotype.substr(spos, bitStringSize - spos); + children[i].genotype = parents[k1].genotype.substr(0, spos) + + parents[k2].genotype.substr(spos, bitStringSize - spos); // convert the bit-strings to doubles - for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) + for(size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) { size_t pos = j * (sizeof(double) * CHAR_BIT); size_t size = sizeof(double) * CHAR_BIT; @@ -453,80 +362,81 @@ void Fitting::crossover(size_t ID, size_t s1, size_t s2, size_t i1, size_t i2, s std::memcpy(&children[i].phenotype.parameters(j), &p, sizeof(double)); } } - else if (tmp1 < 0.499) - // Two-point crossover: - // -------------------- - { - size_t bitStringSize = (sizeof(double) * CHAR_BIT) * isotherms[ID].numberOfParameters; - size_t spos1 = RandomNumber::Integer(1, bitStringSize - 3); - size_t spos2 = RandomNumber::Integer(spos1, bitStringSize - 2); - children[i].genotype = parents[k1].genotype.substr(0, spos1) + parents[k2].genotype.substr(spos1, spos2 - spos1) + - parents[k1].genotype.substr(spos2, bitStringSize - spos2); - // convert the bit-strings to doubles - for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) + else if ( tmp1 < 0.499) + // Two-point crossover: + // -------------------- { - size_t pos = j * (sizeof(double) * CHAR_BIT); - size_t size = sizeof(double) * CHAR_BIT; - std::bitset bitset(children[i].genotype, pos, size); - uint64_t p = bitset.to_ullong(); - std::memcpy(&children[i].phenotype.parameters(j), &p, sizeof(double)); + size_t bitStringSize = (sizeof(double) * CHAR_BIT) * components[ID].isotherm.numberOfParameters; + size_t spos1 = RandomNumber::Integer(1, bitStringSize - 3); + size_t spos2 = RandomNumber::Integer(spos1, bitStringSize - 2); + children[i].genotype = parents[k1].genotype.substr(0, spos1) + + parents[k2].genotype.substr(spos1, spos2 - spos1) + + parents[k1].genotype.substr(spos2, bitStringSize - spos2); + // convert the bit-strings to doubles + for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) + { + size_t pos = j * (sizeof(double) * CHAR_BIT); + size_t size = sizeof(double) * CHAR_BIT; + std::bitset bitset(children[i].genotype, pos, size); + uint64_t p = bitset.to_ullong(); + std::memcpy(&children[i].phenotype.parameters(j), &p, sizeof(double)); + } } - } - else if (tmp1 < 0.500) - { - // Uniform crossover: - // ------------------ - size_t bitStringSize = (sizeof(double) * CHAR_BIT) * isotherms[ID].numberOfParameters; - size_t rolling_k = k1; - for (size_t j = 0; j < bitStringSize; j++) + else if (tmp1 < 0.500) { - if (RandomNumber::Uniform() < 0.25) + // Uniform crossover: + // ------------------ + size_t bitStringSize = (sizeof(double) * CHAR_BIT) * components[ID].isotherm.numberOfParameters; + size_t rolling_k = k1; + for (size_t j = 0; j < bitStringSize; j++) { - if (rolling_k == k1) - { - rolling_k = k2; - } - else + if (RandomNumber::Uniform() < 0.25) { - rolling_k = k1; + if (rolling_k == k1) + { + rolling_k = k2; + } + else + { + rolling_k = k1; + } } + children[i].genotype.substr(j, 1) = parents[rolling_k].genotype.substr(j, 1); } - children[i].genotype.substr(j, 1) = parents[rolling_k].genotype.substr(j, 1); - } - // convert the bit-strings to doubles - for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) - { - size_t pos = j * (sizeof(double) * CHAR_BIT); - size_t size = sizeof(double) * CHAR_BIT; - std::bitset bitset(children[i].genotype, pos, size); - uint64_t p = bitset.to_ullong(); - std::memcpy(&children[i].phenotype.parameters(j), &p, sizeof(double)); - } - } - else - { - children[i].genotype.clear(); - for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) - { - // randomly choose whether the parameter comes from parent k1 or k2 - if (RandomNumber::Uniform() < 0.5) + // convert the bit-strings to doubles + for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) { - children[i].phenotype.parameters(j) = parents[k1].phenotype.parameters(j); + size_t pos = j * (sizeof(double) * CHAR_BIT); + size_t size = sizeof(double) * CHAR_BIT; + std::bitset bitset(children[i].genotype, pos, size); + uint64_t p = bitset.to_ullong(); + std::memcpy(&children[i].phenotype.parameters(j), &p, sizeof(double)); } - else + } + else + { + children[i].genotype.clear(); + for (size_t j = 0; j < children[i].phenotype.numberOfParameters; ++j) { - children[i].phenotype.parameters(j) = parents[k2].phenotype.parameters(j); - } + // randomly choose whether the parameter comes from parent k1 or k2 + if (RandomNumber::Uniform() < 0.5) + { + children[i].phenotype.parameters(j) = parents[k1].phenotype.parameters(j); + } + else + { + children[i].phenotype.parameters(j) = parents[k2].phenotype.parameters(j); + } - // convert from double to bitString - uint64_t p; - std::memcpy(&p, &children[i].phenotype.parameters(j), sizeof(double)); - std::bitset bitset(p); - children[i].genotype += bitset.to_string(); + // convert from double to bitString + uint64_t p; + std::memcpy(&p, &children[i].phenotype.parameters(j), sizeof(double)); + std::bitset bitset(p); + children[i].genotype += bitset.to_string(); + } } - } - children[i].hash = std::hash{}(children[i].phenotype); + children[i].hash = std::hash{}(children[i].phenotype); } } @@ -661,7 +571,7 @@ Fitting::DNA Fitting::fit(size_t ID) sortByFitness(); } - isotherms[ID] = parents[0].phenotype; + components[ID].isotherm = parents[0].phenotype; std::cout << "Starting Genetic Algorithm optimization\n"; @@ -968,7 +878,7 @@ const Fitting::DNA Fitting::simplex(DNA citizen, double scale) void Fitting::createPlotScripts(const DNA &citizen, size_t ID) { - std::string plotFileName = "plot_fit_component_" + std::to_string(ID) + "_" + componentName[ID]; + std::string plotFileName = "plot_fit_component_" + std::to_string(ID) + "_" + components[ID].name; std::ofstream stream(plotFileName); stream << "set encoding utf8\n"; @@ -982,9 +892,9 @@ void Fitting::createPlotScripts(const DNA &citizen, size_t ID) } stream << "set key right bottom vertical samplen 2.5 height 0.5 spacing 1.5 font 'Helvetica, 10'\n"; - stream << "set key title '" << componentName[ID] << "'\n"; + stream << "set key title '" << components[ID].name << "'\n"; - stream << "set output 'isotherms_fit_" << componentName[ID] << ".pdf'\n"; + stream << "set output 'isotherms_fit_" << components[ID].name << ".pdf'\n"; stream << "set term pdf color solid\n"; // colorscheme from book 'gnuplot in action', listing 12.7 @@ -1001,10 +911,10 @@ void Fitting::createPlotScripts(const DNA &citizen, size_t ID) stream << "set linetype 11 pt 12 ps 0.5 lw 2 lc rgb '0x8b2500'\n"; stream << "set linetype 12 pt 14 ps 0.5 lw 2 lc rgb '0x000000'\n"; - stream << "array s[" << isotherms[ID].numberOfParameters << "]\n"; - for (size_t i = 0; i < isotherms[ID].numberOfParameters; ++i) + stream << "array s[" << components[ID].isotherm.numberOfParameters << "]\n"; + for (size_t i = 0; i < components[ID].isotherm.numberOfParameters; ++i) { - stream << "s[" << i + 1 << "]=" << isotherms[ID].parameters(i) << "\n"; + stream << "s[" << i + 1 << "]=" << components[ID].isotherm.parameters(i) << "\n"; } stream << "array p[" << citizen.phenotype.numberOfParameters << "]\n"; for (size_t i = 0; i < citizen.phenotype.numberOfParameters; ++i) @@ -1012,7 +922,7 @@ void Fitting::createPlotScripts(const DNA &citizen, size_t ID) stream << "p[" << i + 1 << "]=" << citizen.phenotype.parameters(i) << "\n"; } stream << "plot \\\n" - << isotherms[ID].gnuplotFunctionString('s') << " title 'start f(x)' with li dt 2 lw 2,\\\n" + << components[ID].isotherm.gnuplotFunctionString('s') << " title 'start f(x)' with li dt 2 lw 2,\\\n" << citizen.phenotype.gnuplotFunctionString('p') << " title 'fit f(x)' with li lw 2,\\\n"; stream << "'" << filename[ID] << "' us " << columnPressure + 1 << ":" << columnLoading + 1 << " title 'raw data' with po pt 5 ps 0.5\n"; @@ -1020,28 +930,97 @@ void Fitting::createPlotScripts(const DNA &citizen, size_t ID) void Fitting::createPlotScript() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream stream_graphs("make_graphs.bat"); - stream_graphs << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program " - "Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; - for (size_t i = 0; i < Ncomp; ++i) + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream stream_graphs("make_graphs.bat"); + stream_graphs << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; + for(size_t i = 0; i < Ncomp; ++i) + { + std::string plotFileName = "plot_fit_component_" + std::to_string(i) + "_" + components[i].name; + stream_graphs << "gnuplot.exe " << plotFileName << "\n"; + } + #else + std::ofstream stream_graphs("make_graphs"); + stream_graphs << "#!/bin/sh\n"; + stream_graphs << "export LC_ALL='en_US.UTF-8'\n"; + for(size_t i = 0; i < Ncomp; ++i) + { + std::string plotFileName = "plot_fit_component_" + std::to_string(i) + "_" + components[i].name; + stream_graphs << "gnuplot " << plotFileName << "\n"; + } + #endif + + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_graphs"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_graphs", S_IRWXU); + #endif + +} + +#ifdef PYBUILD + +void Fitting::selectData(size_t ID, std::vector>> data) +{ + rawData = data[ID]; + + // get pressure range + pressureRange = std::make_pair(rawData.front().first, rawData.back().first); + logPressureRange = std::make_pair(std::log(pressureRange.first), std::log(pressureRange.second)); + + maximumLoading = 0.0; + for (const auto &pair : rawData) { - std::string plotFileName = "plot_fit_component_" + std::to_string(i) + "_" + componentName[i]; - stream_graphs << "gnuplot.exe " << plotFileName << "\n"; + if (pair.second > maximumLoading) + { + maximumLoading = pair.second; + } } -#else - std::ofstream stream_graphs("make_graphs"); - for (size_t i = 0; i < Ncomp; ++i) +} + +std::vector Fitting::compute(std::vector>> data) +{ + std::vector output; + std::cout << "STARTING FITTING\n"; + + for (size_t ID = 0; ID < Ncomp; ++ID) { - std::string plotFileName = "plot_fit_component_" + std::to_string(i) + "_" + componentName[i]; - stream_graphs << "gnuplot " << plotFileName << "\n"; + selectData(ID, data); + + const DNA bestCitizen = fit(ID); + DNA optimizedBestCitizen = simplex(bestCitizen, 1.0); + + // save optimized params to component, insert in output array + for (size_t j = 0; j < optimizedBestCitizen.phenotype.numberOfParameters; j++) + { + components[ID].isotherm.setParameters(j, optimizedBestCitizen.phenotype.parameters(j)); + output.push_back(optimizedBestCitizen.phenotype.parameters(j)); + } } -#endif + for (size_t ID = 0; ID < Ncomp; ++ID) + { + std::cout << components[ID].repr() << "\n"; + } + return output; +} -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_graphs"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_graphs", S_IRWXU); -#endif +py::array_t Fitting::evaluate(std::vector pressure) +{ + // initialize numpy array + size_t Npress = pressure.size(); + std::array shape{{Npress, Ncomp}}; + py::array_t output(shape); + double *data = output.mutable_data(); + + // add datapoints + for (size_t i = 0; i < Npress; i++) + { + for (size_t j = 0; j < Ncomp; j++) + { + data[i * Ncomp + j] = components[j].isotherm.value(pressure[i]); + } + } + return output; } + +#endif // PYBUILD diff --git a/src/fitting.h b/src/fitting.h index 98a59ff..dd7dba5 100644 --- a/src/fitting.h +++ b/src/fitting.h @@ -1,13 +1,13 @@ #pragma once +#include #include +#include #include -#include #include -#include -#include "component.h" #include "inputreader.h" +#include "component.h" #include "multi_site_isotherm.h" #ifdef PYBUILD @@ -16,257 +16,91 @@ namespace py = pybind11; #endif // PYBUILD -/** - * \brief Class for fitting isotherm models to adsorption data. - * - * The Fitting class implements genetic algorithms and the Nelder-Mead simplex method - * to optimize isotherm parameters based on input data. - */ struct Fitting { - /** - * \brief Structure representing an individual in the genetic algorithm. - */ struct DNA { - /** - * \brief Constructs a DNA object with specified genotype, phenotype, and fitness. - * \param g Genotype represented as a string. - * \param p Phenotype represented as a MultiSiteIsotherm. - * \param f Fitness value of the DNA. - */ - DNA(std::string g, MultiSiteIsotherm p, double f) - : genotype(g), phenotype(p), fitness(f), hash(std::hash{}(g)) + DNA(std::string g, MultiSiteIsotherm p, double f): + genotype(g), + phenotype(p), + fitness(f), + hash(std::hash{}(g)) { } DNA() noexcept = default; - std::string genotype; ///< Genotype represented as a bitstring. - MultiSiteIsotherm phenotype; ///< Phenotype of the individual. - double fitness; ///< Fitness value. - size_t hash; ///< Hash value for uniqueness. + std::string genotype; + MultiSiteIsotherm phenotype; + double fitness; + size_t hash; }; - /** - * \brief Enum representing pressure scale options. - */ enum class PressureScale { - Log = 0, ///< Logarithmic pressure scale. - Normal = 1 ///< Linear pressure scale. + Log = 0, + Normal = 1 }; - /** - * \brief Constructs a Fitting object from input parameters. - * \param inputreader InputReader containing simulation parameters. - */ Fitting(const InputReader &inputreader); + Fitting(std::string _displayName, std::vector _components, size_t _pressureScale); - /** - * \brief Reads data for a specific component. - * \param ID Index of the component. - */ void readData(size_t ID); - - /** - * \brief Prints the solution for a specific component. - * \param ID Index of the component. - */ - void printSolution(size_t ID); - - /** - * \brief Runs the fitting process for all components. - */ void run(); - - /** - * \brief Creates plot scripts for a specific component. - * \param citizen DNA of the optimized individual. - * \param ID Index of the component. - */ void createPlotScripts(const DNA &citizen, size_t ID); - - /** - * \brief Creates a master plot script. - */ void createPlotScript(); -#ifdef PYBUILD - /** - * \brief Constructs a Fitting object for Python integration. - * \param _displayName Display name of the fitting session. - * \param _components Vector of components to fit. - * \param _fullData Full dataset for all components. - * \param _pressureScale Pressure scale type. - */ - Fitting(std::string _displayName, std::vector _components, std::vector> _fullData, - size_t _pressureScale); - - /** - * \brief Extracts data slices for a specific component. - * \param ID Index of the component. - */ - void sliceData(size_t ID); - - std::vector> fullData; ///< Full dataset for all components. - - /** - * \brief Computes the fitting parameters. - * \return Vector of optimized parameters. - */ - std::vector compute(); - - /** - * \brief Evaluates the fitted isotherm models. - * \return NumPy array of evaluated values. - */ - py::array_t evaluate(); - -#endif // PYBUILD - - /** - * \brief Generates a new individual for the genetic algorithm. - * \param ID Index of the component. - * \return A new DNA object. - */ DNA newCitizen(size_t ID); - - /** - * \brief Updates the fitness of a DNA object. - * \param citizen DNA object to update. - */ void updateCitizen(DNA &citizen); - - /** - * \brief Calculates the fitness of a phenotype. - * \param phenotype Phenotype to evaluate. - * \return Fitness value. - */ double fitness(const MultiSiteIsotherm &phenotype); - - /** - * \brief Calculates the correlation coefficient R. - * \param phenotype Phenotype to evaluate. - * \return Correlation coefficient. - */ double RCorrelation(const MultiSiteIsotherm &phenotype); - - /** - * \brief Measures biodiversity in the population. - * \param citizens Vector of DNA objects. - * \return Biodiversity metric. - */ size_t biodiversity(const std::vector &citizens); - - /** - * \brief Simulates a nuclear disaster to introduce new genetic material. - * \param ID Index of the component. - */ void nuclearDisaster(size_t ID); - - /** - * \brief Performs elitism selection in the genetic algorithm. - */ void elitism(); - - /** - * \brief Mutates a DNA object. - * \param Mutant DNA object to mutate. - */ void mutate(DNA &Mutant); - - /** - * \brief Performs crossover between individuals. - * \param ID Index of the component. - * \param s1 Start index for children. - * \param s2 End index for children. - * \param i1 Start index for parent1. - * \param i2 End index for parent1. - * \param j1 Start index for parent2. - * \param j2 End index for parent2. - */ - void crossover(size_t ID, size_t s1, size_t s2, size_t i1, size_t i2, size_t j1, size_t j2); - - /** - * \brief Randomly selects parent indices. - * \param kk1 Minimum index for parent1. - * \param kk2 Maximum index for parent1. - * \param jj1 Minimum index for parent2. - * \param jj2 Maximum index for parent2. - * \param ii1 Output index for parent1. - * \param ii2 Output index for parent2. - */ - void chooseRandomly(size_t kk1, size_t kk2, size_t jj1, size_t jj2, size_t &ii1, size_t &ii2); - - /** - * \brief Mates individuals to produce the next generation. - * \param ID Index of the component. - */ + void crossover(size_t ID, size_t s1,size_t s2, size_t i1, size_t i2, size_t j1, size_t j2); + void chooseRandomly(size_t kk1,size_t kk2,size_t jj1,size_t jj2, size_t &ii1, size_t &ii2); void mate(size_t ID); - - /** - * \brief Sorts the population by fitness. - */ void sortByFitness(); - - /** - * \brief Writes information about a DNA object. - * \param citizen Index of the citizen in the population. - * \param id Component index. - * \param step Current optimization step. - * \param variety Biodiversity metric. - * \param fullfilledCondition Condition fulfillment counter. - */ void writeCitizen(size_t citizen, size_t id, size_t step, size_t variety, size_t fullfilledCondition); - - /** - * \brief Fits the isotherm model to data for a specific component. - * \param ID Index of the component. - * \return Best DNA object found. - */ DNA fit(size_t ID); - - /** - * \brief Optimizes a DNA object using the Nelder-Mead simplex method. - * \param citizen DNA object to optimize. - * \param scale Scaling factor for the simplex. - * \return Optimized DNA object. - */ const DNA simplex(DNA citizen, double scale); - size_t Ncomp; ///< Number of components. - std::vector components; ///< Components involved in fitting. - std::string displayName; ///< Display name for the fitting session. - std::vector componentName; ///< Names of the components. - std::vector filename; ///< Filenames for input data. - std::vector isotherms; ///< Isotherm models for each component. - size_t columnPressure{0}; ///< Column index for pressure data. - size_t columnLoading{1}; ///< Column index for loading data. - size_t columnError{2}; ///< Column index for error data. - double maximumLoading{0.0}; ///< Maximum loading observed. - PressureScale pressureScale{PressureScale::Log}; ///< Pressure scale type. - - std::vector> rawData; ///< Raw data points (pressure, loading). + size_t Ncomp; + std::string displayName; + std::vector components; + std::vector filename; + size_t columnPressure{ 0 }; + size_t columnLoading{ 1 }; + size_t columnError{ 2 }; + double maximumLoading{ 0.0 }; + PressureScale pressureScale{ PressureScale::Log }; + + std::vector> rawData; + + bool fittingFlag{ false }; + bool physicalConstrainsFlag{ false }; + bool seedFlag{ false }; + bool pressureRangeFlag{ false }; + bool refittingFlag{ false }; + std::pair pressureRange; + std::pair logPressureRange; + + size_t GA_Size; // population size + double GA_MutationRate; // mutation rate + double GA_EliteRate; // elitists population rate + double GA_MotleyCrowdRate; // pirates population rate + double GA_DisasterRate; + size_t GA_Elitists; // number of elitists + size_t GA_Motleists; // number of pirates + + std::vector popAlpha; + std::vector popBeta; + std::vector &parents; + std::vector &children; - bool fittingFlag{false}; ///< Flag indicating if fitting is active. - bool physicalConstrainsFlag{false}; ///< Flag for physical constraints. - bool seedFlag{false}; ///< Flag for seed initialization. - bool pressureRangeFlag{false}; ///< Flag for pressure range usage. - bool refittingFlag{false}; ///< Flag for refitting. - - std::pair pressureRange; ///< Range of pressures. - std::pair logPressureRange; ///< Logarithmic pressure range. - - size_t GA_Size; ///< Genetic algorithm population size. - double GA_MutationRate; ///< Mutation rate in genetic algorithm. - double GA_EliteRate; ///< Proportion of elite individuals. - double GA_MotleyCrowdRate; ///< Proportion of diverse individuals. - double GA_DisasterRate; ///< Rate of introducing new genetic material. - size_t GA_Elitists; ///< Number of elite individuals. - size_t GA_Motleists; ///< Number of diverse individuals. - - std::vector popAlpha; ///< First population buffer. - std::vector popBeta; ///< Second population buffer. - std::vector &parents; ///< Reference to current parent population. - std::vector &children; ///< Reference to current child population. +#ifdef PYBUILD + void selectData(size_t ID, std::vector>> data); + std::vector compute(std::vector>> data); + py::array_t evaluate(std::vector pressure); +#endif }; diff --git a/src/hash_combine.h b/src/hash_combine.h index 4c2f706..2a3e446 100644 --- a/src/hash_combine.h +++ b/src/hash_combine.h @@ -1,17 +1,17 @@ #pragma once -#include #include #include +#include // https://stackoverflow.com/questions/35985960/c-why-is-boosthash-combine-the-best-way-to-combine-hash-values/50978188 -inline void hash_combine([[maybe_unused]] std::size_t& seed) {} +inline void hash_combine([[maybe_unused]] std::size_t& seed) { } template -inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) -{ - std::hash hasher; - seed ^= hasher(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); - hash_combine(seed, rest...); +inline void hash_combine(std::size_t& seed, const T& v, Rest... rest) { + std::hash hasher; + seed ^= hasher(v) + 0x9e3779b9 + (seed<<6) + (seed>>2); + hash_combine(seed, rest...); } + diff --git a/src/inputreader.cpp b/src/inputreader.cpp index b2f4347..d240bea 100644 --- a/src/inputreader.cpp +++ b/src/inputreader.cpp @@ -1,32 +1,31 @@ #include "inputreader.h" -#include #include -#include +#include #include #include +#include bool caseInSensStringCompare(const std::string& str1, const std::string& str2) { - return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), - [](int a, int b) { return std::tolower(a) == std::tolower(b); }); + return str1.size() == str2.size() && std::equal(str1.begin(), str1.end(), str2.begin(), + [](int a, int b) {return std::tolower(a) == std::tolower(b); }); } -bool startsWith(const std::string& str, const std::string& prefix) -{ - return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix; +bool startsWith(const std::string &str, const std::string &prefix) { + return str.size() >= prefix.size() && str.substr(0, prefix.size()) == prefix; } std::string trim(const std::string& s) { auto start = s.begin(); - while (start != s.end() && std::isspace(*start)) + while (start != s.end() && std::isspace(*start)) { start++; } auto end = s.end(); - do + do { end--; } while (std::distance(start, end) > 0 && std::isspace(*end)); @@ -34,7 +33,7 @@ std::string trim(const std::string& s) return std::string(start, end + 1); } -template +template T parse(const std::string& arguments, [[maybe_unused]] const std::string& keyword, [[maybe_unused]] size_t lineNumber) { T value; @@ -47,7 +46,7 @@ T parse(const std::string& arguments, [[maybe_unused]] const std::string& keywor return value; } -template +template std::vector parseListOfSystemValues(const std::string& arguments, const std::string& keyword, size_t lineNumber) { std::vector list{}; @@ -55,8 +54,8 @@ std::vector parseListOfSystemValues(const std::string& arguments, const std:: std::string str; std::istringstream ss(arguments); - std::string errorString = - "No values could be read for keyword '" + keyword + "' at line: " + std::to_string(lineNumber) + "\n"; + std::string errorString = "No values could be read for keyword '" + keyword + + "' at line: " + std::to_string(lineNumber) + "\n"; while (ss >> str) { @@ -82,6 +81,7 @@ std::vector parseListOfSystemValues(const std::string& arguments, const std:: } return list; } + }; if (list.empty()) @@ -100,11 +100,11 @@ double parseDouble(const std::string& arguments, const std::string& keyword, siz if (ss >> value) { - return value; + return value; }; - std::string errorString = - "Numbers could not be read for keyword '" + keyword + "' at line: " + std::to_string(lineNumber) + "\n"; + std::string errorString = "Numbers could not be read for keyword '" + keyword + + "' at line: " + std::to_string(lineNumber) + "\n"; throw std::runtime_error(errorString); } @@ -116,7 +116,7 @@ int parseBoolean(const std::string& arguments, const std::string& keyword, size_ if (ss >> std::boolalpha >> value) { - return value; + return value; }; std::string str; @@ -127,24 +127,27 @@ int parseBoolean(const std::string& arguments, const std::string& keyword, size_ if (caseInSensStringCompare(str, "no")) return false; }; - std::string errorString = - "Booleands could not be read for keyword '" + keyword + "' at line: " + std::to_string(lineNumber) + "\n"; + std::string errorString = "Booleands could not be read for keyword '" + keyword + + "' at line: " + std::to_string(lineNumber) + "\n"; throw std::runtime_error(errorString); } -InputReader::InputReader(const std::string fileName) : components() + + +InputReader::InputReader(const std::string fileName): + components() { components.reserve(16); - std::ifstream fileInput{fileName}; + std::ifstream fileInput{ fileName }; std::string errorOpeningFile = "Required input file '" + fileName + "' does not exist"; if (!fileInput) throw std::runtime_error(errorOpeningFile); std::string line{}; std::string keyword{}; std::string arguments{}; - size_t lineNumber{0}; - size_t numberOfComponents{0}; + size_t lineNumber{ 0 }; + size_t numberOfComponents{ 0 }; while (std::getline(fileInput, line)) { @@ -164,22 +167,22 @@ InputReader::InputReader(const std::string fileName) : components() std::istringstream ss(arguments); if (ss >> str) { - if (caseInSensStringCompare(str, "Breakthrough")) + if (caseInSensStringCompare(str, "Breakthrough")) { simulationType = SimulationType::Breakthrough; continue; } - if (caseInSensStringCompare(str, "MixturePrediction")) + if (caseInSensStringCompare(str, "MixturePrediction")) { simulationType = SimulationType::MixturePrediction; continue; } - if (caseInSensStringCompare(str, "Fitting")) + if (caseInSensStringCompare(str, "Fitting")) { simulationType = SimulationType::Fitting; continue; } - if (caseInSensStringCompare(str, "Test")) + if (caseInSensStringCompare(str, "Test")) { simulationType = SimulationType::Test; continue; @@ -192,22 +195,22 @@ InputReader::InputReader(const std::string fileName) : components() std::istringstream ss(arguments); if (ss >> str) { - if (caseInSensStringCompare(str, "IAST")) + if (caseInSensStringCompare(str, "IAST")) { mixturePredictionMethod = 0; continue; } - if (caseInSensStringCompare(str, "SIAST")) + if (caseInSensStringCompare(str, "SIAST")) { mixturePredictionMethod = 1; continue; } - if (caseInSensStringCompare(str, "EI")) + if (caseInSensStringCompare(str, "EI")) { mixturePredictionMethod = 2; continue; } - if (caseInSensStringCompare(str, "SEI")) + if (caseInSensStringCompare(str, "SEI")) { mixturePredictionMethod = 3; continue; @@ -220,12 +223,12 @@ InputReader::InputReader(const std::string fileName) : components() std::istringstream ss(arguments); if (ss >> str) { - if (caseInSensStringCompare(str, "FastIAS")) + if (caseInSensStringCompare(str, "FastIAS")) { IASTMethod = 0; continue; } - if (caseInSensStringCompare(str, "Bisection")) + if (caseInSensStringCompare(str, "Bisection")) { IASTMethod = 1; continue; @@ -291,17 +294,17 @@ InputReader::InputReader(const std::string fileName) : components() std::istringstream ss(arguments); if (ss >> str) { - if (caseInSensStringCompare(str, "Log")) + if (caseInSensStringCompare(str, "Log")) { pressureScale = 0; continue; } - if (caseInSensStringCompare(str, "Linear")) + if (caseInSensStringCompare(str, "Linear")) { pressureScale = 1; continue; } - if (caseInSensStringCompare(str, "Normal")) + if (caseInSensStringCompare(str, "Normal")) { pressureScale = 1; continue; @@ -331,7 +334,7 @@ InputReader::InputReader(const std::string fileName) : components() { autoNumberOfTimeSteps = true; } - else + else { size_t value = parse(arguments, keyword, lineNumber); this->numberOfTimeSteps = value; @@ -353,7 +356,7 @@ InputReader::InputReader(const std::string fileName) : components() double value = parseDouble(arguments, keyword, lineNumber); this->pulseTime = value; continue; - } + } if (caseInSensStringCompare(keyword, "TimeStep")) { double value = parseDouble(arguments, keyword, lineNumber); @@ -410,7 +413,7 @@ InputReader::InputReader(const std::string fileName) : components() { std::istringstream ss(arguments); std::cout << "arguments: " << arguments << std::endl; - std::string c, moleculeNameKeyword, remainder, componentName; + std::string c, moleculeNameKeyword,remainder,componentName; ss >> c >> moleculeNameKeyword >> componentName; std::getline(ss, remainder); @@ -462,7 +465,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Langmuir")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 2) + if(values.size() < 2) { throw std::runtime_error("Error: Langmuir requires two parameters"); } @@ -474,7 +477,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Anti-Langmuir")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 2) + if(values.size() < 2) { throw std::runtime_error("Error: Anti-Langmuir requires two parameters"); } @@ -486,7 +489,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "BET")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: BET requires three parameters"); } @@ -498,7 +501,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Henry")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 1) + if(values.size() < 1) { throw std::runtime_error("Error: Henry requires one parameter"); } @@ -510,7 +513,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Freundlich")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 2) + if(values.size() < 2) { throw std::runtime_error("Error: Freundlich requires two parameters"); } @@ -522,7 +525,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Sips")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Sips requires three parameters"); } @@ -534,7 +537,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Langmuir-Freundlich")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Langmuir-Freundlich requires three parameters"); } @@ -546,7 +549,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Redlich-Peterson")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Redlich-Peterson requires three parameters"); } @@ -558,7 +561,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Toth")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Toth requires three parameters"); } @@ -570,7 +573,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Unilan")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Unilan requires three parameters"); } @@ -582,7 +585,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "O'Brian&Myers")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: O'Brien&Myers requires three parameters"); } @@ -594,7 +597,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Quadratic")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Quadratic requires three parameters"); } @@ -606,7 +609,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Temkin")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Temkin requires three parameters"); } @@ -618,7 +621,7 @@ InputReader::InputReader(const std::string fileName) : components() if (caseInSensStringCompare(keyword, "Bingel&Walton")) { std::vector values = parseListOfSystemValues(arguments, keyword, lineNumber); - if (values.size() < 3) + if(values.size() < 3) { throw std::runtime_error("Error: Bingel&Walton requires three parameters"); } @@ -628,7 +631,7 @@ InputReader::InputReader(const std::string fileName) : components() continue; } - if (!(startsWith(keyword, "//") || startsWith(keyword, "#"))) + if(!(startsWith(keyword, "//") || startsWith(keyword, "#"))) { std::cout << "Error: unknown keyword (" << keyword << ") with arguments (" << arguments << ")" << std::endl; exit(0); @@ -637,17 +640,17 @@ InputReader::InputReader(const std::string fileName) : components() } // normalize gas-phase mol-fractions to unity - if (simulationType != SimulationType::Fitting) + if(simulationType != SimulationType::Fitting) { double sum = 0.0; - for (size_t j = 0; j < components.size(); ++j) + for(size_t j = 0; j < components.size(); ++j) { sum += components[j].Yi0; } - if (std::abs(sum - 1.0) > 1e-15) + if(std::abs(sum-1.0)>1e-15) { std::cout << "Normalizing: Gas-phase molfractions did not sum exactly to unity!\n\n"; - for (size_t j = 0; j < components.size(); ++j) + for(size_t j = 0; j < components.size(); ++j) { components[j].Yi0 /= sum; } @@ -656,9 +659,9 @@ InputReader::InputReader(const std::string fileName) : components() numberOfCarrierGases = 0; carrierGasComponent = 0; - for (size_t j = 0; j < components.size(); ++j) + for(size_t j = 0; j < components.size(); ++j) { - if (components[j].isCarrierGas) + if(components[j].isCarrierGas) { carrierGasComponent = j; std::vector values{1.0, 0.0}; @@ -670,13 +673,13 @@ InputReader::InputReader(const std::string fileName) : components() } } - if ((mixturePredictionMethod == 2) || (mixturePredictionMethod == 3)) + if((mixturePredictionMethod == 2) || (mixturePredictionMethod == 3)) { - for (size_t i = 0; i < components.size(); ++i) + for(size_t i = 0; i < components.size(); ++i) { - for (size_t j = 0; j < components[i].isotherm.numberOfSites; ++j) + for(size_t j = 0; j < components[i].isotherm.numberOfSites; ++j) { - if (components[i].isotherm.sites[j].type != Isotherm::Type::Langmuir) + if( components[i].isotherm.sites[j].type != Isotherm::Type::Langmuir) { throw std::runtime_error("Error: Explicit mixture prediction must use single Langmuir isotherms"); } @@ -684,55 +687,57 @@ InputReader::InputReader(const std::string fileName) : components() } } + maxIsothermTerms = 0; - if (!components.empty()) + if(!components.empty()) { - std::vector::iterator maxIsothermTermsIterator = - std::max_element(components.begin(), components.end(), [](Component& lhs, Component& rhs) - { return lhs.isotherm.numberOfSites < rhs.isotherm.numberOfSites; }); + std::vector::iterator maxIsothermTermsIterator = std::max_element(components.begin(), components.end(), + [] (Component& lhs, Component& rhs) { + return lhs.isotherm.numberOfSites < rhs.isotherm.numberOfSites; + }); maxIsothermTerms = maxIsothermTermsIterator->isotherm.numberOfSites; } - if (simulationType == SimulationType::Breakthrough) + if(simulationType == SimulationType::Breakthrough) { - if (numberOfCarrierGases == 0) + if(numberOfCarrierGases == 0) { throw std::runtime_error("Error: no carrier gas component present"); } - if (numberOfCarrierGases > 1) + if(numberOfCarrierGases > 1) { throw std::runtime_error("Error: multiple carrier gas component present (there can be only one)"); } - if (temperature < 0.0) + if(temperature < 0.0) { throw std::runtime_error("Error: temperature not set (Use e.g.: 'Temperature 300'"); } - if (columnVoidFraction < 0.0) + if(columnVoidFraction < 0.0) { throw std::runtime_error("Error: void-fraction of the colum not set (Use e.g.: 'ColumnVoidFraction 0.4'"); } - if (particleDensity < 0.0) + if(particleDensity < 0.0) { throw std::runtime_error("Error: particle density not set (Use e.g.: 'ParticleDensity 1408.2'"); } - if (totalPressure < 0.0) + if(totalPressure < 0.0) { throw std::runtime_error("Error: total pressure bot set (Use e.g.: 'TotalPressure 1e5'"); } - if (columnEntranceVelocity < 0.0) + if(columnEntranceVelocity < 0.0) { throw std::runtime_error("Error: column entrance velocity not set (Use e.g.: 'columnEntranceVelocity 300'"); } - if ((numberOfTimeSteps == 0) && (!autoNumberOfTimeSteps)) + if((numberOfTimeSteps == 0) && (!autoNumberOfTimeSteps)) { throw std::runtime_error("Error: number of time steps not set (Use e.g.: 'NumberOfTimeSteps 5000000'"); } - if (numberOfGridPoints == 0) + if(numberOfGridPoints == 0) { throw std::runtime_error("Error: number of grid points not set (Use e.g.: 'NumberOfGridPoints 50'"); } - if (columnLength < 0) + if(columnLength < 0) { throw std::runtime_error("Error: column length not set (Use e.g.: 'ColumnLength 0.3'"); } diff --git a/src/inputreader.h b/src/inputreader.h index 854ad2e..fc6155b 100644 --- a/src/inputreader.h +++ b/src/inputreader.h @@ -1,72 +1,57 @@ #pragma once -#include #include +#include #include "component.h" extern bool startsWith(const std::string &str, const std::string &prefix); -extern std::string trim(const std::string &s); +extern std::string trim(const std::string& s); -/** - * \brief Parses input files and stores simulation parameters. - * - * The InputReader struct is responsible for reading and parsing input files containing simulation parameters. - * It stores all the necessary data required to set up and run simulations, including components, simulation types, - * and various parameters related to the simulation environment. - */ struct InputReader { - /** - * \brief Constructs an InputReader and parses the given input file. - * - * \param fileName The name of the input file to parse. - */ InputReader(const std::string fileName); - /** - * \brief Enumerates the types of simulations supported. - */ enum class SimulationType { - Breakthrough = 0, ///< Breakthrough simulation. - MixturePrediction = 1, ///< Mixture prediction simulation. - Fitting = 2, ///< Fitting simulation. - Test = 3 ///< Test simulation. + Breakthrough = 0, + MixturePrediction = 1, + Fitting = 2, + Test = 3 }; - std::vector components; ///< The list of components involved in the simulation. - size_t numberOfCarrierGases{0}; ///< The number of carrier gas components. - size_t carrierGasComponent{0}; ///< The index of the carrier gas component. - size_t maxIsothermTerms{0}; ///< The maximum number of isotherm terms among all components. - - SimulationType simulationType{SimulationType::Breakthrough}; ///< The type of simulation to perform. - size_t mixturePredictionMethod{0}; ///< The method used for mixture prediction. - size_t IASTMethod{0}; ///< The method used for IAST calculations. - std::string displayName{"Column"}; ///< The display name for the simulation. - double temperature{433.0}; ///< The simulation temperature in Kelvin. - double columnVoidFraction{0.4}; ///< The void fraction of the column. - double particleDensity{1000.0}; ///< The density of the particles in kg/m^3. - double totalPressure{1.0e6}; ///< The total pressure in the system in Pa. - double pressureGradient{0.0}; ///< The pressure gradient in the column. - double columnEntranceVelocity{0.1}; ///< The entrance velocity of the column in m/s. - double columnLength{0.3}; ///< The length of the column in meters. - - size_t numberOfTimeSteps{0}; ///< The number of time steps in the simulation. - bool autoNumberOfTimeSteps{true}; ///< Whether to automatically determine the number of time steps. - double timeStep{0.0005}; ///< The time step size in seconds. - bool pulseBreakthrough{false}; ///< Whether to use pulse breakthrough mode. - double pulseTime{0.0}; ///< The duration of the pulse in seconds. - size_t printEvery{10000}; ///< The interval at which to print output. - size_t writeEvery{10000}; ///< The interval at which to write output. - size_t numberOfGridPoints{100}; ///< The number of grid points in the column. - - double pressureStart{-1.0}; ///< The starting pressure for isotherm calculations. - double pressureEnd{-1.0}; ///< The ending pressure for isotherm calculations. - size_t numberOfPressurePoints{100}; ///< The number of pressure points to calculate. - size_t pressureScale{0}; ///< The scale for pressure calculations (0 for log, 1 for linear). - - size_t columnPressure{0}; ///< The index of the column for pressure data. - size_t columnLoading{1}; ///< The index of the column for loading data. - size_t columnError{2}; ///< The index of the column for error data. + std::vector components; + size_t numberOfCarrierGases{ 0 }; + size_t carrierGasComponent{ 0 }; + size_t maxIsothermTerms{ 0 }; + + SimulationType simulationType{ SimulationType::Breakthrough }; + size_t mixturePredictionMethod{ 0 }; + size_t IASTMethod{ 0 }; + std::string displayName{"Column"}; + double temperature{ 433.0 }; + double columnVoidFraction{ 0.4 }; + double particleDensity{ 1000.0 }; + double totalPressure{ 1.0e6 }; + double pressureGradient{ 0.0 }; + double columnEntranceVelocity{ 0.1 }; + double columnLength { 0.3 }; + + size_t numberOfTimeSteps{ 0 }; + bool autoNumberOfTimeSteps{ true }; + double timeStep{ 0.0005 }; + bool pulseBreakthrough{ false }; + double pulseTime{ 0.0 }; + size_t printEvery{ 10000 }; + size_t writeEvery{ 10000 }; + size_t numberOfGridPoints{ 100 }; + + double pressureStart{ -1.0 }; + double pressureEnd{ -1.0 }; + size_t numberOfPressurePoints{ 100 }; + size_t pressureScale{ 0 }; + + size_t columnPressure{ 0 }; + size_t columnLoading{ 1 }; + size_t columnError{ 2 }; }; diff --git a/src/isotherm.cpp b/src/isotherm.cpp index de50594..d2e2fd8 100644 --- a/src/isotherm.cpp +++ b/src/isotherm.cpp @@ -2,21 +2,32 @@ #include -Isotherm::Isotherm(Isotherm::Type t, const std::vector &values, size_t numberOfValues) - : type(t), parameters(values), numberOfParameters(numberOfValues) +Isotherm::Isotherm(Isotherm::Type t, const std::vector &values, size_t numberOfValues): + type(t), + parameters(values), + numberOfParameters(numberOfValues) { } -Isotherm::Isotherm(size_t t, const std::vector &values, size_t numberOfValues) - : type(Isotherm::Type(t)), parameters(values), numberOfParameters(numberOfValues) + +Isotherm::Isotherm(std::string t, const std::vector &values, size_t numberOfValues) + : parameters(values), numberOfParameters(numberOfValues) { + auto itr = Isotherm::isotherm_map.find(t); + if (itr != isotherm_map.end()) + { + type = itr->second; + } + else + { + std::cout << "Error: unknown isotherm type (" << t << ")."; + exit(0); + } } -void Isotherm::print() const { std::cout << repr(); } - std::string Isotherm::repr() const { std::string s; - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { @@ -133,16 +144,16 @@ std::string Isotherm::repr() const bool Isotherm::isUnphysical() const { - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { - if (parameters[0] < 0 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10) return true; + if(parameters[0] < 0 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10 ) return true; return false; } case Isotherm::Type::Anti_Langmuir: { - if (parameters[0] < 0 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10) return true; + if(parameters[0] < 0 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10 ) return true; return false; } case Isotherm::Type::BET: @@ -151,61 +162,57 @@ bool Isotherm::isUnphysical() const } case Isotherm::Type::Henry: { - if (parameters[0] < 0.0) return true; + if(parameters[0] < 0.0) return true; return false; } case Isotherm::Type::Freundlich: { - if (parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; + if(parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; return false; } case Isotherm::Type::Sips: { - if (parameters[0] < 0 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10 || - parameters[2] < 0.0 || parameters[2] > 100.0) - return true; + if(parameters[0] < 0 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10 || parameters[2] < 0.0 || parameters[2] > 100.0 ) return true; return false; } case Isotherm::Type::Langmuir_Freundlich: { - if (parameters[0] < 1.0e-20 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10 || - parameters[2] < 0.0 || parameters[2] > 100.0) - return true; + if(parameters[0] < 1.0e-20 || parameters[0] > 1.0e20 || parameters[1] < 0.0 || parameters[1] > 1.0e10 || parameters[2] < 0.0 || parameters[2] > 100.0 ) return true; return false; } case Isotherm::Type::Redlich_Peterson: { - if (parameters[0] < 0.0 || parameters[1] < 0.0) return true; + if(parameters[0] < 0.0 || parameters[1] < 0.0) return true; return false; } case Isotherm::Type::Toth: { - if (parameters[0] < 0 || parameters[1] < 0.0 || parameters[2] < 0.0 || parameters[2] > 100.0) return true; + if(parameters[0] < 0 || parameters[1] < 0.0 || parameters[2] < 0.0 || parameters[2] > 100.0 ) return true; return false; } case Isotherm::Type::Unilan: { - if (parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; + if(parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; return false; } case Isotherm::Type::OBrien_Myers: { - if (parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; + if(parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; return false; } case Isotherm::Type::Quadratic: { - if (parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; + if(parameters[0] < 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; return false; } case Isotherm::Type::Temkin: { - if (parameters[0] <= 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; + if(parameters[0] <= 0.0 || parameters[1] < 0.0 || parameters[2] < 0.0) return true; return false; } case Isotherm::Type::BingelWalton: { - if (parameters[0] <= 0.0 || (parameters[1] + parameters[2]) < 1e-3) return true; + if(parameters[0] <= 0.0 || (parameters[1] + parameters[2]) < 1e-3) return true; return false; } default: @@ -215,7 +222,7 @@ bool Isotherm::isUnphysical() const void Isotherm::randomize(double maximumLoading) { - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { @@ -319,22 +326,22 @@ std::string Isotherm::gnuplotFunctionString(char c, size_t i) const { char stringBuffer[1024]; - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { - snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x/(1.0+%c[%ld]*x)", c, i, c, i + 1, c, i + 1); + snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x/(1.0+%c[%ld]*x)", c, i, c, i+1, c, i+1); return stringBuffer; } case Isotherm::Type::Anti_Langmuir: { - snprintf(stringBuffer, 1024, "%c[%ld]*x/(1.0-%c[%ld]*x)", c, i, c, i + 1); + snprintf(stringBuffer, 1024, "%c[%ld]*x/(1.0-%c[%ld]*x)", c, i, c, i+1); return stringBuffer; } case Isotherm::Type::BET: { - snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x/((1.0-%c[%ld]*x)*(1.0-%c[%ld]+%c[%ld]*x))", c, i, c, i + 1, c, - i + 2, c, i + 2, c, i + 1); + snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x/((1.0-%c[%ld]*x)*(1.0-%c[%ld]+%c[%ld]*x))", + c, i, c, i+1, c, i+2, c, i+2, c, i+1); return stringBuffer; } case Isotherm::Type::Henry: @@ -344,65 +351,60 @@ std::string Isotherm::gnuplotFunctionString(char c, size_t i) const } case Isotherm::Type::Freundlich: { - snprintf(stringBuffer, 1024, "%c[%ld]*x**[%ld]", c, i, i + 1); + snprintf(stringBuffer, 1024, "%c[%ld]*x**[%ld]", c, i, i+1); return stringBuffer; } case Isotherm::Type::Sips: { - snprintf(stringBuffer, 1024, "%c[%ld]*((%c[%ld]*x)**(1.0/%c[%ld]))/(1.0+(%c[%ld]*x)**(1.0/%c[%ld]))", c, i, c, - i + 1, c, i + 2, c, i + 1, c, i + 2); + snprintf(stringBuffer, 1024, "%c[%ld]*((%c[%ld]*x)**(1.0/%c[%ld]))/(1.0+(%c[%ld]*x)**(1.0/%c[%ld]))", + c, i, c, i+1, c, i+2, c, i+1, c, i+2); return stringBuffer; } case Isotherm::Type::Langmuir_Freundlich: { - snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x**%c[%ld]/(1.0+%c[%ld]*x**%c[%ld])", c, i, c, i + 1, c, i + 2, c, - i + 1, c, i + 2); + snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x**%c[%ld]/(1.0+%c[%ld]*x**%c[%ld])", + c, i, c, i+1, c, i+2, c, i +1, c, i+2); return stringBuffer; } case Isotherm::Type::Redlich_Peterson: { - snprintf(stringBuffer, 1024, "%c[%ld]*x/(1.0+%c[%ld]*x**%c[%ld])", c, i, c, i + 1, c, i + 2); + snprintf(stringBuffer, 1024, "%c[%ld]*x/(1.0+%c[%ld]*x**%c[%ld])", c, i, c, i+1, c, i+2); return stringBuffer; } case Isotherm::Type::Toth: { - snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x/((1.0+(%c[%ld]*x)**%c[%ld])**(1.0/%c[%ld]))", c, i, c, i + 1, c, - i + 1, c, i + 2, c, i + 2); + snprintf(stringBuffer, 1024, "%c[%ld]*%c[%ld]*x/((1.0+(%c[%ld]*x)**%c[%ld])**(1.0/%c[%ld]))", + c, i, c, i+1, c, i+1, c, i+2, c, i+2); return stringBuffer; } case Isotherm::Type::Unilan: { - snprintf(stringBuffer, 1024, - "(%c[%ld]/(2.0*%c[%ld]))*log((1.0+%c[%ld]*exp(%c[%ld])*x)/(1.0+%c[%ld]*exp(-%c[%ld])*x))", c, i, c, - i + 2, c, i + 1, c, i + 2, c, i + 1, c, i + 2); + snprintf(stringBuffer, 1024, "(%c[%ld]/(2.0*%c[%ld]))*log((1.0+%c[%ld]*exp(%c[%ld])*x)/(1.0+%c[%ld]*exp(-%c[%ld])*x))", + c, i, c, i+2, c, i+1, c, i+2, c, i+1, c, i+2); return stringBuffer; } case Isotherm::Type::OBrien_Myers: { - snprintf(stringBuffer, 1024, - "%c[%ld]*(%c[%ld]*x/(1.0+%c[%ld]*x) + (%c[%ld]**2)*%c[%ld]*x*(1.0-%c[%ld]*x)/(2.0*(1.0+%c[%ld]*x)**3))", - c, i, c, i + 1, c, i + 1, c, i + 2, c, i + 1, c, i + 1, c, i + 1); + snprintf(stringBuffer, 1024, "%c[%ld]*(%c[%ld]*x/(1.0+%c[%ld]*x) + (%c[%ld]**2)*%c[%ld]*x*(1.0-%c[%ld]*x)/(2.0*(1.0+%c[%ld]*x)**3))", + c, i, c, i+1, c, i+1, c, i+2, c, i+1, c, i+1, c, i+1); return stringBuffer; } case Isotherm::Type::Quadratic: { - snprintf(stringBuffer, 1024, "%c[%ld]*(%c[%ld]*x+2.0*%c[%ld]*x**2)/(1.0+%c[%ld]*x+%c[%ld]*x**2)", c, i, c, i + 1, - c, i + 2, c, i + 1, c, i + 2); + snprintf(stringBuffer, 1024, "%c[%ld]*(%c[%ld]*x+2.0*%c[%ld]*x**2)/(1.0+%c[%ld]*x+%c[%ld]*x**2)", + c, i, c, i+1, c, i+2, c, i+1, c, i+2); return stringBuffer; } case Isotherm::Type::Temkin: { - snprintf(stringBuffer, 1024, - "%c[%ld]*(%c[%ld]*x/(1.0+%c[%ld]*x))+%c[%ld]*%c[%ld]*((%c[%ld]*x/(1.0+%c[%ld]*x))**2)*(%c[%ld]*x/" - "(1.0+%c[%ld]*x)-1.0)", - c, i, c, i + 1, c, i + 1, c, i, c, i + 2, c, i + 1, c, i + 1, c, i + 1, c, i + 1); + snprintf(stringBuffer, 1024, "%c[%ld]*(%c[%ld]*x/(1.0+%c[%ld]*x))+%c[%ld]*%c[%ld]*((%c[%ld]*x/(1.0+%c[%ld]*x))**2)*(%c[%ld]*x/(1.0+%c[%ld]*x)-1.0)", + c, i, c, i+1, c, i+1, c, i, c, i+2, c, i+1, c, i+1, c, i+1, c, i+1); return stringBuffer; } case Isotherm::Type::BingelWalton: { - snprintf(stringBuffer, 1024, - "%c[%ld]*(1.0-exp(-(%c[%ld]+%c[%ld])*x))/(1.0+(%c[%ld]/%c[%ld])*exp(-(%c[%ld]+%c[%ld])*x))", c, i, c, - i + 1, c, i + 2, c, i + 2, c, i + 1, c, i + 1, c, i + 2); + snprintf(stringBuffer, 1024, "%c[%ld]*(1.0-exp(-(%c[%ld]+%c[%ld])*x))/(1.0+(%c[%ld]/%c[%ld])*exp(-(%c[%ld]+%c[%ld])*x))", + c, i, c, i+1, c, i+2, c, i+2, c, i+1, c, i +1, c, i+2); return stringBuffer; } default: diff --git a/src/isotherm.h b/src/isotherm.h index 758b6de..f8d6f55 100644 --- a/src/isotherm.h +++ b/src/isotherm.h @@ -1,23 +1,21 @@ #pragma once -#include #include +#include #include +#include #define _USE_MATH_DEFINES #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) -#include + #include #else -#include + #include #endif #include #include -#include "random_numbers.h" #include "special_functions.h" +#include "random_numbers.h" -/** - * \brief Maximum number of terms supported in the isotherm calculations. - */ constexpr size_t maxTerms = 5; // Langmuir: @@ -29,85 +27,55 @@ constexpr size_t maxTerms = 5; // parameter 1: N // parameter 2: power -/** - * \brief Represents an isotherm model for adsorption processes. - * - * The Isotherm struct encapsulates various isotherm models used to describe the adsorption equilibrium - * between a fluid and a solid at a constant temperature. It supports different types of isotherm models, - * such as Langmuir, Freundlich, BET, and others. - */ struct Isotherm { - /** - * \brief Enumeration of the different types of isotherm models. - */ enum class Type { - Langmuir = 0, ///< Langmuir isotherm model - Anti_Langmuir = 1, ///< Anti-Langmuir isotherm model - BET = 2, ///< Brunauer–Emmett–Teller (BET) isotherm model - Henry = 3, ///< Henry's law isotherm model - Freundlich = 4, ///< Freundlich isotherm model - Sips = 5, ///< Sips isotherm model - Langmuir_Freundlich = 6, ///< Langmuir-Freundlich isotherm model - Redlich_Peterson = 7, ///< Redlich-Peterson isotherm model - Toth = 8, ///< Toth isotherm model - Unilan = 9, ///< Unilan isotherm model - OBrien_Myers = 10, ///< O'Brien and Myers isotherm model - Quadratic = 11, ///< Quadratic isotherm model - Temkin = 12, ///< Temkin isotherm model - BingelWalton = 13 ///< Bingel and Walton isotherm model + Langmuir = 0, + Anti_Langmuir = 1, + BET = 2, + Henry = 3, + Freundlich = 4, + Sips = 5, + Langmuir_Freundlich = 6, + Redlich_Peterson = 7, + Toth = 8, + Unilan = 9, + OBrien_Myers = 10, + Quadratic = 11, + Temkin = 12, + BingelWalton = 13 + }; + + std::map isotherm_map = { + {"Langmuir", Type::Langmuir}, + {"Anti-Langmuir", Type::Anti_Langmuir}, + {"BET", Type::BET}, + {"Henry", Type::Henry}, + {"Freundlich", Type::Freundlich}, + {"Sips", Type::Sips}, + {"Langmuir-Freundlich", Type::Langmuir_Freundlich}, + {"Redlich-Peterson", Type::Redlich_Peterson}, + {"Toth", Type::Toth}, + {"Unilan", Type::Unilan}, + {"OBrien_Myers", Type::OBrien_Myers}, + {"Quadratic", Type::Quadratic}, + {"Temkin", Type::Temkin}, + {"Bingel-Walton", Type::BingelWalton}, }; - /** - * \brief Constructs an Isotherm with specified type and parameters. - * - * Initializes an Isotherm object with the given isotherm type, parameter values, and the number of parameters. - * - * \param type The type of the isotherm model. - * \param values A vector of parameter values for the isotherm model. - * \param numberOfValues The number of parameters. - */ Isotherm(Isotherm::Type type, const std::vector &values, size_t numberOfValues); + Isotherm(std::string type, const std::vector &values, size_t numberOfValues); + + Isotherm::Type type; + std::vector parameters; + size_t numberOfParameters; - /** - * \brief Constructs an Isotherm with specified type index and parameters. - * - * Initializes an Isotherm object using the type index, parameter values, and the number of parameters. - * - * \param t The index of the isotherm type. - * \param values A vector of parameter values for the isotherm model. - * \param numberOfValues The number of parameters. - */ - Isotherm(size_t t, const std::vector &values, size_t numberOfValues); - - Isotherm::Type type; ///< The type of the isotherm model. - std::vector parameters; ///< Parameter values for the isotherm model. - size_t numberOfParameters; ///< The number of parameters for the isotherm model. - - /** - * \brief Prints a representation of the isotherm to standard output. - */ - void print() const; - - /** - * \brief Returns a string representation of the isotherm. - * - * \return A string representing the isotherm model and its parameters. - */ std::string repr() const; - /** - * \brief Computes the adsorption amount at a given pressure. - * - * Calculates the adsorption loading based on the isotherm model and parameters for the specified pressure. - * - * \param pressure The pressure at which to evaluate the isotherm. - * \return The adsorption amount at the given pressure. - */ inline double value(double pressure) const { - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { @@ -152,16 +120,15 @@ struct Isotherm } case Isotherm::Type::Unilan: { - double temp1 = 1.0 + parameters[1] * std::exp(parameters[2]) * pressure; - double temp2 = 1.0 + parameters[1] * std::exp(-parameters[2]) * pressure; + double temp1 = 1.0 + parameters[1] * std::exp(parameters[2]) * pressure; + double temp2 = 1.0 + parameters[1] * std::exp(-parameters[2]) * pressure; return parameters[0] * (0.5 / parameters[2]) * std::log(temp1 / temp2); } case Isotherm::Type::OBrien_Myers: { double temp1 = parameters[1] * pressure; double temp2 = 1.0 + temp1; - return parameters[0] * - (temp1 / temp2 + parameters[2] * parameters[2] * temp1 * (1.0 - temp1) / (temp2 * temp2 * temp2)); + return parameters[0] * (temp1 / temp2 + parameters[2] * parameters[2] * temp1 * (1.0 - temp1) / (temp2 * temp2 * temp2)); } case Isotherm::Type::Quadratic: { @@ -177,25 +144,18 @@ struct Isotherm } case Isotherm::Type::BingelWalton: { - return parameters[0] * (1.0 - std::exp(-(parameters[1] + parameters[2]) * pressure)) / + return parameters[0] * (1.0 - std::exp(-(parameters[1] + parameters[2]) * pressure)) / (1.0 + (parameters[2] / parameters[1]) * std::exp(-(parameters[1] + parameters[2]) * pressure)); } default: - throw std::runtime_error("Error: unknown isotherm type"); + throw std::runtime_error("Error: unkown isotherm type"); } } - /** - * \brief Computes the reduced grand potential (spreading pressure) at a given pressure. - * - * Calculates the reduced grand potential psi based on the isotherm model and parameters for the specified pressure. - * - * \param pressure The pressure at which to evaluate the reduced grand potential. - * \return The reduced grand potential psi at the given pressure. - */ + // the reduced grand potential psi (spreading pressure) for this pressure inline double psiForPressure(double pressure) const { - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { @@ -207,9 +167,8 @@ struct Isotherm } case Isotherm::Type::BET: { - return (parameters[0] * parameters[1]) * - std::log((1.0 - parameters[2] + parameters[1] * pressure) / - ((1.0 - parameters[2]) * (1.0 - parameters[2] * pressure))) / + return (parameters[0] * parameters[1]) * std::log((1.0 - parameters[2] + parameters[1] * pressure) / + ((1.0 - parameters[2]) * (1.0 - parameters[2] * pressure))) / (parameters[1] + parameters[2] - parameters[2] * parameters[2]); } case Isotherm::Type::Henry: @@ -218,11 +177,11 @@ struct Isotherm } case Isotherm::Type::Freundlich: { - return parameters[0] * parameters[1] * std::pow(pressure, 1.0 / parameters[1]); + return parameters[0] * parameters[1] * std::pow(pressure, 1.0/parameters[1]); } case Isotherm::Type::Sips: { - return parameters[2] * parameters[0] * std::log(1.0 + std::pow(parameters[1] * pressure, 1.0 / parameters[2])); + return parameters[2] * parameters[0] * std::log(1.0 + std::pow(parameters[1] * pressure, 1.0/parameters[2])); } case Isotherm::Type::Langmuir_Freundlich: { @@ -230,22 +189,21 @@ struct Isotherm } case Isotherm::Type::Redlich_Peterson: { - if (parameters[1] * std::pow(pressure, parameters[2]) < 1.0) + if(parameters[1] * std::pow(pressure, parameters[2]) < 1.0) { - return parameters[0] * pressure * - hypergeometric2F1(1.0, 1.0 / parameters[2], 1.0 + 1.0 / parameters[2], - -parameters[1] * std::pow(pressure, parameters[2])); + return parameters[0] * pressure * hypergeometric2F1(1.0, 1.0 / parameters[2], 1.0 + 1.0 / parameters[2], + -parameters[1] * std::pow(pressure, parameters[2])); } - else + else { double prefactor = parameters[0] / parameters[2]; double temp = M_PI / (std::pow(parameters[1], 1.0 / parameters[2]) * std::sin(M_PI * 1.0 / parameters[2])); - double term1 = -1.0 / (parameters[1] * std::pow(pressure, parameters[2])); + double term1 = -1.0/(parameters[1] * std::pow(pressure, parameters[2])); double numerator = 1.0; - double sum = 0.0; + double sum=0.0; // quickly converging sum - for (size_t k = 1; k <= 15; k++) + for(size_t k = 1; k <= 15; k++) { numerator *= term1; sum += numerator / (static_cast(k) * parameters[2] - 1.0); @@ -258,12 +216,12 @@ struct Isotherm double temp = parameters[1] * pressure; double theta = temp / std::pow(1.0 + std::pow(temp, parameters[2]), 1.0 / parameters[2]); double theta_pow = std::pow(theta, parameters[2]); - double psi = parameters[0] * (theta - (theta / parameters[2]) * std::log(1.0 - theta_pow)); + double psi = parameters[0] * (theta - (theta / parameters[2]) * std::log(1.0-theta_pow)); // use the first 100 terms of the sum double temp1 = parameters[0] * theta; double temp2 = 0.0; - for (size_t k = 1; k <= 100; ++k) + for(size_t k = 1; k <= 100; ++k) { temp1 *= theta_pow; temp2 += parameters[2]; @@ -274,8 +232,8 @@ struct Isotherm } case Isotherm::Type::Unilan: { - return (0.5 * parameters[0] / parameters[2]) * (li2(-parameters[1] * std::exp(-parameters[2]) * pressure) - - li2(-parameters[1] * std::exp(parameters[2]) * pressure)); + return (0.5 * parameters[0] / parameters[2]) * (li2(-parameters[1] * std::exp(-parameters[2]) * pressure) - + li2(-parameters[1] * std::exp(parameters[2]) * pressure)); } case Isotherm::Type::OBrien_Myers: { @@ -302,29 +260,29 @@ struct Isotherm double acc = 1e-6; // Romberg integration: https://en.wikipedia.org/wiki/Romberg%27s_method - std::vector R1(max_steps), R2(max_steps); // buffers - double *Rp = &R1[0], *Rc = &R2[0]; // Rp is previous row, Rc is current row - double h = pressure - start; // step size - Rp[0] = (value(start) / start + value(pressure) / pressure) * h * 0.5; // first trapezoidal step + std::vector R1(max_steps), R2(max_steps); // buffers + double *Rp = &R1[0], *Rc = &R2[0]; // Rp is previous row, Rc is current row + double h = pressure - start; //step size + Rp[0] = (value(start)/start + value(pressure)/pressure)*h*0.5; // first trapezoidal step for (size_t i = 1; i < max_steps; ++i) { h /= 2.0; double c = 0; - size_t ep = size_t{1} << (i - 1); // 2^(n-1) + size_t ep = size_t{1} << (i-1); //2^(n-1) for (size_t j = 1; j <= ep; ++j) { - c += value(start + static_cast(2 * j - 1) * h) / (start + static_cast(2 * j - 1) * h); + c += value(start + static_cast(2*j-1)*h) / (start + static_cast(2*j-1)*h); } - Rc[0] = h * c + 0.5 * Rp[0]; // R(i,0) + Rc[0] = h*c + 0.5*Rp[0]; // R(i,0) for (size_t j = 1; j <= i; ++j) { - double n_k = std::pow(4, j); - Rc[j] = (n_k * Rc[j - 1] - Rp[j - 1]) / (n_k - 1); // compute R(i,j) + double n_k = std::pow(4, j); + Rc[j] = (n_k*Rc[j-1] - Rp[j-1]) / (n_k-1); // compute R(i,j) } - if (i > 1 && std::fabs(Rp[i - 1] - Rc[i]) < acc) + if (i > 1 && std::fabs(Rp[i-1]-Rc[i]) < acc) { return Rc[i]; } @@ -333,26 +291,16 @@ struct Isotherm Rp = Rc; Rc = rt; } - return Rp[max_steps - 1]; // return our best guess + return Rp[max_steps-1]; // return our best guess } default: - throw std::runtime_error("Error: unknown isotherm type"); + throw std::runtime_error("Error: unkown isotherm type"); } } - /** - * \brief Computes the inverse pressure corresponding to a given reduced grand potential psi. - * - * Calculates the pressure that corresponds to the specified reduced grand potential psi using the isotherm model. - * This function may cache intermediate results to improve performance in repeated calculations. - * - * \param reduced_grand_potential The reduced grand potential psi. - * \param cachedP0 A reference to a cached pressure value used to initialize the calculation. - * \return The inverse of the pressure corresponding to the given psi. - */ inline double inversePressureForPsi(double reduced_grand_potential, double &cachedP0) const { - switch (type) + switch(type) { case Isotherm::Type::Langmuir: { @@ -370,12 +318,12 @@ struct Isotherm } case Isotherm::Type::Freundlich: { - return std::pow((parameters[0] * parameters[1]) / reduced_grand_potential, parameters[1]); + return std::pow((parameters[0] * parameters[1])/reduced_grand_potential, parameters[1]); } case Isotherm::Type::Sips: { - return parameters[1] / - std::pow((std::exp(reduced_grand_potential / (parameters[2] * parameters[0])) - 1.0), parameters[2]); + return parameters[1] / std::pow((std::exp(reduced_grand_potential/ + (parameters[2] * parameters[0])) - 1.0), parameters[2]); } case Isotherm::Type::Langmuir_Freundlich: { @@ -388,7 +336,7 @@ struct Isotherm // from here on, work with pressure, and return 1.0 / pressure at the end of the routine double p_start; - if (cachedP0 <= 0.0) + if(cachedP0 <= 0.0) { p_start = 5.0; } @@ -405,7 +353,7 @@ struct Isotherm double left_bracket = p_start; double right_bracket = p_start; - if (s < reduced_grand_potential) + if(s < reduced_grand_potential) { // find the bracket on the right do @@ -414,17 +362,17 @@ struct Isotherm s = psiForPressure(right_bracket); ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "reduced_grand_potential: " << reduced_grand_potential << std::endl; std::cout << "psi: " << s << std::endl; std::cout << "p_start: " << p_start << std::endl; std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; - throw std::runtime_error( - "Error (Inverse bisection): initial bracketing (for sum < 1) does NOT converge\n"); + throw std::runtime_error("Error (Inverse bisection): initial bracketing (for sum < 1) does NOT converge\n"); } - } while (s < reduced_grand_potential); + } + while(s < reduced_grand_potential); } else { @@ -435,17 +383,17 @@ struct Isotherm s = psiForPressure(left_bracket); ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "reduced_grand_potential: " << reduced_grand_potential << std::endl; std::cout << "psi: " << s << std::endl; std::cout << "p_start: " << p_start << std::endl; std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; - throw std::runtime_error( - "Error (Inverse bisection): initial bracketing (for sum > 1) does NOT converge\n"); + throw std::runtime_error("Error (Inverse bisection): initial bracketing (for sum > 1) does NOT converge\n"); } - } while (s > reduced_grand_potential); + } + while(s > reduced_grand_potential); } do @@ -453,19 +401,20 @@ struct Isotherm double middle = 0.5 * (left_bracket + right_bracket); s = psiForPressure(middle); - if (s > reduced_grand_potential) - right_bracket = middle; + if(s > reduced_grand_potential) + right_bracket = middle; else - left_bracket = middle; + left_bracket = middle; ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; throw std::runtime_error("Error (Inverse bisection): initial bracketing (for sum < 1) does NOT converge\n"); } - } while (std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); + } + while(std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); double middle = 0.5 * (left_bracket + right_bracket); @@ -477,34 +426,11 @@ struct Isotherm } } - /** - * \brief Randomizes the isotherm parameters within specified bounds. - * - * Sets the isotherm parameters to random values within physically meaningful ranges, - * based on the specified maximum loading. - * - * \param maximumLoading The maximum adsorption loading used to scale the randomized parameters. - */ void randomize(double maximumLoading); - /** - * \brief Checks if the isotherm parameters are physically meaningful. - * - * Determines whether the isotherm parameters are within acceptable physical ranges. - * - * \return True if the parameters are unphysical; false otherwise. - */ bool isUnphysical() const; - /** - * \brief Generates a Gnuplot-compatible function string for the isotherm. - * - * Creates a string representing the isotherm function that can be used in Gnuplot scripts, - * using the specified character and index for parameter substitution. - * - * \param s The character representing the parameter array in Gnuplot (e.g., 'c' or 'p'). - * \param i The starting index for the parameters. - * \return A string representing the isotherm function for Gnuplot. - */ std::string gnuplotFunctionString(char s, size_t i) const; }; + + diff --git a/src/main.cpp b/src/main.cpp index 07fb9a8..0bf617f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,28 +1,29 @@ #include -#include "breakthrough.h" -#include "fitting.h" +#include "special_functions.h" + #include "inputreader.h" +#include "breakthrough.h" #include "mixture_prediction.h" -#include "special_functions.h" +#include "fitting.h" int main(void) { - try + try { InputReader reader("simulation.input"); - switch (reader.simulationType) + switch(reader.simulationType) { case InputReader::SimulationType::Breakthrough: default: { Breakthrough breakthrough(reader); - breakthrough.print(); + std::cout << breakthrough.repr(); breakthrough.initialize(); - breakthrough.createPlotScript(); - breakthrough.createMovieScripts(); + // breakthrough.createPlotScript(); Not used because I have not modified these based on new equations + // breakthrough.createMovieScripts(); Not used because I have not modified these based on new equations breakthrough.run(); break; } @@ -30,13 +31,13 @@ int main(void) { MixturePrediction mixture(reader); - mixture.print(); + std::cout << mixture.repr(); mixture.run(); mixture.createPureComponentsPlotScript(); mixture.createMixturePlotScript(); mixture.createMixtureAdsorbedMolFractionPlotScript(); mixture.createPlotScript(); - mixture.print(); + std::cout << mixture.repr(); break; } case InputReader::SimulationType::Fitting: diff --git a/src/makefile b/src/makefile index fd88c47..2d87fb3 100644 --- a/src/makefile +++ b/src/makefile @@ -1,4 +1,4 @@ -CXXFLAGS=-g -O3 -std=c++17 -march=native -ffast-math -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wpedantic -Wconversion -Wsign-conversion -Wnull-dereference -Wdouble-promotion -Wformat=2 -Werror -fomit-frame-pointer -ftree-vectorize -fno-stack-check -funroll-loops +CXXFLAGS=-g -O0 -std=c++17 -march=native -Wall -Wextra -Wshadow -Wnon-virtual-dtor -Wold-style-cast -Wcast-align -Wunused -Woverloaded-virtual -Wpedantic -Wconversion -Wsign-conversion -Wnull-dereference -Wdouble-promotion -Wformat=2 -Werror -fomit-frame-pointer -ftree-vectorize -fno-stack-check -funroll-loops default: ruptura; @@ -36,4 +36,4 @@ ruptura: random_numbers.o special_functions.o isotherm.o multi_site_isotherm.o c $(CXX) main.o fitting.o breakthrough.o inputreader.o mixture_prediction.o component.o multi_site_isotherm.o isotherm.o special_functions.o random_numbers.o -o ruptura clean: - rm -f *.pcm *.o *.a ruptura + rm -f *.pcm *.a ruptura diff --git a/src/mixture_prediction.cpp b/src/mixture_prediction.cpp index fef3a22..ff5ea6a 100644 --- a/src/mixture_prediction.cpp +++ b/src/mixture_prediction.cpp @@ -1,50 +1,43 @@ -#include +#include #include -#include -#include +#include #include +#include +#include #include -#include -#include +#include #if __cplusplus >= 201703L && __has_include() -#include + #include #elif __cplusplus >= 201703L && __has_include() -#include + #include #else -#include + #include #endif #include "mixture_prediction.h" -#ifdef PYBUILD -#include -#include -namespace py = pybind11; -#endif // PYBUILD - -bool LangmuirLoadingSorter(Component const &lhs, Component const &rhs) +bool LangmuirLoadingSorter(Component const& lhs, Component const& rhs) { - if (lhs.isCarrierGas) return false; - if (rhs.isCarrierGas) return true; + if(lhs.isCarrierGas) return false; + if(rhs.isCarrierGas) return true; return lhs.isotherm.sites[0].parameters[0] < rhs.isotherm.sites[0].parameters[0]; } // allow std::pairs to be added -template -std::pair operator+(const std::pair &l, const std::pair &r) -{ - return {l.first + r.first, l.second + r.second}; +template +std::pair operator+(const std::pair & l,const std::pair & r) { + return {l.first+r.first,l.second+r.second}; } template -std::pair &operator+=(std::pair &l, const std::pair &r) -{ - l.first += r.first; - l.second += r.second; - return l; +std::pair &operator+=(std::pair & l, const std::pair & r) { + l.first += r.first; + l.second += r.second; + return l; } MixturePrediction::MixturePrediction(const InputReader &inputreader) - : displayName(inputreader.displayName), + : maxIsothermTerms(inputreader.maxIsothermTerms), + displayName(inputreader.displayName), components(inputreader.components), sortedComponents(components), Ncomp(components.size()), @@ -53,7 +46,6 @@ MixturePrediction::MixturePrediction(const InputReader &inputreader) carrierGasComponent(inputreader.carrierGasComponent), predictionMethod(PredictionMethod(inputreader.mixturePredictionMethod)), iastMethod(IASTMethod(inputreader.IASTMethod)), - maxIsothermTerms(inputreader.maxIsothermTerms), segregatedSortedComponents(maxIsothermTerms, std::vector(components)), alpha1(Ncomp), alpha2(Ncomp), @@ -78,7 +70,7 @@ MixturePrediction::MixturePrediction(std::string _displayName, std::vector::iterator maxIsothermTermsIterator = - std::max_element(_components.begin(), _components.end(), [](Component &lhs, Component &rhs) - { return lhs.isotherm.numberOfSites < rhs.isotherm.numberOfSites; }); + std::vector::iterator maxIsothermTermsIterator = std::max_element( + _components.begin(), _components.end(), + [](Component &lhs, Component &rhs) { return lhs.isotherm.numberOfSites < rhs.isotherm.numberOfSites; }); maxIsothermTerms = maxIsothermTermsIterator->isotherm.numberOfSites; } segregatedSortedComponents = @@ -115,48 +107,52 @@ MixturePrediction::MixturePrediction(std::string _displayName, std::vector MixturePrediction::predictMixture(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, - double *cachedP0, double *cachedPsi) +std::pair MixturePrediction::predictMixture(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { const double tiny = 1.0e-10; - if (P < 0.0) + if(P < 0.0) { printErrorStatus(0.0, 0.0, P, Yi, cachedP0); throw std::runtime_error("Error (IAST): negative total pressure\n"); } double sumYi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumYi += Yi[i]; } - if (std::abs(sumYi - 1.0) > 1e-15) + if(std::abs(sumYi-1.0) > 1e-15) { printErrorStatus(0.0, sumYi, P, Yi, cachedP0); throw std::runtime_error("Error (IAST): sum Yi at IAST start not unity\n"); } + // if only an inert component present // this happens at the beginning of the simulation when the whole column is filled with the carrier gas - if (std::abs(Yi[carrierGasComponent] - 1.0) < tiny) + if(std::abs(Yi[carrierGasComponent] - 1.0) < tiny) { - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - Xi[i] = 0.0; - Ni[i] = 0.0; + Xi[i] = 0.0; + Ni[i] = 0.0; } // do not count it for the IAST statistics return std::make_pair(0, 0); } - switch (predictionMethod) + switch(predictionMethod) { case PredictionMethod::IAST: default: - switch (iastMethod) + switch(iastMethod) { case IASTMethod::FastIAST: default: @@ -165,7 +161,7 @@ std::pair MixturePrediction::predictMixture(const std::vector MixturePrediction::predictMixture(const std::vector MixturePrediction::computeFastIAST(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, - double *cachedP0, double *cachedPsi) +std::pair MixturePrediction::computeFastIAST(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { const double tiny = 1.0e-13; @@ -197,17 +196,17 @@ std::pair MixturePrediction::computeFastIAST(const std::vector 0.0) + if(cachedPsi[0] > 0.0) { - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { pstar[i] = cachedP0[sortedComponents[i].id]; } } - else + else { double initial_psi = 0.0; - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { double temp_psi = Yi[sortedComponents[i].id] * sortedComponents[i].isotherm.psiForPressure(P); initial_psi += temp_psi; @@ -215,7 +214,7 @@ std::pair MixturePrediction::computeFastIAST(const std::vector MixturePrediction::computeFastIAST(const std::vector 0.0) + if(newvalue > 0.0) pstar[i] = newvalue; else { @@ -284,13 +282,13 @@ std::pair MixturePrediction::computeFastIAST(const std::vector MixturePrediction::computeFastIAST(const std::vector(psi.size()); double accum = 0.0; - std::for_each(std::begin(psi), std::end(psi), [&](const double d) { accum += (d - avg) * (d - avg); }); + std::for_each (std::begin(psi), std::end(psi), [&](const double d) { + accum += (d - avg) * (d - avg); + }); - error = std::sqrt(accum / static_cast(psi.size() - 1)); + error = std::sqrt(accum / static_cast(psi.size()-1)); numberOfIASTSteps++; - } while (!(((error < tiny) && (std::fabs(sum_xi - 1.0) < 1e-10)) || (numberOfIASTSteps >= 50))); + } + while(!(((error < tiny) && (std::fabs(sum_xi - 1.0) < 1e-10)) || (numberOfIASTSteps >= 50) )); + - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { cachedP0[sortedComponents[i].id] = pstar[i]; } - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { Xi[sortedComponents[i].id] = Yi[sortedComponents[i].id] * P / std::max(pstar[i], 1e-15); } - if (numberOfCarrierGases > 0) + if(numberOfCarrierGases > 0) { Xi[carrierGasComponent] = 0.0; } double sum = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sum += Xi[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] /= sum; } double inverse_q_total = 0.0; - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { inverse_q_total += Xi[sortedComponents[i].id] / sortedComponents[i].isotherm.value(pstar[i]); } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Ni[i] = Xi[i] / inverse_q_total; } - if (numberOfCarrierGases > 0) + if(numberOfCarrierGases > 0) { Ni[carrierGasComponent] = 0.0; } @@ -350,25 +352,28 @@ std::pair MixturePrediction::computeFastIAST(const std::vector MixturePrediction::computeFastSIAST(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, - double *cachedP0, double *cachedPsi) +std::pair MixturePrediction::computeFastSIAST(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { std::fill(Xi.begin(), Xi.end(), 0.0); std::fill(Ni.begin(), Ni.end(), 0.0); std::pair acc; - for (size_t i = 0; i < maxIsothermTerms; ++i) + for(size_t i = 0; i < maxIsothermTerms; ++i) { acc += computeFastSIAST(i, Yi, P, Xi, Ni, cachedP0, cachedPsi); } double N = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { N += Ni[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] = Ni[i] / N; } @@ -381,10 +386,13 @@ std::pair MixturePrediction::computeFastSIAST(const std::vector< // P = total pressure // Xi = adsorbed phase molefraction // Ni = number of adsorbed molecules of component i -std::pair MixturePrediction::computeFastSIAST(size_t site, const std::vector &Yi, - const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, - double *cachedPsi) +std::pair MixturePrediction::computeFastSIAST(size_t site, + const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { const double tiny = 1.0e-13; @@ -395,17 +403,17 @@ std::pair MixturePrediction::computeFastSIAST(size_t site, const std::fill(delta.begin(), delta.end(), 0.0); std::fill(Phi.begin(), Phi.end(), 0.0); - if (cachedPsi[site] > tiny) + if(cachedPsi[site] > tiny) { - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { pstar[i] = cachedP0[sortedComponents[i].id + site * Ncomp]; } } - else + else { double initial_psi = 0.0; - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { double temp_psi = Yi[sortedComponents[i].id] * sortedComponents[i].isotherm.psiForPressure(site, P); initial_psi += temp_psi; @@ -413,7 +421,7 @@ std::pair MixturePrediction::computeFastSIAST(size_t site, const cachedPsi[site] = initial_psi; double cachevalue = 0.0; - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { pstar[i] = 1.0 / sortedComponents[i].isotherm.inversePressureForPsi(site, initial_psi, cachevalue); } @@ -424,56 +432,55 @@ std::pair MixturePrediction::computeFastSIAST(size_t site, const do { // compute G - for (size_t i = 0; i < Nsorted - 1; ++i) + for(size_t i = 0; i < Nsorted - 1; ++i) { - G[i] = sortedComponents[i].isotherm.psiForPressure(site, pstar[i]) - - sortedComponents[Nsorted - 1].isotherm.psiForPressure(site, pstar[Nsorted - 1]); + G[i] = sortedComponents[i].isotherm.psiForPressure(site, pstar[i]) - + sortedComponents[Nsorted-1].isotherm.psiForPressure(site, pstar[Nsorted-1]); } G[Nsorted - 1] = 0.0; - for (size_t i = 0; i < Nsorted; i++) + for(size_t i = 0; i < Nsorted; i++) { G[Nsorted - 1] += Yi[sortedComponents[i].id] * P / pstar[i]; } G[Nsorted - 1] -= 1.0; // compute Jacobian matrix Phi - for (size_t i = 0; i < Nsorted - 1; i++) + for(size_t i = 0; i < Nsorted - 1; i++) { Phi[i + i * Nsorted] = sortedComponents[i].isotherm.value(site, pstar[i]) / pstar[i]; } - for (size_t i = 0; i < Nsorted - 1; i++) + for(size_t i = 0; i < Nsorted - 1; i++) { - Phi[i + (Nsorted - 1) * Nsorted] = - -sortedComponents[Nsorted - 1].isotherm.value(site, pstar[Nsorted - 1]) / pstar[Nsorted - 1]; + Phi[i + (Nsorted - 1) * Nsorted] = -sortedComponents[Nsorted - 1].isotherm.value(site, pstar[Nsorted - 1]) / pstar[Nsorted - 1]; } - for (size_t i = 0; i < Nsorted; i++) + for(size_t i = 0; i < Nsorted; i++) { - Phi[(Nsorted - 1) + i * Nsorted] = -Yi[sortedComponents[i].id] * P / (pstar[i] * pstar[i]); + Phi[(Nsorted - 1) + i * Nsorted] = -Yi[sortedComponents[i].id] * P / ( pstar[i] * pstar[i] ); } // corrections - for (size_t i = 0; i < Nsorted - 1; i++) + for(size_t i = 0; i < Nsorted - 1; i++) { - Phi[(Nsorted - 1) + (Nsorted - 1) * Nsorted] -= - Phi[(Nsorted - 1) + i * Nsorted] * Phi[i + (Nsorted - 1) * Nsorted] / Phi[i + i * Nsorted]; + Phi[(Nsorted - 1) + (Nsorted - 1) * Nsorted] -= Phi[(Nsorted - 1) + i * Nsorted] * Phi[i + (Nsorted - 1) * Nsorted] / + Phi[i + i * Nsorted]; G[Nsorted - 1] -= Phi[(Nsorted - 1) + i * Nsorted] * G[i] / Phi[i + i * Nsorted]; } // compute delta delta[Nsorted - 1] = G[Nsorted - 1] / Phi[(Nsorted - 1) + (Nsorted - 1) * Nsorted]; - // trick to loop downward from Nsorted - 2 to and including zero (still using size_t as index) - for (size_t i = Nsorted - 1; i-- != 0;) + // trick to loop downward from Nsorted - 2 to and including zero (still using size_t as index) + for (size_t i = Nsorted - 1; i-- != 0; ) { delta[i] = (G[i] - delta[Nsorted - 1] * Phi[i + (Nsorted - 1) * Nsorted]) / Phi[i + i * Nsorted]; } // update pstar - for (size_t i = 0; i < Nsorted; i++) + for(size_t i = 0; i < Nsorted; i++) { double newvalue = pstar[i] - delta[i]; - if (newvalue > 0.0) + if(newvalue > 0.0) pstar[i] = newvalue; else { @@ -482,13 +489,13 @@ std::pair MixturePrediction::computeFastSIAST(size_t site, const } // compute error in psi's - for (size_t i = 0; i < Nsorted; i++) + for(size_t i = 0; i < Nsorted; i++) { psi[i] = sortedComponents[i].isotherm.psiForPressure(site, pstar[i]); } sum_xi = 0.0; - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { sum_xi += Yi[sortedComponents[i].id] * P / std::max(pstar[i], 1e-15); } @@ -496,47 +503,51 @@ std::pair MixturePrediction::computeFastSIAST(size_t site, const double avg = std::accumulate(std::begin(psi), std::end(psi), 0.0) / static_cast(psi.size()); double accum = 0.0; - std::for_each(std::begin(psi), std::end(psi), [&](const double d) { accum += (d - avg) * (d - avg); }); + std::for_each (std::begin(psi), std::end(psi), [&](const double d) { + accum += (d - avg) * (d - avg); + }); - error = std::sqrt(accum / static_cast(psi.size() - 1)); + error = std::sqrt(accum / static_cast(psi.size()-1)); numberOfIASTSteps++; - } while (!(((error < tiny) && (std::fabs(sum_xi - 1.0) < 1e-10)) || (numberOfIASTSteps >= 50))); + } + while(!(((error < tiny) && (std::fabs(sum_xi - 1.0) < 1e-10)) || (numberOfIASTSteps >= 50) )); - for (size_t i = 0; i < Nsorted; ++i) + + for(size_t i = 0; i < Nsorted; ++i) { cachedP0[sortedComponents[i].id + site * Ncomp] = pstar[i]; } - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { Xi[sortedComponents[i].id] = Yi[sortedComponents[i].id] * P / std::max(pstar[i], 1e-15); } - if (numberOfCarrierGases > 0) + if(numberOfCarrierGases > 0) { Xi[carrierGasComponent] = 0.0; } double sum = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sum += Xi[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] /= sum; } double inverse_q_total = 0.0; - for (size_t i = 0; i < Nsorted; ++i) + for(size_t i = 0; i < Nsorted; ++i) { inverse_q_total += Xi[sortedComponents[i].id] / sortedComponents[i].isotherm.value(site, pstar[i]); } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Ni[i] += Xi[i] / inverse_q_total; } - if (numberOfCarrierGases > 0) + if(numberOfCarrierGases > 0) { Ni[carrierGasComponent] = 0.0; } @@ -544,30 +555,33 @@ std::pair MixturePrediction::computeFastSIAST(size_t site, const return std::make_pair(numberOfIASTSteps, 1); } + // Yi = gas phase molefraction // P = total pressure // Xi = adsorbed phase molefraction // Ni = number of adsorbed molecules of component i std::pair MixturePrediction::computeIASTNestedLoopBisection(const std::vector &Yi, - const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, - double *cachedPsi) + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { const double tiny = 1.0e-15; double initial_psi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { initial_psi += Yi[i] * components[i].isotherm.psiForPressure(P); } - if (initial_psi < tiny) + if(initial_psi < tiny) { // nothing is adsorbing - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - Xi[i] = 0.0; - Ni[i] = 0.0; + Xi[i] = 0.0; + Ni[i] = 0.0; } // do not count it for the IAST statistics @@ -578,14 +592,14 @@ std::pair MixturePrediction::computeIASTNestedLoopBisection(cons // condition 2: mol-fractions add up to unity double psi_value = 0.0; - size_t nr_steps = 0; - if (cachedPsi[0] > tiny) + size_t nr_steps=0; + if(cachedPsi[0] > tiny) { initial_psi = cachedPsi[0]; } // for this initial estimate 'initial_psi' compute the sum of mol-fractions double sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(initial_psi, cachedP0[i]); } @@ -593,64 +607,66 @@ std::pair MixturePrediction::computeIASTNestedLoopBisection(cons // initialize the bisection algorithm double left_bracket = initial_psi; double right_bracket = initial_psi; - if (sumXi > 1.0) + if(sumXi > 1.0) { do { right_bracket *= 2.0; sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(right_bracket, cachedP0[i]); } ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; printErrorStatus(0.0, sumXi, P, Yi, cachedP0); throw std::runtime_error("Error (IAST bisection): initial bracketing (for sum > 1) does NOT converge\n"); } - } while (sumXi > 1.0); + } while(sumXi > 1.0); } else { + // Make an initial estimate for the reduced grandpotential when the // sum of the molefractions is larger than 1 - do + do { left_bracket *= 0.5; sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(left_bracket, cachedP0[i]); } ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; printErrorStatus(0.0, sumXi, P, Yi, cachedP0); throw std::runtime_error("Error (IAST bisection): initial bracketing (for sum < 1) does NOT converge\n"); } - } while (sumXi < 1.0); + } + while(sumXi < 1.0); } // bisection algorithm size_t numberOfIASTSteps = 0; - do + do { - psi_value = 0.5 * (left_bracket + right_bracket); + psi_value = 0.5 * (left_bracket + right_bracket); sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(psi_value, cachedP0[i]); } - if (sumXi > 1.0) + if(sumXi > 1.0) { left_bracket = psi_value; } @@ -660,16 +676,17 @@ std::pair MixturePrediction::computeIASTNestedLoopBisection(cons } ++numberOfIASTSteps; - if (numberOfIASTSteps > 100000) + if(numberOfIASTSteps>100000) { throw std::runtime_error("Error (IAST bisection): NO convergence\n"); } - } while (std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); // convergence test - + } + while(std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); // convergence test + psi_value = 0.5 * (left_bracket + right_bracket); sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(psi_value, cachedP0[i]); } @@ -679,12 +696,12 @@ std::pair MixturePrediction::computeIASTNestedLoopBisection(cons // calculate mol-fractions in adsorbed phase and total loading double inverse_q_total = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { double ip = components[i].isotherm.inversePressureForPsi(psi_value, cachedP0[i]); Xi[i] = Yi[i] * P * ip / sumXi; - if (Xi[i] > tiny) + if(Xi[i] > tiny) { inverse_q_total += Xi[i] / components[i].isotherm.value(1.0 / ip); } @@ -697,14 +714,14 @@ std::pair MixturePrediction::computeIASTNestedLoopBisection(cons // calculate loading for all of the components if (inverse_q_total == 0.0) { - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Ni[i] = 0.0; } } else { - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Ni[i] = Xi[i] / inverse_q_total; } @@ -718,25 +735,27 @@ std::pair MixturePrediction::computeIASTNestedLoopBisection(cons // Xi = adsorbed phase molefraction // Ni = number of adsorbed molecules of component i std::pair MixturePrediction::computeSIASTNestedLoopBisection(const std::vector &Yi, - const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, - double *cachedPsi) + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { std::fill(Xi.begin(), Xi.end(), 0.0); std::fill(Ni.begin(), Ni.end(), 0.0); std::pair acc; - for (size_t i = 0; i < maxIsothermTerms; ++i) + for(size_t i = 0; i < maxIsothermTerms; ++i) { acc += computeSIASTNestedLoopBisection(i, Yi, P, Xi, Ni, cachedP0, cachedPsi); } double N = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { N += Ni[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] = Ni[i] / N; } @@ -749,20 +768,23 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(con // P = total pressure // Xi = adsorbed phase molefraction // Ni = number of adsorbed molecules of component i -std::pair MixturePrediction::computeSIASTNestedLoopBisection(size_t site, const std::vector &Yi, - const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, - double *cachedPsi) +std::pair MixturePrediction::computeSIASTNestedLoopBisection(size_t site, + const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi) { const double tiny = 1.0e-15; double initial_psi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { initial_psi += Yi[i] * components[i].isotherm.psiForPressure(site, P); } - if (initial_psi < tiny) + if(initial_psi < tiny) { // nothing is adsorbing // do not count it for the IAST statistics @@ -773,14 +795,14 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(siz // condition 2: mol-fractions add up to unity double psi_value = 0.0; - size_t nr_steps = 0; - if (cachedPsi[site] > tiny) + size_t nr_steps=0; + if(cachedPsi[site] > tiny) { initial_psi = cachedPsi[site]; } // for this initial estimate 'initial_psi' compute the sum of mol-fractions double sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(site, initial_psi, cachedP0[i + Ncomp * site]); } @@ -788,66 +810,66 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(siz // initialize the bisection algorithm double left_bracket = initial_psi; double right_bracket = initial_psi; - if (sumXi > 1.0) + if(sumXi > 1.0) { do { right_bracket *= 2.0; sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - sumXi += - Yi[i] * P * components[i].isotherm.inversePressureForPsi(site, right_bracket, cachedP0[i + Ncomp * site]); + sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(site, right_bracket, cachedP0[i + Ncomp * site]); } ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; printErrorStatus(0.0, sumXi, P, Yi, cachedP0); throw std::runtime_error("Error (IAST bisection): initial bracketing (for sum > 1) does NOT converge\n"); } - } while (sumXi > 1.0); + } while(sumXi > 1.0); } else { + // Make an initial estimate for the reduced grandpotential when the // sum of the molefractions is larger than 1 - do + do { left_bracket *= 0.5; sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - sumXi += - Yi[i] * P * components[i].isotherm.inversePressureForPsi(site, left_bracket, cachedP0[i + Ncomp * site]); + sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(site, left_bracket, cachedP0[i + Ncomp * site]); } ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; printErrorStatus(0.0, sumXi, P, Yi, cachedP0); throw std::runtime_error("Error (IAST bisection): initial bracketing (for sum < 1) does NOT converge\n"); } - } while (sumXi < 1.0); + } + while(sumXi < 1.0); } // bisection algorithm size_t numberOfIASTSteps = 0; - do + do { - psi_value = 0.5 * (left_bracket + right_bracket); + psi_value = 0.5 * (left_bracket + right_bracket); sumXi = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { sumXi += Yi[i] * P * components[i].isotherm.inversePressureForPsi(site, psi_value, cachedP0[i + Ncomp * site]); } - if (sumXi > 1.0) + if(sumXi > 1.0) { left_bracket = psi_value; } @@ -857,12 +879,13 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(siz } ++numberOfIASTSteps; - if (numberOfIASTSteps > 100000) + if(numberOfIASTSteps>100000) { throw std::runtime_error("Error (IAST bisection): NO convergence\n"); } - } while (std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); // convergence test - + } + while(std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); // convergence test + psi_value = 0.5 * (left_bracket + right_bracket); // cache the value of psi for subsequent use @@ -870,12 +893,12 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(siz // calculate mol-fractions in adsorbed phase and total loading double inverse_q_total = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { double ip = components[i].isotherm.inversePressureForPsi(site, psi_value, cachedP0[i + Ncomp * site]); Xi[i] = Yi[i] * P * ip; - if (Xi[i] > tiny) + if(Xi[i] > tiny) { inverse_q_total += Xi[i] / components[i].isotherm.value(site, 1.0 / ip); } @@ -884,7 +907,7 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(siz // calculate loading for all of the components if (inverse_q_total > 0.0) { - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Ni[i] += Xi[i] / inverse_q_total; } @@ -896,39 +919,41 @@ std::pair MixturePrediction::computeSIASTNestedLoopBisection(siz // solve the mixed-langmuir equations derived by Assche et al. // T. R. Van Assche, G.V. Baron, and J. F. Denayer // An explicit multicomponent adsorption isotherm model: -// Accounting for the size-effect for components with Langmuir adsorption behavior. +// Accounting for the size-effect for components with Langmuir adsorption behavior. // Adsorption, 24(6), 517-530 (2018) -// An explicit multicomponent adsorption isotherm model: accounting for the +// An explicit multicomponent adsorption isotherm model: accounting for the // size-effect for components with Langmuir adsorption behavior // In the input file molecules must be added in the following order: -// Largest molecule should be the first component or the component with +// Largest molecule should be the first component or the component with // smallest saturation(Nimax) loading should be the first component // Last component is the carrier gas // At present, only single site isotherms are considered for pure components -std::pair MixturePrediction::computeExplicitIsotherm(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni) +std::pair MixturePrediction::computeExplicitIsotherm(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni) { x[0] = 1.0; - for (size_t i = 1; i < Ncomp; ++i) + for(size_t i = 1; i < Ncomp; ++i) { - x[i] = - sortedComponents[i].isotherm.sites[0].parameters[0] / sortedComponents[i - 1].isotherm.sites[0].parameters[0]; + x[i] = sortedComponents[i].isotherm.sites[0].parameters[0] / + sortedComponents[i - 1].isotherm.sites[0].parameters[0]; } - alpha1[Ncomp - 1] = std::pow( - (1.0 + sortedComponents[Ncomp - 1].isotherm.sites[0].parameters[1] * Yi[sortedComponents[Ncomp - 1].id] * P), - x[Ncomp - 1]); - alpha2[Ncomp - 1] = - 1.0 + sortedComponents[Ncomp - 1].isotherm.sites[0].parameters[1] * Yi[sortedComponents[Ncomp - 1].id] * P; - for (size_t i = Ncomp - 2; i > 0; i--) + alpha1[Ncomp - 1] = std::pow((1.0 + sortedComponents[Ncomp - 1].isotherm.sites[0].parameters[1] * + Yi[sortedComponents[Ncomp - 1].id] * P), x[Ncomp - 1]); + alpha2[Ncomp - 1] = 1.0 + sortedComponents[Ncomp - 1].isotherm.sites[0].parameters[1] * + Yi[sortedComponents[Ncomp - 1].id] * P; + for(size_t i = Ncomp - 2; i > 0; i--) { - alpha1[i] = std::pow( - (alpha1[i + 1] + sortedComponents[i].isotherm.sites[0].parameters[1] * Yi[sortedComponents[i].id] * P), x[i]); - alpha2[i] = alpha1[i + 1] + sortedComponents[i].isotherm.sites[0].parameters[1] * Yi[sortedComponents[i].id] * P; + alpha1[i] = std::pow((alpha1[i + 1] + sortedComponents[i].isotherm.sites[0].parameters[1] * + Yi[sortedComponents[i].id] * P), x[i]); + alpha2[i] = alpha1[i + 1] + sortedComponents[i].isotherm.sites[0].parameters[1] * + Yi[sortedComponents[i].id] * P; } alpha1[0] = alpha1[1] + sortedComponents[0].isotherm.sites[0].parameters[1] * Yi[sortedComponents[0].id] * P; alpha2[0] = alpha1[1] + sortedComponents[0].isotherm.sites[0].parameters[1] * Yi[sortedComponents[0].id] * P; @@ -936,49 +961,50 @@ std::pair MixturePrediction::computeExplicitIsotherm(const std:: double beta = alpha2[0]; alpha_prod[0] = 1.0; - for (size_t i = 1; i < Ncomp; ++i) + for(size_t i = 1; i < Ncomp; ++i) { alpha_prod[i] = (alpha1[i] / alpha2[i]) * alpha_prod[i - 1]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { size_t index = sortedComponents[i].id; - Ni[index] = sortedComponents[i].isotherm.sites[0].parameters[0] * - sortedComponents[i].isotherm.sites[0].parameters[1] * Yi[index] * P * alpha_prod[i] / beta; + Ni[index] = sortedComponents[i].isotherm.sites[0].parameters[0] * sortedComponents[i].isotherm.sites[0].parameters[1] * + Yi[index] * P * alpha_prod[i] / beta; } double N = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { N += Ni[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] = Ni[i] / N; } - return std::make_pair(1, 1); + return std::make_pair(1,1); } std::pair MixturePrediction::computeSegratedExplicitIsotherm(const std::vector &Yi, - const double &P, std::vector &Xi, - std::vector &Ni) + const double &P, + std::vector &Xi, + std::vector &Ni) { std::fill(Xi.begin(), Xi.end(), 0.0); std::fill(Ni.begin(), Ni.end(), 0.0); std::pair acc; - for (size_t i = 0; i < maxIsothermTerms; ++i) + for(size_t i = 0; i < maxIsothermTerms; ++i) { acc += computeSegratedExplicitIsotherm(i, Yi, P, Xi, Ni); } double N = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { N += Ni[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] = Ni[i] / N; } @@ -986,65 +1012,63 @@ std::pair MixturePrediction::computeSegratedExplicitIsotherm(con return acc; } -std::pair MixturePrediction::computeSegratedExplicitIsotherm(size_t site, const std::vector &Yi, - const double &P, std::vector &Xi, - std::vector &Ni) +std::pair MixturePrediction::computeSegratedExplicitIsotherm(size_t site, + const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni) { x[0] = 1.0; - for (size_t i = 1; i < Ncomp; ++i) + for(size_t i = 1; i < Ncomp; ++i) { - x[i] = segregatedSortedComponents[site][i].isotherm.sites[0].parameters[0] / + x[i] = segregatedSortedComponents[site][i].isotherm.sites[0].parameters[0] / segregatedSortedComponents[site][i - 1].isotherm.sites[0].parameters[0]; } - alpha1[Ncomp - 1] = std::pow((1.0 + segregatedSortedComponents[site][Ncomp - 1].isotherm.sites[0].parameters[1] * - Yi[segregatedSortedComponents[site][Ncomp - 1].id] * P), - x[Ncomp - 1]); - alpha2[Ncomp - 1] = 1.0 + segregatedSortedComponents[site][Ncomp - 1].isotherm.sites[0].parameters[1] * - Yi[segregatedSortedComponents[site][Ncomp - 1].id] * P; - for (size_t i = Ncomp - 2; i > 0; i--) + alpha1[Ncomp - 1] = std::pow((1.0 + segregatedSortedComponents[site][Ncomp - 1].isotherm.sites[0].parameters[1] * + Yi[segregatedSortedComponents[site][Ncomp - 1].id] * P), x[Ncomp - 1]); + alpha2[Ncomp - 1] = 1.0 + segregatedSortedComponents[site][Ncomp - 1].isotherm.sites[0].parameters[1] * + Yi[segregatedSortedComponents[site][Ncomp - 1].id] * P; + for(size_t i = Ncomp - 2; i > 0; i--) { - alpha1[i] = std::pow((alpha1[i + 1] + segregatedSortedComponents[site][i].isotherm.sites[0].parameters[1] * - Yi[segregatedSortedComponents[site][i].id] * P), - x[i]); - alpha2[i] = alpha1[i + 1] + segregatedSortedComponents[site][i].isotherm.sites[0].parameters[1] * - Yi[segregatedSortedComponents[site][i].id] * P; + alpha1[i] = std::pow((alpha1[i + 1] + segregatedSortedComponents[site][i].isotherm.sites[0].parameters[1] * + Yi[segregatedSortedComponents[site][i].id] * P), x[i]); + alpha2[i] = alpha1[i + 1] + segregatedSortedComponents[site][i].isotherm.sites[0].parameters[1] * + Yi[segregatedSortedComponents[site][i].id] * P; } - alpha1[0] = alpha1[1] + segregatedSortedComponents[site][0].isotherm.sites[0].parameters[1] * - Yi[segregatedSortedComponents[site][0].id] * P; - alpha2[0] = alpha1[1] + segregatedSortedComponents[site][0].isotherm.sites[0].parameters[1] * - Yi[segregatedSortedComponents[site][0].id] * P; + alpha1[0] = alpha1[1] + segregatedSortedComponents[site][0].isotherm.sites[0].parameters[1] * + Yi[segregatedSortedComponents[site][0].id] * P; + alpha2[0] = alpha1[1] + segregatedSortedComponents[site][0].isotherm.sites[0].parameters[1] * + Yi[segregatedSortedComponents[site][0].id] * P; double beta = alpha2[0]; alpha_prod[0] = 1.0; - for (size_t i = 1; i < Ncomp; ++i) + for(size_t i = 1; i < Ncomp; ++i) { alpha_prod[i] = (alpha1[i] / alpha2[i]) * alpha_prod[i - 1]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { size_t index = segregatedSortedComponents[site][i].id; - Ni[index] += segregatedSortedComponents[site][i].isotherm.sites[0].parameters[0] * - segregatedSortedComponents[site][i].isotherm.sites[0].parameters[1] * Yi[index] * P * alpha_prod[i] / - beta; + Ni[index] += segregatedSortedComponents[site][i].isotherm.sites[0].parameters[0] * + segregatedSortedComponents[site][i].isotherm.sites[0].parameters[1] * + Yi[index] * P * alpha_prod[i] / beta; } double N = 0.0; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { N += Ni[i]; } - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { Xi[i] = Ni[i] / N; } - return std::make_pair(1, 1); + return std::make_pair(1,1); } -void MixturePrediction::print() const { std::cout << repr(); } - std::string MixturePrediction::repr() const { std::string s; @@ -1108,85 +1132,6 @@ void MixturePrediction::run() } } -#ifdef PYBUILD -py::array_t MixturePrediction::compute() -{ - // based on the run() method, but returns array. - std::vector Yi(Ncomp); - std::vector Xi(Ncomp); - std::vector Ni(Ncomp); - std::vector cachedP0(Ncomp * maxIsothermTerms); - std::vector cachedPsi(maxIsothermTerms); - - for (size_t i = 0; i < Ncomp; ++i) - { - Yi[i] = components[i].Yi0; - } - - std::vector pressures = initPressures(); - - std::array shape{{numberOfPressurePoints, Ncomp, 6}}; - py::array_t mixPred(shape); - double *data = mixPred.mutable_data(); - - for (size_t i = 0; i < numberOfPressurePoints; ++i) - { - // check for error from python side (keyboard interrupt) - if (PyErr_CheckSignals() != 0) - { - throw py::error_already_set(); - } - - predictMixture(Yi, pressures[i], Xi, Ni, &cachedP0[0], &cachedPsi[0]); - for (size_t j = 0; j < Ncomp; j++) - { - double p_star = Yi[j] * pressures[i] / Xi[j]; - size_t k = (i * Ncomp + j) * 6; - data[k] = pressures[i]; - data[k + 1] = components[j].isotherm.value(pressures[i]); - data[k + 2] = Ni[j]; - data[k + 3] = Yi[j]; - data[k + 4] = Xi[j]; - data[k + 5] = components[j].isotherm.psiForPressure(p_star); - } - } - return mixPred; -} - -void MixturePrediction::setPressure(double _pressureStart, double _pressureEnd) -{ - pressureStart = _pressureStart; - pressureEnd = _pressureEnd; -} - -void MixturePrediction::setComponentsParameters(std::vector molfracs, std::vector params) -{ - size_t index = 0; - for (size_t i = 0; i < Ncomp; ++i) - { - components[i].Yi0 = molfracs[i]; - size_t n_params = components[i].isotherm.numberOfParameters; - std::vector slicedVec(params.begin() + index, params.begin() + index + n_params); - index = index + n_params; - components[i].isotherm.setParameters(slicedVec); - } - sortedComponents = components; - std::vector> segregatedSortedComponents(maxIsothermTerms, std::vector(components)); - sortComponents(); -} - -std::vector MixturePrediction::getComponentsParameters() -{ - std::vector params; - for (size_t i = 0; i < Ncomp; ++i) - { - std::vector compParams = components[i].isotherm.getParameters(); - params.insert(params.end(), compParams.begin(), compParams.end()); - } - return params; -} -#endif // PYBUILD - std::vector MixturePrediction::initPressures() { std::vector pressures(numberOfPressurePoints); @@ -1227,14 +1172,14 @@ void MixturePrediction::createPureComponentsPlotScript() stream << "set xlabel 'Total bulk fluid phase fugacity, {/Helvetica-Italic f} / Pa' font \"Helvetica,18\"\n"; stream << "set ylabel 'Absolute loading, {/Helvetica-Italic q}_i' offset 0.0,0 font \"Helvetica,18\"\n"; stream << "set bmargin 4\n"; - if (pressureScale == PressureScale::Log) + if(pressureScale == PressureScale::Log) { stream << "set key top left width 2 samplen 2.5 height 0.5 spacing 1.5 font \"Helvetica, 10\" maxcolumns 2\n"; stream << "set log x\n"; stream << "set format x \"10^{%T}\"\n"; stream << "set xrange[" << pressureStart << ":]\n"; } - else + else { stream << "set key outside right width 2 samplen 2.5 height 0.5 spacing 1.5 font \"Helvetica, 10\" maxcolumns 2\n"; } @@ -1261,11 +1206,8 @@ void MixturePrediction::createPureComponentsPlotScript() for (size_t i = 0; i < Ncomp; i++) { std::string fileName = "component_" + std::to_string(i) + "_" + components[i].name + ".data"; - stream << " " - << "\"" << fileName << "\"" - << " us ($1):($2)" - << " title \"" << components[i].name << "\"" - << " with po" << (i < Ncomp - 1 ? ",\\" : "") << "\n"; + stream << " " << "\"" << fileName << "\"" << " us ($1):($2)" << " title \"" + << components[i].name << "\"" << " with po" << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } } @@ -1277,14 +1219,14 @@ void MixturePrediction::createMixturePlotScript() stream << "set xlabel 'Total bulk fluid phase fugacity, {/Helvetica-Italic f} / Pa' font \"Helvetica,18\"\n"; stream << "set ylabel 'Absolute loading, {/Helvetica-Italic q}_i' offset 0.0,0 font \"Helvetica,18\"\n"; stream << "set bmargin 4\n"; - if (pressureScale == PressureScale::Log) + if(pressureScale == PressureScale::Log) { stream << "set key top left samplen 2.5 height 0.5 spacing 1.5 font \"Helvetica, 10\" maxcolumns 2\n"; stream << "set log x\n"; stream << "set format x \"10^{%T}\"\n"; stream << "set xrange[" << pressureStart << ":]\n"; } - else + else { stream << "set key outside right samplen 2.5 height 0.5 spacing 1.5 font \"Helvetica, 10\" maxcolumns 2\n"; } @@ -1311,10 +1253,8 @@ void MixturePrediction::createMixturePlotScript() for (size_t i = 0; i < Ncomp; i++) { std::string fileName = "component_" + std::to_string(i) + "_" + components[i].name + ".data"; - stream << " " - << "\"" << fileName << "\"" - << " us ($1):($3)" - << " title \"" << components[i].name << " (y_i=" << components[i].Yi0 << ")\"" + stream << " " << "\"" << fileName << "\"" << " us ($1):($3)" << " title \"" + << components[i].name << " (y_i=" << components[i].Yi0 << ")\"" << " with po" << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } } @@ -1327,14 +1267,14 @@ void MixturePrediction::createMixtureAdsorbedMolFractionPlotScript() stream << "set xlabel 'Total bulk fluid phase fugacity, {/Helvetica-Italic f} / Pa' font \"Helvetica,18\"\n"; stream << "set ylabel 'Adsorbed mol-fraction, {/Helvetica-Italic Y}_i / [-]' offset 0.0,0 font \"Helvetica,18\"\n"; stream << "set bmargin 4\n"; - if (pressureScale == PressureScale::Log) + if(pressureScale == PressureScale::Log) { stream << "set key outside right samplen 2.5 height 0.5 spacing 1.5 font \"Helvetica, 10\" maxcolumns 2\n"; stream << "set log x\n"; stream << "set format x \"10^{%T}\"\n"; stream << "set xrange[" << pressureStart << ":]\n"; } - else + else { stream << "set key outside right samplen 2.5 height 0.5 spacing 1.5 font \"Helvetica, 10\" maxcolumns 2\n"; } @@ -1361,55 +1301,54 @@ void MixturePrediction::createMixtureAdsorbedMolFractionPlotScript() for (size_t i = 0; i < Ncomp; i++) { std::string fileName = "component_" + std::to_string(i) + "_" + components[i].name + ".data"; - stream << " " - << "\"" << fileName << "\"" - << " us ($1):($5)" - << " title \"" << components[i].name << " (y_i=" << components[i].Yi0 << ")\"" + stream << " " << "\"" << fileName << "\"" << " us ($1):($5)" << " title \"" + << components[i].name << " (y_i=" << components[i].Yi0 << ")\"" << " with po" << (i < Ncomp - 1 ? ",\\" : "") << "\n"; } } void MixturePrediction::createPlotScript() { -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) - std::ofstream stream_graphs("make_graphs.bat"); - stream_graphs << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program " - "Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; - stream_graphs << "gnuplot.exe plot_pure_components\n"; - stream_graphs << "gnuplot.exe plot_mixture\n"; - stream_graphs << "gnuplot.exe plot_mixture_mol_fractions\n"; -#else - std::ofstream stream_graphs("make_graphs"); - stream_graphs << "#!/bin/sh\n"; - stream_graphs << "cd -- \"$(dirname \"$0\")\"\n"; - stream_graphs << "gnuplot plot_pure_components\n"; - stream_graphs << "gnuplot plot_mixture\n"; - stream_graphs << "gnuplot plot_mixture_mol_fractions\n"; -#endif + #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) || defined(__NT__) + std::ofstream stream_graphs("make_graphs.bat"); + stream_graphs << "set PATH=%PATH%;C:\\Program Files\\gnuplot\\bin;C:\\Program Files\\ffmpeg-master-latest-win64-gpl\\bin;C:\\Program Files\\ffmpeg\\bin\n"; + stream_graphs << "gnuplot.exe plot_pure_components\n"; + stream_graphs << "gnuplot.exe plot_mixture\n"; + stream_graphs << "gnuplot.exe plot_mixture_mol_fractions\n"; + #else + std::ofstream stream_graphs("make_graphs"); + stream_graphs << "#!/bin/sh\n"; + stream_graphs << "export LC_ALL='en_US.UTF-8'\n"; + stream_graphs << "cd -- \"$(dirname \"$0\")\"\n"; + stream_graphs << "gnuplot plot_pure_components\n"; + stream_graphs << "gnuplot plot_mixture\n"; + stream_graphs << "gnuplot plot_mixture_mol_fractions\n"; + #endif + + #if (__cplusplus >= 201703L) + std::filesystem::path path{"make_graphs"}; + std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); + #else + chmod("make_graphs", S_IRWXU); + #endif -#if (__cplusplus >= 201703L) - std::filesystem::path path{"make_graphs"}; - std::filesystem::permissions(path, std::filesystem::perms::owner_exec, std::filesystem::perm_options::add); -#else - chmod("make_graphs", S_IRWXU); -#endif } -void MixturePrediction::printErrorStatus(double psi_value, double sum, double P, const std::vector Yi, - double cachedP0[]) +void MixturePrediction::printErrorStatus(double psi_value, double sum, double P, const std::vector Yi, double cachedP0[]) { std::cout << "psi: " << psi_value << std::endl; std::cout << "sum: " << sum << std::endl; - for (size_t i = 0; i < Ncomp; ++i) std::cout << "cachedP0: " << cachedP0[i] << std::endl; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) + std::cout << "cachedP0: " << cachedP0[i] << std::endl; + for(size_t i = 0; i < Ncomp; ++i) { double value = components[i].isotherm.inversePressureForPsi(psi_value, cachedP0[i]); std::cout << "inversePressure: " << value << std::endl; } std::cout << "P: " << P << std::endl; - for (size_t i = 0; i < Ncomp; ++i) + for(size_t i = 0; i < Ncomp; ++i) { - std::cout << "Yi[i] " << i << " " << Yi[i] << std::endl; + std::cout << "Yi[i] "<< i << " " << Yi[i] << std::endl; } } @@ -1442,4 +1381,51 @@ void MixturePrediction::sortComponents() auto it = sortedComponents.begin() + static_cast(carrierGasComponent); std::rotate(it, it + 1, sortedComponents.end()); } -} \ No newline at end of file +} + +#ifdef PYBUILD +py::array_t MixturePrediction::compute() +{ + // based on the run() method, but returns array. + std::vector Yi(Ncomp); + std::vector Xi(Ncomp); + std::vector Ni(Ncomp); + std::vector cachedP0(Ncomp * maxIsothermTerms); + std::vector cachedPsi(maxIsothermTerms); + + for (size_t i = 0; i < Ncomp; ++i) + { + Yi[i] = components[i].Yi0; + } + + std::vector pressures = initPressures(); + + std::array shape{{numberOfPressurePoints, Ncomp, 6}}; + py::array_t mixPred(shape); + double *data = mixPred.mutable_data(); + + for (size_t i = 0; i < numberOfPressurePoints; ++i) + { + // check for error from python side (keyboard interrupt) + if (PyErr_CheckSignals() != 0) + { + throw py::error_already_set(); + } + + predictMixture(Yi, pressures[i], Xi, Ni, &cachedP0[0], &cachedPsi[0]); + for (size_t j = 0; j < Ncomp; j++) + { + double p_star = Yi[j] * pressures[i] / Xi[j]; + size_t k = (i * Ncomp + j) * 6; + data[k] = pressures[i]; + data[k + 1] = components[j].isotherm.value(pressures[i]); + data[k + 2] = Ni[j]; + data[k + 3] = Yi[j]; + data[k + 4] = Xi[j]; + data[k + 5] = components[j].isotherm.psiForPressure(p_star); + } + } + return mixPred; +} + +#endif // PYBUILD diff --git a/src/mixture_prediction.h b/src/mixture_prediction.h index 9ff2feb..d066f1c 100644 --- a/src/mixture_prediction.h +++ b/src/mixture_prediction.h @@ -1,388 +1,153 @@ #pragma once -#include #include +#include + +#include "inputreader.h" +#include "component.h" #ifdef PYBUILD #include #include namespace py = pybind11; -#endif // PYBUILD +#endif // P -#include "component.h" -#include "inputreader.h" - -/** - * \brief Class for predicting mixture adsorption isotherms. - * - * The MixturePrediction class provides methods to predict mixture adsorption isotherms using various methods like IAST, - * SIAST, and explicit isotherm models. It handles the computation of adsorbed phase mole fractions and loadings based - * on gas phase compositions and pressure. - */ class MixturePrediction { - public: - /** - * \brief Enum class for prediction methods. - * - * Specifies the method used for predicting mixture adsorption isotherms. - */ - enum class PredictionMethod - { - IAST = 0, ///< Ideal Adsorbed Solution Theory - SIAST = 1, ///< Segregated Ideal Adsorbed Solution Theory - EI = 2, ///< Explicit Isotherm - SEI = 3 ///< Segregated Explicit Isotherm - }; - - /** - * \brief Enum class for IAST methods. - * - * Specifies the method used for solving IAST equations. - */ - enum class IASTMethod - { - FastIAST = 0, ///< Fast IAST algorithm - NestedLoopBisection = 1 ///< Nested Loop Bisection method - }; - - /** - * \brief Constructs a MixturePrediction object from an InputReader. - * - * Initializes the MixturePrediction instance using the parameters provided by the InputReader. - * - * \param inputreader The InputReader containing the simulation parameters. - */ - MixturePrediction(const InputReader &inputreader); - - /** - * \brief Constructs a MixturePrediction object with specified parameters. - * - * Initializes the MixturePrediction instance using the provided parameters. - * - * \param _displayName The display name for the simulation. - * \param _components A vector of Component objects representing the mixture components. - * \param _numberOfCarrierGases The number of carrier gases in the mixture. - * \param _carrierGasComponent The index of the carrier gas component. - * \param _temperature The temperature of the system. - * \param _pressureStart The starting pressure for the simulation. - * \param _pressureEnd The ending pressure for the simulation. - * \param _numberOfPressurePoints The number of pressure points in the simulation. - * \param _pressureScale The pressure scale (0 for Log, 1 for Normal). - * \param _predictionMethod The prediction method to use. - * \param _iastMethod The IAST method to use. - */ - MixturePrediction(std::string _displayName, std::vector _components, size_t _numberOfCarrierGases, - size_t _carrierGasComponent, double _temperature, double _pressureStart, double _pressureEnd, - size_t _numberOfPressurePoints, size_t _pressureScale, size_t _predictionMethod, - size_t _iastMethod); - - /** - * \brief Prints the mixture prediction data. - * - * Outputs the mixture prediction data to the standard output. - */ - void print() const; - - /** - * \brief Returns a string representation of the MixturePrediction object. - * - * \return A string representing the MixturePrediction object. - */ - std::string repr() const; - - /** - * \brief Runs the mixture prediction simulation. - * - * Performs the mixture prediction calculations and writes the results to output files. - */ - void run(); - - /** - * \brief Creates a Gnuplot script for pure component isotherms. - * - * Generates a Gnuplot script to plot pure component isotherms. - */ - void createPureComponentsPlotScript(); - - /** - * \brief Creates a Gnuplot script for mixture prediction. - * - * Generates a Gnuplot script to plot mixture prediction results. - */ - void createMixturePlotScript(); - - /** - * \brief Creates a Gnuplot script for mixture adsorbed mol fractions. - * - * Generates a Gnuplot script to plot adsorbed mol fractions in the mixture. - */ - void createMixtureAdsorbedMolFractionPlotScript(); + public: + enum class PredictionMethod + { + IAST = 0, + SIAST = 1, + EI = 2, + SEI = 3 + }; + + enum class IASTMethod + { + FastIAST = 0, + NestedLoopBisection = 1 + }; + + MixturePrediction(const InputReader &inputreader); + MixturePrediction(std::string _displayName, std::vector _components, size_t _numberOfCarrierGases, + size_t _carrierGasComponent, double _temperature, double _pressureStart, double _pressureEnd, + size_t _numberOfPressurePoints, size_t _pressureScale, size_t _predictionMethod, + size_t _iastMethod); + + std::string repr() const; + void sortComponents(); + void run(); + std::vector initPressures(); + void createPureComponentsPlotScript(); + void createMixturePlotScript(); + void createMixtureAdsorbedMolFractionPlotScript(); + void createPlotScript(); + + // Yi = gas phase mol-fraction + // P = total pressure + // Xi = adsorbed phase mol-fraction + // Ni = number of adsorbed molecules of component i + std::pair predictMixture(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); - /** - * \brief Creates plot scripts for the mixture prediction. - * - * Generates the necessary Gnuplot scripts to plot the simulation results. - */ - void createPlotScript(); + // keep this non private for breakthrough + size_t maxIsothermTerms; #ifdef PYBUILD - /** - * \brief Computes the mixture prediction. - * - * Performs the mixture prediction calculations and returns the results as a NumPy array. - * - * \return A NumPy array containing the mixture prediction results. - */ - py::array_t compute(); - - /** - * \brief Sets the pressure range for the simulation. - * - * Updates the starting and ending pressures for the simulation. - * - * \param _pressureStart The new starting pressure. - * \param _pressureEnd The new ending pressure. - */ - void setPressure(double _pressureStart, double _pressureEnd); - - /** - * \brief Sets the components' parameters. - * - * Updates the molar fractions and isotherm parameters of the components. - * - * \param molfracs A vector of molar fractions for each component. - * \param params A vector of isotherm parameters for the components. - */ - void setComponentsParameters(std::vector molfracs, std::vector params); - - /** - * \brief Gets the components' parameters. - * - * Retrieves the isotherm parameters of the components. - * - * \return A vector containing the isotherm parameters of the components. - */ - std::vector getComponentsParameters(); + py::array_t compute(); #endif // PYBUILD - /** - * \brief Gets the maximum number of isotherm terms. - * - * \return The maximum number of isotherm terms. - */ - size_t getMaxIsothermTerms() const { return maxIsothermTerms; } - - /** - * \brief Predicts the mixture adsorption isotherm. - * - * Computes the adsorbed phase mole fractions and loadings based on the gas phase compositions and pressure. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair predictMixture(const std::vector &Yi, const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, double *cachedPsi); - - private: - std::string displayName; ///< The display name for the simulation. - std::vector components; ///< The vector of components in the mixture. - std::vector sortedComponents; ///< Components sorted according to specific criteria. - const size_t Ncomp; ///< The total number of components. - const size_t Nsorted; ///< The number of sorted components. - size_t numberOfCarrierGases; ///< The number of carrier gases in the mixture. - size_t carrierGasComponent; ///< The index of the carrier gas component. - PredictionMethod predictionMethod; ///< The method used for predicting mixture adsorption isotherms. - IASTMethod iastMethod; ///< The method used for solving IAST equations. - size_t maxIsothermTerms; ///< The maximum number of isotherm terms. - std::vector> - segregatedSortedComponents; ///< Segregated and sorted components for SIAST/SEI methods. - - std::vector alpha1; ///< Intermediate calculation vector for explicit isotherms. - std::vector alpha2; ///< Intermediate calculation vector for explicit isotherms. - std::vector alpha_prod; ///< Product of alpha values for explicit isotherms. - std::vector x; ///< Intermediate calculation vector. - - std::vector pstar; ///< Hypothetical pressures in IAST calculations. - std::vector psi; ///< Reduced grand potential values. - std::vector G; ///< Intermediate calculation vector in IAST. - std::vector delta; ///< Correction vector in IAST. - std::vector Phi; ///< Jacobian matrix in IAST calculations. - - /** - * \brief Enum class for pressure scales. - * - * Specifies the scale to use for pressure in the simulation. - */ - enum class PressureScale - { - Log = 0, ///< Logarithmic pressure scale - Normal = 1 ///< Linear pressure scale - }; - double temperature{300.0}; ///< The temperature of the system. - double pressureStart{1e3}; ///< The starting pressure for the simulation. - double pressureEnd{1e8}; ///< The ending pressure for the simulation. - size_t numberOfPressurePoints{100}; ///< The number of pressure points in the simulation. - PressureScale pressureScale{PressureScale::Log}; ///< The pressure scale to use. - - /** - * \brief Initializes the pressure points for the simulation. - * - * Generates a vector of pressure points based on the starting and ending pressures and the pressure scale. - * - * \return A vector containing the pressure points for the simulation. - */ - std::vector initPressures(); - - /** - * \brief Sorts the components based on specific criteria. - * - * Sorts the components to optimize calculations in prediction methods. - */ - void sortComponents(); - - /** - * \brief Computes mixture prediction using Fast IAST method. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair computeFastIAST(const std::vector &Yi, const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, double *cachedPsi); - - /** - * \brief Computes mixture prediction using Fast SIAST method. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair computeFastSIAST(const std::vector &Yi, const double &P, std::vector &Xi, - std::vector &Ni, double *cachedP0, double *cachedPsi); - - /** - * \brief Computes mixture prediction for a specific term using Fast SIAST method. - * - * \param term The index of the isotherm term. - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair computeFastSIAST(size_t term, const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, double *cachedP0, - double *cachedPsi); - - /** - * \brief Computes mixture prediction using IAST with nested loop bisection method. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair computeIASTNestedLoopBisection(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, - double *cachedP0, double *cachedPsi); - - /** - * \brief Computes mixture prediction using SIAST with nested loop bisection method. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair computeSIASTNestedLoopBisection(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, - double *cachedP0, double *cachedPsi); - - /** - * \brief Computes mixture prediction for a specific term using SIAST with nested loop bisection method. - * - * \param term The index of the isotherm term. - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \param cachedP0 An array to cache intermediate pressure calculations. - * \param cachedPsi An array to cache intermediate psi calculations. - * \return A pair containing the number of IAST steps and a status code. - */ - std::pair computeSIASTNestedLoopBisection(size_t term, const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni, - double *cachedP0, double *cachedPsi); - - /** - * \brief Computes mixture prediction using explicit isotherm model. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \return A pair containing the number of steps and a status code. - */ - std::pair computeExplicitIsotherm(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni); - - /** - * \brief Computes mixture prediction using segregated explicit isotherm model. - * - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \return A pair containing the number of steps and a status code. - */ - std::pair computeSegratedExplicitIsotherm(const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni); - - /** - * \brief Computes mixture prediction for a specific term using segregated explicit isotherm model. - * - * \param site The index of the isotherm site. - * \param Yi The gas phase mole fractions. - * \param P The total pressure. - * \param Xi The adsorbed phase mole fractions (output). - * \param Ni The number of adsorbed molecules of each component (output). - * \return A pair containing the number of steps and a status code. - */ - std::pair computeSegratedExplicitIsotherm(size_t site, const std::vector &Yi, const double &P, - std::vector &Xi, std::vector &Ni); - - /** - * \brief Prints error status for debugging purposes. - * - * Outputs the current state of variables when an error occurs in IAST calculations. - * - * \param psi The current psi value. - * \param sum The current sum of mole fractions. - * \param P The total pressure. - * \param Yi The gas phase mole fractions. - * \param cachedP0 An array of cached pressure values. - */ - void printErrorStatus(double psi, double sum, double P, const std::vector Yi, double cachedP0[]); + private: + std::string displayName; + const std::vector components; + std::vector sortedComponents; + const size_t Ncomp; + const size_t Nsorted; + size_t numberOfCarrierGases; + size_t carrierGasComponent; + PredictionMethod predictionMethod; + IASTMethod iastMethod; + std::vector> segregatedSortedComponents; + + std::vector alpha1; + std::vector alpha2; + std::vector alpha_prod; + std::vector x; + + std::vector pstar; + std::vector psi; + std::vector G; + std::vector delta; + std::vector Phi; + + enum class PressureScale + { + Log = 0, + Normal = 1 + }; + double temperature{ 300.0 }; + double pressureStart{ 1e3 }; + double pressureEnd{ 1e8 }; + size_t numberOfPressurePoints{ 100 }; + PressureScale pressureScale{ PressureScale::Log }; + + std::pair computeFastIAST(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); + std::pair computeFastSIAST(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); + std::pair computeFastSIAST(size_t term, + const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); + + std::pair computeIASTNestedLoopBisection(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); + std::pair computeSIASTNestedLoopBisection(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); + std::pair computeSIASTNestedLoopBisection(size_t term, + const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni, + double *cachedP0, + double *cachedPsi); + std::pair computeExplicitIsotherm(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni); + std::pair computeSegratedExplicitIsotherm(const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni); + std::pair computeSegratedExplicitIsotherm(size_t site, const std::vector &Yi, + const double &P, + std::vector &Xi, + std::vector &Ni); + + void printErrorStatus(double psi, double sum, double P, const std::vector Yi, double cachedP0[]); }; + diff --git a/src/multi_site_isotherm.cpp b/src/multi_site_isotherm.cpp index 5f6cc52..eda2033 100644 --- a/src/multi_site_isotherm.cpp +++ b/src/multi_site_isotherm.cpp @@ -1,11 +1,8 @@ #include "multi_site_isotherm.h" - -#include -#include - #include "special_functions.h" -void MultiSiteIsotherm::print() const { std::cout << repr(); } +#include +#include std::string MultiSiteIsotherm::repr() const { @@ -24,30 +21,16 @@ void MultiSiteIsotherm::add(const Isotherm &isotherm) sites.push_back(isotherm); numberOfParameters += isotherm.numberOfParameters; - for (size_t i = 0; i < isotherm.numberOfParameters; ++i) + for(size_t i = 0; i < isotherm.numberOfParameters; ++i) { parameterIndices.emplace_back(sites.size() - 1, i); } } -void MultiSiteIsotherm::setParameters(std::vector params) +void MultiSiteIsotherm::setParameters(size_t i, double value) { - for (size_t i = 0; i < params.size(); ++i) - { - std::pair index = parameterIndices[i]; - sites[index.first].parameters[index.second] = params[i]; - } -} - -std::vector MultiSiteIsotherm::getParameters() -{ - std::vector params; - for (size_t i = 0; i < numberOfParameters; ++i) - { - std::pair index = parameterIndices[i]; - params.push_back(sites[index.first].parameters[index.second]); - } - return params; + std::pair index = parameterIndices[i]; + sites[index.first].parameters[index.second] = value; } // returns the inverse-pressure (1/P) that corresponds to the given reduced_grand_potential psi @@ -60,18 +43,18 @@ double MultiSiteIsotherm::inversePressureForPsi(double reduced_grand_potential, double right_bracket; // For a single Langmuir or Langmuir-Freundlich site, the inverse can be handled analytically - if (numberOfSites == 1) + if(numberOfSites == 1) { return sites[0].inversePressureForPsi(reduced_grand_potential, cachedP0); } // from here on, work with pressure, and return 1.0 / pressure at the end of the routine double p_start; - if (cachedP0 <= 0.0) + if(cachedP0 <= 0.0) { p_start = 5.0; } - else + else { // use the last value of Pi0 p_start = cachedP0; @@ -84,16 +67,16 @@ double MultiSiteIsotherm::inversePressureForPsi(double reduced_grand_potential, left_bracket = p_start; right_bracket = p_start; - if (s < reduced_grand_potential) + if(s < reduced_grand_potential) { // find the bracket on the right - do + do { right_bracket *= 2.0; s = psiForPressure(right_bracket); ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "reduced_grand_potential: " << reduced_grand_potential << std::endl; std::cout << "psi: " << s << std::endl; @@ -102,18 +85,19 @@ double MultiSiteIsotherm::inversePressureForPsi(double reduced_grand_potential, std::cout << "Right bracket: " << right_bracket << std::endl; throw std::runtime_error("Error (Inverse bisection): initial bracketing (for sum < 1) does NOT converge\n"); } - } while (s < reduced_grand_potential); + } + while(s < reduced_grand_potential); } else { // find the bracket on the left - do + do { left_bracket *= 0.5; s = psiForPressure(left_bracket); ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "reduced_grand_potential: " << reduced_grand_potential << std::endl; std::cout << "psi: " << s << std::endl; @@ -122,7 +106,8 @@ double MultiSiteIsotherm::inversePressureForPsi(double reduced_grand_potential, std::cout << "Right bracket: " << right_bracket << std::endl; throw std::runtime_error("Error (Inverse bisection): initial bracketing (for sum > 1) does NOT converge\n"); } - } while (s > reduced_grand_potential); + } + while(s > reduced_grand_potential); } do @@ -130,19 +115,20 @@ double MultiSiteIsotherm::inversePressureForPsi(double reduced_grand_potential, double middle = 0.5 * (left_bracket + right_bracket); s = psiForPressure(middle); - if (s > reduced_grand_potential) - right_bracket = middle; + if(s > reduced_grand_potential) + right_bracket = middle; else - left_bracket = middle; + left_bracket = middle; ++nr_steps; - if (nr_steps > 100000) + if(nr_steps>100000) { std::cout << "Left bracket: " << left_bracket << std::endl; std::cout << "Right bracket: " << right_bracket << std::endl; throw std::runtime_error("Error (Inverse bisection): initial bracketing (for sum < 1) does NOT converge\n"); } - } while (std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); + } + while(std::abs(left_bracket - right_bracket) / std::abs(left_bracket + right_bracket) > tiny); double middle = 0.5 * (left_bracket + right_bracket); @@ -155,21 +141,21 @@ double MultiSiteIsotherm::inversePressureForPsi(double reduced_grand_potential, double MultiSiteIsotherm::fitness() const { const double penaltyCost = 50.0; - for (size_t i = 0; i < numberOfSites; ++i) + for(size_t i = 0; i < numberOfSites; ++i) { - if (sites[i].isUnphysical()) return penaltyCost; + if(sites[i].isUnphysical()) return penaltyCost; } return 0.0; } -std::string MultiSiteIsotherm::gnuplotFunctionString([[maybe_unused]] char s) const +std::string MultiSiteIsotherm::gnuplotFunctionString([[maybe_unused]]char s) const { std::ostringstream stream; - for (size_t i = 0; i < numberOfSites; ++i) + for(size_t i = 0; i < numberOfSites; ++i) { // +1 because gnuplot start counting from 1 stream << sites[i].gnuplotFunctionString(s, siteParameterIndex[i] + 1); - if (i < numberOfSites - 1) + if(i #include +#include #include #include -#include +#include #include +#include -#include "hash_combine.h" #include "isotherm.h" +#include "hash_combine.h" -/** - * \brief Represents a collection of adsorption isotherm sites. - * - * The MultiSiteIsotherm struct encapsulates multiple individual isotherm sites, - * allowing the modeling of complex adsorption behavior as a sum of multiple isotherm contributions. - * It manages a collection of Isotherm objects and provides methods to evaluate the combined adsorption values, - * inverse computations, and parameter handling. This struct facilitates operations such as parameter randomization, - * fitness evaluation, and generating representations for plotting. - */ struct MultiSiteIsotherm { - size_t numberOfSites{0}; ///< The number of isotherm sites included in the model. - std::vector sites{}; ///< A vector containing the individual isotherm site objects. - - size_t numberOfParameters{0}; ///< The total number of parameters across all isotherm sites. - std::vector> - parameterIndices{}; ///< Mapping from parameter index to site and parameter within site. - std::vector siteParameterIndex{}; ///< Indices indicating the starting parameter index for each site. - - /** - * \brief Accesses the parameter at the specified index. - * - * Provides a reference to the parameter value corresponding to the given global parameter index, allowing for - * modification. - * - * \param i The global parameter index. - * \return Reference to the parameter value. - */ - double ¶meters(size_t i) - { + size_t numberOfSites{ 0 }; + std::vector sites{}; + + size_t numberOfParameters { 0 }; + std::vector> parameterIndices{}; + std::vector siteParameterIndex{}; + double& parameters(size_t i) + { std::pair index = parameterIndices[i]; return sites[index.first].parameters[index.second]; } - - /** - * \brief Accesses the parameter at the specified index (const version). - * - * Provides a const reference to the parameter value corresponding to the given global parameter index. - * - * \param i The global parameter index. - * \return Const reference to the parameter value. - */ - const double ¶meters(size_t i) const - { + const double& parameters(size_t i) const + { std::pair index = parameterIndices[i]; return sites[index.first].parameters[index.second]; } + void setParameters(size_t i, double value); - /** - * \brief Adds an isotherm site to the collection. - * - * Incorporates a new Isotherm object into the MultiSiteIsotherm, updating the parameter indices and counts - * accordingly. - * - * \param isotherm The Isotherm object to be added. - */ void add(const Isotherm &isotherm); - /** - * \brief Prints the string representation of the MultiSiteIsotherm. - * - * Outputs the result of repr() to the standard output. - */ - void print() const; - - /** - * \brief Generates a string representation of the MultiSiteIsotherm. - * - * Creates a string that includes the number of sites and the representations of each site. - * - * \return A string representing the MultiSiteIsotherm. - */ std::string repr() const; - /** - * \brief Generates a randomized version of the MultiSiteIsotherm. - * - * Creates a copy of the current MultiSiteIsotherm with randomized parameters for each site, - * within the specified maximum loading. - * - * \param maximumLoading The maximum loading value used for randomization. - * \return A randomized MultiSiteIsotherm object. - */ MultiSiteIsotherm randomized(double maximumLoading) { MultiSiteIsotherm copy(*this); - for (size_t i = 0; i < numberOfSites; ++i) + for(size_t i = 0; i < numberOfSites; ++i) { copy.sites[i].randomize(maximumLoading); } return copy; } - /** - * \brief Sets the parameters of all isotherm sites. - * - * Updates the parameters of the isotherm sites using the provided vector of parameter values. - * - * \param params A vector containing the new parameter values. - */ - void setParameters(std::vector params); - - /** - * \brief Retrieves all parameters of the isotherm sites. - * - * Collects the parameters from all isotherm sites into a single vector. - * - * \return A vector containing all parameter values. - */ - std::vector getParameters(); - - /** - * \brief Computes the total adsorption value at a given pressure. - * - * Sums the adsorption values from all isotherm sites at the specified pressure. - * - * \param pressure The pressure at which to evaluate the adsorption. - * \return The total adsorption value. - */ inline double value(double pressure) const { double sum = 0.0; - for (size_t i = 0; i < numberOfSites; ++i) + for(size_t i = 0; i < numberOfSites; ++i) { sum += sites[i].value(pressure); } return sum; } - /** - * \brief Computes the adsorption value for a specific site at a given pressure. - * - * Evaluates the adsorption value from a specified isotherm site at the given pressure. - * - * \param site The index of the isotherm site. - * \param pressure The pressure at which to evaluate the adsorption. - * \return The adsorption value for the specified site, or 0.0 if the site index is invalid. - */ inline double value(size_t site, double pressure) const { - if (site < numberOfSites) + if(site < numberOfSites) { return sites[site].value(pressure); } return 0.0; } - /** - * \brief Computes the reduced grand potential at a given pressure. - * - * Sums the reduced grand potential contributions from all isotherm sites at the specified pressure. - * - * \param pressure The pressure at which to compute the reduced grand potential. - * \return The total reduced grand potential. - */ + // computed reduced grand potential for pressure inline double psiForPressure(double pressure) const { double sum = 0.0; - for (size_t i = 0; i < numberOfSites; ++i) + for(size_t i = 0; i < numberOfSites; ++i) { sum += sites[i].psiForPressure(pressure); } return sum; } - /** - * \brief Computes the reduced grand potential for a specific site at a given pressure. - * - * Evaluates the reduced grand potential from a specified isotherm site at the given pressure. - * - * \param site The index of the isotherm site. - * \param pressure The pressure at which to compute the reduced grand potential. - * \return The reduced grand potential for the specified site, or 0.0 if the site index is invalid. - */ + // computed reduced grand potential for pressure inline double psiForPressure(size_t site, double pressure) const { - if (site < numberOfSites) + if(site < numberOfSites) { return sites[site].psiForPressure(pressure); } return 0.0; } - /** - * \brief Computes the inverse pressure corresponding to a given reduced grand potential. - * - * Calculates the inverse pressure (1/P) that corresponds to the specified total reduced grand potential - * using a bisection algorithm. - * - * \param reduced_grand_potential The target reduced grand potential. - * \param cachedP0 A reference to a cached pressure value for starting point optimization. - * \return The inverse of the pressure corresponding to the reduced grand potential. - */ double inversePressureForPsi(double reduced_grand_potential, double &cachedP0) const; - /** - * \brief Computes the inverse pressure for a specific site corresponding to a given reduced grand potential. - * - * Calculates the inverse pressure (1/P) for a specified isotherm site that corresponds to the given reduced grand - * potential. - * - * \param site The index of the isotherm site. - * \param reduced_grand_potential The target reduced grand potential. - * \param cachedP0 A reference to a cached pressure value for starting point optimization. - * \return The inverse of the pressure for the specified site, or 0.0 if the site index is invalid. - */ double inversePressureForPsi(size_t site, double reduced_grand_potential, double &cachedP0) const { - if (site < numberOfSites) + if(site < numberOfSites) { return sites[site].inversePressureForPsi(reduced_grand_potential, cachedP0); } return 0.0; } - /** - * \brief Evaluates the fitness of the MultiSiteIsotherm. - * - * Calculates a fitness value used in optimization, applying a penalty if any of the isotherm sites are unphysical. - * - * \return The fitness value, where higher values indicate worse fitness. - */ double fitness() const; - - /** - * \brief Generates a gnuplot function string for plotting. - * - * Creates a string that represents the MultiSiteIsotherm as a gnuplot function, suitable for plotting purposes. - * - * \param s A character representing the parameter variable in the function string. - * \return A string representing the MultiSiteIsotherm in gnuplot syntax. - */ std::string gnuplotFunctionString(char s) const; }; namespace std { -template <> -struct hash -{ - size_t operator()(const MultiSiteIsotherm &k) const + template <> struct hash { - std::size_t h = 0; - for (const Isotherm &isotherm : k.sites) + size_t operator()(const MultiSiteIsotherm& k) const { - for (size_t i = 0; i < isotherm.numberOfParameters; ++i) + std::size_t h=0; + for(const Isotherm &isotherm: k.sites) { - hash_combine(h, isotherm.parameters[i]); + for(size_t i = 0; i < isotherm.numberOfParameters; ++i) + { + hash_combine(h, isotherm.parameters[i]); + } } + return h; } - return h; - } -}; -} // namespace std + }; +} diff --git a/src/random_numbers.h b/src/random_numbers.h index 64ee85b..85ae2d1 100644 --- a/src/random_numbers.h +++ b/src/random_numbers.h @@ -4,38 +4,45 @@ class RandomNumber { - public: - static double Uniform() { return getInstance().uniformDistribution_(getInstance().mt); } - static double Gaussian() { return getInstance().normalDistribution_(getInstance().mt); } - static size_t Integer(size_t i, size_t j) - { - return i + static_cast(static_cast(j + 1 - i) * Uniform()); - } - static uint64_t UInt64() { return getInstance().uniformUInt64Distribution_(getInstance().mt); } +public: + static double Uniform() + { + return getInstance().uniformDistribution_(getInstance().mt); + } + static double Gaussian() + { + return getInstance().normalDistribution_(getInstance().mt); + } + static size_t Integer(size_t i, size_t j) + { + return i + static_cast(static_cast(j + 1 - i) * Uniform()); + } + static uint64_t UInt64() + { + return getInstance().uniformUInt64Distribution_(getInstance().mt); + } +private: + RandomNumber() + { + std::random_device rd; + mt = std::mt19937_64(rd()); + //mt = std::mt19937_64(10); + uniformDistribution_ = std::uniform_real_distribution(0.0, 1.0); + uniformUInt64Distribution_ = std::uniform_int_distribution(); + normalDistribution_ = std::normal_distribution(); + } + ~RandomNumber() {} + + static RandomNumber& getInstance() { + static RandomNumber s; + return s; + } - private: - RandomNumber() - { - std::random_device rd; - mt = std::mt19937_64(rd()); - // mt = std::mt19937_64(10); - uniformDistribution_ = std::uniform_real_distribution(0.0, 1.0); - uniformUInt64Distribution_ = std::uniform_int_distribution(); - normalDistribution_ = std::normal_distribution(); - } - ~RandomNumber() {} + RandomNumber(RandomNumber const&) = delete; + RandomNumber& operator= (RandomNumber const&) = delete; - static RandomNumber& getInstance() - { - static RandomNumber s; - return s; - } - - RandomNumber(RandomNumber const&) = delete; - RandomNumber& operator=(RandomNumber const&) = delete; - - std::mt19937_64 mt; - std::uniform_real_distribution uniformDistribution_; - std::normal_distribution normalDistribution_; - std::uniform_int_distribution uniformUInt64Distribution_; + std::mt19937_64 mt; + std::uniform_real_distribution uniformDistribution_; + std::normal_distribution normalDistribution_; + std::uniform_int_distribution uniformUInt64Distribution_; }; diff --git a/src/simulation.input b/src/simulation.input new file mode 100644 index 0000000..471d100 --- /dev/null +++ b/src/simulation.input @@ -0,0 +1,40 @@ +SimulationType Breakthrough + +// Column settings +DisplayName CALF-20 +Temperature 298.15 // [K] Feed Gas Temperature, scaling variable for Temperature +ColumnVoidFraction 0.4 // [-] +ParticleDensity 520 // [kg/m^3] +TotalPressure 10e05 // [Pa] Total pressure, scaling variable for Pressure +PressureGradient 0 // [Pa/m] +ColumnEntranceVelocity 0.50 // [m/s] Interstitial velocity, scaling variable for velocity and time +ColumnLength 0.50 // [m] Total pressure, scaling variable for step length and time + +// Run settings +NumberOfTimeSteps 1000000 +PrintEvery 100 +WriteEvery 100 +TimeStep 1e-8 +NumberOfGridPoints 20 +MixturePredictionMethod IAST // Not used + +Component 0 MoleculeName Helium + GasPhaseMolFraction 0.90 // [-] + CarrierGas yes + +Component 1 MoleculeName CO2 + GasPhaseMolFraction 0.05 // [-] + MassTransferCoefficient 0.20 // [1/s] + AxialDispersionCoefficient 0.0 // [m^2/s] + NumberOfIsothermSites 2 + Langmuir 0 0 // Please specify in the breakthrough cpp file: initialize function + Langmuir 0 0 // Please specify in the breakthrough cpp file: initialize function + +Component 2 MoleculeName N2 + GasPhaseMolFraction 0.05 // [-] + MassTransferCoefficient 0.20 // [1/s] + AxialDispersionCoefficient 0.0 // [m^2/s] + NumberOfIsothermSites 1 // Please specify in the breakthrough cpp file: initialize function + Langmuir 0 0 // Please specify in the breakthrough cpp file: initialize function + +// Please specify all the other neccessary parameters and properties in the breakthrough cpp file: initialize function. diff --git a/src/special_functions.cpp b/src/special_functions.cpp index d119e05..e60fe41 100644 --- a/src/special_functions.cpp +++ b/src/special_functions.cpp @@ -1,12 +1,11 @@ #include "special_functions.h" +#include #include - +#include +#include #include #include -#include -#include -#include // routine by Alexander Voigt // https://arxiv.org/abs/2201.01678 @@ -14,131 +13,139 @@ // https://arxiv.org/src/2201.01678v1/anc/Li2.c double li2(double x) { - const double PI = 3.1415926535897932; - const double P[] = {0.9999999999999999502e+0, -2.6883926818565423430e+0, 2.6477222699473109692e+0, - -1.1538559607887416355e+0, 2.0886077795020607837e-1, -1.0859777134152463084e-2}; - const double Q[] = {1.0000000000000000000e+0, -2.9383926818565635485e+0, 3.2712093293018635389e+0, - -1.7076702173954289421e+0, 4.1596017228400603836e-1, -3.9801343754084482956e-2, - 8.2743668974466659035e-4}; - - double y = 0.0, r = 0.0, s = 1.0; - - if (x < -1.0) - { - const double l = std::log(1.0 - x); - y = 1.0 / (1.0 - x); - r = -PI * PI / 6.0 + l * (0.5 * l - std::log(-x)); - s = 1; - } - else if (x == -1.0) - { - return -PI * PI / 12.0; - } - else if (x < 0.0) - { - const double l = std::log1p(-x); - y = x / (x - 1); - r = -0.5 * l * l; - s = -1; - } - else if (x == 0.0) - { - return 0; - } - else if (x < 0.5) - { - y = x; - r = 0.0; - s = 1.0; - } - else if (x < 1.0) - { - y = 1.0 - x; - r = PI * PI / 6.0 - std::log(x) * std::log(y); - s = -1.0; - } - else if (x == 1.0) - { - return PI * PI / 6.0; - } - else if (x < 2.0) - { - const double l = std::log(x); - y = 1.0 - 1.0 / x; - r = PI * PI / 6.0 - l * (std::log(y) + 0.5 * l); - s = 1.0; - } - else - { - const double l = std::log(x); - y = 1.0 / x; - r = PI * PI / 3.0 - 0.5 * l * l; - s = -1.0; - } - - const double y2 = y * y; - const double y4 = y2 * y2; - const double p = P[0] + y * P[1] + y2 * (P[2] + y * P[3]) + y4 * (P[4] + y * P[5]); - const double q = Q[0] + y * Q[1] + y2 * (Q[2] + y * Q[3]) + y4 * (Q[4] + y * Q[5] + y2 * Q[6]); - - return r + s * y * p / q; + const double PI = 3.1415926535897932; + const double P[] = { + 0.9999999999999999502e+0, + -2.6883926818565423430e+0, + 2.6477222699473109692e+0, + -1.1538559607887416355e+0, + 2.0886077795020607837e-1, + -1.0859777134152463084e-2 + }; + const double Q[] = { + 1.0000000000000000000e+0, + -2.9383926818565635485e+0, + 3.2712093293018635389e+0, + -1.7076702173954289421e+0, + 4.1596017228400603836e-1, + -3.9801343754084482956e-2, + 8.2743668974466659035e-4 + }; + + double y = 0.0, r = 0.0, s = 1.0; + + if (x < -1.0) + { + const double l = std::log(1.0 - x); + y = 1.0/(1.0 - x); + r = -PI * PI / 6.0 + l * (0.5 * l - std::log(-x)); + s = 1; + } + else if (x == -1.0) + { + return -PI*PI/12.0; + } + else if (x < 0.0) + { + const double l = std::log1p(-x); + y = x/(x - 1); + r = -0.5*l*l; + s = -1; + } + else if (x == 0.0) + { + return 0; + } + else if (x < 0.5) + { + y = x; + r = 0.0; + s = 1.0; + } + else if (x < 1.0) + { + y = 1.0 - x; + r = PI*PI/6.0 - std::log(x)*std::log(y); + s = -1.0; + } else if (x == 1.0) { + return PI*PI/6.0; + } else if (x < 2.0) { + const double l = std::log(x); + y = 1.0 - 1.0/x; + r = PI*PI/6.0 - l*(std::log(y) + 0.5*l); + s = 1.0; + } else { + const double l = std::log(x); + y = 1.0/x; + r = PI*PI/3.0 - 0.5*l*l; + s = -1.0; + } + + const double y2 = y*y; + const double y4 = y2*y2; + const double p = P[0] + y*P[1] + y2*(P[2] + y*P[3]) + y4*(P[4] + y*P[5]); + const double q = Q[0] + y*Q[1] + y2*(Q[2] + y*Q[3]) + y4*(Q[4] + y*Q[5] + y2*Q[6]); + + return r + s*y*p/q; } // Note convergence restrictions: abs(x) < 1 and c not a negative integer or zero -double hypergeometric(double a, double b, double c, double x) +double hypergeometric( double a, double b, double c, double x ) { - const double TOLERANCE = 1.0e-3; - double term = a * b * x / c; - double value = 1.0 + term; - int n = 1; - - while (std::abs(term) > TOLERANCE) - { - a++, b++, c++, n++; - term *= a * b * x / c / n; - value += term; - } - - return value; + const double TOLERANCE = 1.0e-3; + double term = a * b * x / c; + double value = 1.0 + term; + int n = 1; + + while ( std::abs( term ) > TOLERANCE ) + { + a++, b++, c++, n++; + term *= a * b * x / c / n; + value += term; + } + + return value; } -int areClose(const double x1, const double x2, const double epsilon) +int areClose (const double x1, const double x2, const double epsilon) { int exponent; double delta, difference, maxXY; /* Find exponent of largest absolute value */ - maxXY = (std::abs(x1) > std::abs(x2)) ? x1 : x2; - std::frexp(maxXY, &exponent); + maxXY = (std::abs (x1) > std::abs (x2)) ? x1 : x2; + std::frexp (maxXY, &exponent); /* Form a neighborhood of size 2 * delta */ delta = std::ldexp(epsilon, exponent); difference = x1 - x2; - if (difference > delta) /* x1 > x2 */ - return 0; + if (difference > delta) /* x1 > x2 */ + return 0; else if (difference < -delta) /* x1 < x2 */ - return 0; - else /* -delta <= difference <= delta */ - return 1; /* x1 ~=~ x2 */ + return 0; + else /* -delta <= difference <= delta */ + return 1; /* x1 ~=~ x2 */ } -int areEqual(double a, double b) +int areEqual (double a, double b) { - if (a == b) return 1; + if ( a == b) return 1; - if (areClose(a, b, DBL_EPSILON)) return 1; + if ( areClose(a, b, DBL_EPSILON)) + return 1; - return 0; + return 0; } -double maxNORM(double z) // = maximum norm +double maxNORM(double z) // = maximum norm { - return std::fabs(z); + return std::fabs(z); } + // abs(z) <= 1, actually only used for abs(z) < 1/2 -double series_2F1(double a, double b, double c, double z) +double series_2F1 (double a, double b, double c, double z) { int n; int nMax; @@ -149,8 +156,10 @@ double series_2F1(double a, double b, double c, double z) nMax = 2000; - if (areEqual(a, 0.0) == 1 || areEqual(b, 0.0) == 1) return (1.0); - if (areEqual(z, 0.0) == 1) return (1.0); + if (areEqual(a, 0.0) == 1 || areEqual(b, 0.0) == 1) + return(1.0); + if (areEqual(z, 0.0) == 1) + return(1.0); n = 0; s = 1.0; @@ -161,7 +170,7 @@ double series_2F1(double a, double b, double c, double z) r = (a * b / c / static_cast(n) * z); t = (t * r); sNew = (t + s); - if (areEqual(sNew, s) == 1 && 3 < n && maxNORM(t) < DBL_EPSILON) + if ( areEqual(sNew,s) == 1 && 3 < n && maxNORM(t) < DBL_EPSILON) break; else s = (sNew); @@ -171,7 +180,7 @@ double series_2F1(double a, double b, double c, double z) c = (c + 1.0); } - return (s); + return(s); } // Gosper's method @@ -180,7 +189,7 @@ double series_2F1(double a, double b, double c, double z) double hypergeometric2F1(double a, double b, double c, double z) { int k; - // double K; + //double K; int kMax; double d_k; double e_k; @@ -196,7 +205,7 @@ double hypergeometric2F1(double a, double b, double c, double z) if (std::abs(z) <= 0.5) { result = series_2F1(a, b, c, z); - return (result); + return(result); } kMax = 2000; @@ -207,20 +216,22 @@ double hypergeometric2F1(double a, double b, double c, double z) f_k = 0.0; for (k = 0; k <= kMax; k++) { - lambda = (static_cast(k) + a) * (static_cast(k) + b) / (static_cast(k) + 1.0) / - (2.0 * static_cast(k) + c) / (2.0 * static_cast(k) + c + 1.0); + lambda = (static_cast(k) + a) * (static_cast(k) + b) / + (static_cast(k) + 1.0) / (2.0 * static_cast(k) + c) / + (2.0 * static_cast(k) + c + 1.0); d_k1 = (lambda * z * (xi * d_k * (mu + static_cast(k)) + e_k)); e_k1 = (lambda * z * (-a * b * xi * d_k + (static_cast(k) + c) * e_k)); - t = d_k * (-static_cast(k) / (-1.0 + z) - (b * a - (mu + static_cast(k)) * static_cast(k)) / - (2.0 * static_cast(k) + c) * xi) + - e_k; + t = d_k * (-static_cast(k) / (-1.0 + z) - (b * a - (mu + + static_cast(k)) * static_cast(k)) / (2.0 * static_cast(k) + c) * xi) + e_k; f_k1 = (f_k + t); - if (areEqual(f_k1, f_k) == 1 && maxNORM(t) <= DBL_EPSILON && 3 < k) break; + if (areEqual(f_k1, f_k) == 1 && maxNORM(t) <= DBL_EPSILON && 3 < k) + break; d_k = (d_k1); e_k = (e_k1); f_k = (f_k1); } result = f_k; - return (result); + return(result); } + diff --git a/src/special_functions.h b/src/special_functions.h index c7145c2..9a8faab 100644 --- a/src/special_functions.h +++ b/src/special_functions.h @@ -1,9 +1,9 @@ #pragma once -#include -#include #include #include +#include +#include extern std::string floatToBitString(float v); extern std::string doubleToBitString(double v); @@ -11,10 +11,10 @@ extern float bitStringToFloat(std::string s); extern double bitStringToDouble(std::string s); extern double li2(double x); extern double hypergeometric2F1(double a, double b, double c, double z); -extern double hypergeometric(double a, double b, double c, double x); +extern double hypergeometric( double a, double b, double c, double x ); template -std::vector sort_indexes(const std::vector &v) +std::vector sort_indexes(const std::vector &v) { // initialize original index locations std::vector idx(v.size()); @@ -24,7 +24,8 @@ std::vector sort_indexes(const std::vector &v) // using std::stable_sort instead of std::sort // to avoid unnecessary index re-orderings // when v contains elements of equal values - stable_sort(idx.begin(), idx.end(), [&v](size_t i1, size_t i2) { return v[i1] < v[i2]; }); + stable_sort(idx.begin(), idx.end(), + [&v](size_t i1, size_t i2) {return v[i1] < v[i2];}); return idx; } From 83c9b2a358a5d6b239ec7f76e04d3a91b68873b3 Mon Sep 17 00:00:00 2001 From: Hassan Date: Fri, 10 Jan 2025 20:03:40 +0900 Subject: [PATCH 2/3] Added WENO, UDS and TVD functions --- src/breakthrough.cpp | 520 ++++++++++++++++++++++++++----------------- src/breakthrough.h | 33 ++- 2 files changed, 345 insertions(+), 208 deletions(-) diff --git a/src/breakthrough.cpp b/src/breakthrough.cpp index 15e8229..39e3a9d 100644 --- a/src/breakthrough.cpp +++ b/src/breakthrough.cpp @@ -159,6 +159,16 @@ Breakthrough::Breakthrough(std::string _displayName, std::vector _com void Breakthrough::initialize() { + // Hassan: Initializing vectors to store dummy values and node wall values + P_dum = std::vector (Ngrid+1, 0.0); + y_dum = std::vector ((Ngrid+1)*Ncomp, 0.0); + T_dum = std::vector (Ngrid+1, 0.0); + + // The size is Ngrid+1 because these vectors store the values of outlet (right wall of Ngrid node as well) + Ph = std::vector (Ngrid+1, 0.0); + yh = std::vector ((Ngrid+1)*Ncomp, 0.0); + Th = std::vector (Ngrid+1, 0.0); + // Hassan Properties and Parameters K_z = 0.09; C_ps = 750.0; @@ -194,29 +204,49 @@ void Breakthrough::initialize() std::fill(T.begin(), T.end(), T_gas/T_gas); // Equal to T_gas std::fill(y.begin(), y.end(), 0.0); + // set the molefraction of the carrier gas equal to 1.0, as column is initially filled with carrier gas only. + // for the column except for the entrance (i=0) + for(size_t i = 1; i < Ngrid + 1; ++i) + { + y[i * Ncomp + carrierGasComponent] = 1.0; + } + // initial pressure along the column std::vector pt_init(Ngrid + 1); // set the initial total pressure along the column assuming the pressure gradient is constant - for(size_t i = 0; i < Ngrid + 1; ++i) + // for(size_t i = 0; i < Ngrid + 1; ++i) + // { + // pt_init[i] = (p_total + dptdx * static_cast(i) * dx*L) / p_total; + // pt_init[i] = (p_total - dptdx * static_cast(Ngrid-i) * dx*L) / p_total; + // } + + // Pressure Profile initialization based on Ergun equation + double sum_y; + double vis_term = 150.0 * mu * std::pow((1-epsilon), 2) + / 4.0 / std::pow(r_p, 2) / std::pow(epsilon, 2); + + pt_init[Ngrid] = p_total/p_total; + for(size_t i = Ngrid-1; i >= 1; --i) { - pt_init[i] = (p_total + dptdx * static_cast(i) * dx*L) / p_total; + sum_y = 0.0; + for(size_t j = 0; j < Ncomp; ++j) + { + sum_y += y[i * Ncomp + j] * MW[j]; + } + pt_init[i] = ((vis_term*v_in*dx*L/p_total) + pt_init[i+1]) + / (1.0 - (dx*L/R/T[i]/T_gas)*sum_y*((1.75*(1-epsilon))/2.0/r_p/epsilon)*std::pow(v_in, 2.0)); } + // For 1st node half cell approximation + pt_init[0] = ((vis_term*v_in*dx/2.0*L/p_total) + pt_init[1]) + / (1.0 - (dx/2.0*L/R/T[0]/T_gas)*sum_y*((1.75*(1-epsilon))/2.0/r_p/epsilon)*std::pow(v_in, 2.0)); // initialize the interstitial gas velocity in the column for(size_t i = 0; i < Ngrid + 1; ++i) { // V[i] = v_in * p_total / pt_init[i]; // V[i] = (v_in * 1 / pt_init[i]) / v_in; - V[i] = 0.0; - } - V[0] = v_in/v_in; - - // set the molefraction of the carrier gas equal to 1.0, as column is initially filled with carrier gas only. - // for the column except for the entrance (i=0) - for(size_t i = 1; i < Ngrid + 1; ++i) - { - y[i * Ncomp + carrierGasComponent] = pt_init[i]/pt_init[i]; + V[i] = v_in/v_in; } // at the column entrance, the mol-fractions of the components in the gas phase are fixed @@ -262,11 +292,6 @@ void Breakthrough::initialize() for(size_t i = 0; i < Ngrid + 1; ++i) { - // Pt[i] = 0.0; - // for(size_t j = 0; j < Ncomp; ++j) - // { - // Pt[i] += std::max(0.0, P[i * Ncomp + j]); - // } // Initial pressure profile is same as pt_init P[i] += std::max(pt_init[i], 0.0); } @@ -401,7 +426,7 @@ void Breakthrough::computeStep(size_t step) // ====================================================================== // calculate the derivatives Dq/dt and Dp/dt based on Qeq, Q, V, and P - computeFirstDerivatives(Dqdt, DPdt, DTdt, Dydt, Qeq, Q, V, P, T, y); + computeFirstDerivatives(Dqdt, DPdt, DTdt, Dydt, Qeq, Q, P, T, y); // Dqdt and Dpdt are calculated at old time step // make estimate for the new loadings and new gas phase partial pressures @@ -431,20 +456,20 @@ void Breakthrough::computeStep(size_t step) } computeEquilibriumLoadings(); - for (size_t i; i &dqdt, std::vector &dydt, const std::vector &q_eq, const std::vector &q, - const std::vector &v, const std::vector &p, - // const std::vector &T_arg, - // const std::vector &y_arg) - const std::vector &Temp, + const std::vector &T_vec, const std::vector &y_vec) { double idx = 1.0 / dx; double idx2 = 1.0 / (dx * dx); - - // The following variables have already been initialized in the initialize function - // double K_z = 0.09; // Thermal conductivity of gas [J/mol/K] - // double C_ps = 750.0; // Heat capacity of adsorbent [J/kg/K] - // double C_pg = 35.8; // Heat capacity of gas [J/mol/K] - // double C_pa = 35.8; // Heat capacity of adsorbate [J/mol/K] - // double mu = 1.13e-05; // Viscoisty of gas [Pa.s] - // double r_p = 5.0e-03; // Radius of adsorbent particles [m] // %%%%%%%%%%%%%%%%%%%% Variables required for balances equations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% std::vector ro_g(Ngrid+1); std::vector sink_term(Ngrid+1); - std::vector kinetic_term(Ngrid+1); - std::vector p_dum(Ngrid+1); - // std::vector Temp(Ngrid+1); - // std::vector y_vec(Ngrid+1); + double vis_term; + std::vector kinetic_term_h(Ngrid+1); // Variables for finite differences std::vector dTdx(Ngrid+1, 0.0); @@ -658,102 +671,180 @@ void Breakthrough::computeFirstDerivatives(std::vector &dqdt, std::vector PvT(Ngrid+1, 0.0); std::vector Pv(Ngrid+1, 0.0); std::vector dPdx(Ngrid+1, 0.0); + std::vector dPdxh(Ngrid+1, 0.0); + double dyPVT; + + // Velocity vector storing values as calculated using Ergun equation + std:: vector v(Ngrid+1, 0.0); + // Variables for balance equations double phi = R*rho_p*Q_s0*T_gas*(1-epsilon)/epsilon/p_total; double dTdt1, dTdt2, dTdt3; double dPdt1, dPdt2, dPdt3; double dydt1, dydt2, dydt3; + double sum_q, sum_y; + double res_dqdt; + int v_sign; - std::copy(p.begin(), p.end(), p_dum.begin()); - // std::copy(y_arg.begin(), y_arg.end(), y_vec.begin()); - // std::copy(T_arg.begin(), T_arg.end(), Temp.begin()); + //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Process Variables Retrieval %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + std::copy(p.begin(), p.end(), P_dum.begin()); + std::copy(y_vec.begin(), y_vec.end(), y_dum.begin()); + std::copy(T_vec.begin(), T_vec.end(), T_dum.begin()); + + //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Calculation of variable properties %%%%%%%%%%%%%%%%%%%%%% + vis_term = 150.0 * mu * std::pow((1-epsilon), 2) + / 4.0 / std::pow(r_p, 2) / std::pow(epsilon, 2); - // Calculation of variable properties - double sum_q; for(size_t i = 0; i < Ngrid+1; i++) { sum_q = 0.0; for(size_t j = 0; j < Ncomp; ++j) { - sum_q += q[i * Ncomp + j]; // Sum of adsorbent loading + sum_q += q[i * Ncomp + j]; // Sum of adsorbent loading } - ro_g[i] = (p_dum[i]*p_total) / R / Temp[i] / T_gas; + ro_g[i] = (P_dum[i]*p_total) / R / T_dum[i] / T_gas; sink_term[i] = (1 - epsilon) * (rho_p*C_ps + rho_p*sum_q*C_pa) + (epsilon*ro_g[i]*C_pg); } // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Inlet Boundary Pressure correction %%%%%%%%%%%%%% + sum_y = 0.0; + for(size_t j = 0; j < Ncomp; ++j) + { + sum_y += (MW[j] * y_dum[0 * Ncomp + j]); // sum of molar masses at the inlet node + } - double vis_term = 150.0 * mu * std::pow((1-epsilon), 2) - / 4.0 / std::pow(r_p, 2) / std::pow(epsilon, 2); + // Inlet pressure is calculated using inlet velocity and 2nd grid point pressure based on Ergun's equation + // Half cell approximation will be used to get the pressure at the inlet node (x=0) + P_dum[0] = ((vis_term*v_in*dx/2.0*L/p_total) + P_dum[1]) + / (1.0 - (dx/2.0*L/R/T_dum[0]/T_gas)*sum_y*((1.75*(1-epsilon))/2.0/r_p/epsilon)*std::pow(v_in, 2.0)); - double sum = 0.0; - for(size_t j = 0; j < Ncomp; ++j) + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WENO Values Calculation %%%%%%%%%%%%%%%%%%%%%%%% + // Ph = compute_WENO(P_dum, true); + // Th = compute_WENO(T_dum, false); + + // Ph = compute_TVD(P_dum, true); + // Th = compute_TVD(T_dum, false); + + Ph = compute_UDS(P_dum, true); + Th = compute_UDS(T_dum, false); + + std::vector y_comp_dum(Ngrid+1, 0.0); // Vector to store values for individual component mole fraction + std::vector yh_comp_dum(Ngrid+1, 0.0); // Vector to store values for WENO calculation + + // Component vector WENO calculation + for (size_t j=0; j p_dum[p_dum.size()-2]){ - p_dum[p_dum.size()-1] = p_dum[p_dum.size()-2]; + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Velocity Calculation using Ergun equation %%%%%%%%%%%%%%%%%%%%%%%%%%%% + // Calculate property values at the node walls using WENO calculated values + for(size_t i = 0; i < Ngrid+1; i++) + { + sum_y = 0.0; + for(size_t j = 0; j < Ncomp; ++j) + { + sum_y += (MW[j] * yh[i * Ncomp + j]); // Sum of molar masses + } + ro_g[i] = (Ph[i]*p_total) / R / Th[i] / T_gas; // Gas Density based on wall values + kinetic_term_h[i] = (ro_g[i] * sum_y) * (1.75*(1-epsilon)) / 2.0 / r_p / epsilon; } - // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% BC Check %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // BCs check (This check ensures that variables at the boundary, also implemented in MATLAB code) - // In order to do that I need modifiable copy of vectors y_vec and Temp. Unfotunately, if I am initializing those copies within this function,... - // I get the C++ error : free(): invalid pointer ... - // I am a new to C++ and trying to address this issue. - // Thanks - - // for(size_t j = 0; j < Ncomp; ++j) - // { - // y_vec[Ngrid * Ncomp + j] = y_vec[Ngrid-1 * Ncomp + j]; - // } - // Temp[Ngrid] = Temp[Ngrid-1]; - p_dum[Ngrid] = p_dum[Ngrid-1]; + // Pressure Gradient calculation at the walls + dPdxh[0] = 2.0 * (P_dum[1] - P_dum[0]) * idx; // Inlet grid point using half cell approximation + for(size_t i = 1; i < Ngrid; ++i) + { + dPdxh[i] = (P_dum[i+1] - P_dum[i]) * idx; + } + dPdxh[Ngrid] = 2.0 * (Ph[Ngrid] - P_dum[Ngrid]) * idx; // Outlet grid point using half cell approximation - if (p_dum[Ngrid-1] >= 1.0){ - p_dum[Ngrid] = 1.0; - }else{ - p_dum[Ngrid] = p_dum[Ngrid-1]; + // Calculate Velocity based on Ergun's equation + v[0] = v_in/v_in; // first grid point + + for(size_t i = 1; i < Ngrid+1; ++i) + { + if (dPdxh[i] <= 0.0) + { + v_sign = 1; + } + else + { + v_sign = -1; + } + + v[i] = v_sign * (-vis_term + std::pow( + (std::abs(std::pow(vis_term, 2) + 4.0 * kinetic_term_h[i] * std::abs(dPdxh[i]*p_total/L))), 0.5)) + / 2.0 / kinetic_term_h[i] / v_in; + if (std::isnan(v[i])) + { + std::cout << "Nan encountered" << std::endl; + } } + // Copying calculated velocities to Vnew vector + std::copy(v.begin(), v.end(), Vnew.begin()); + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Finite Difference Calculation %%%%%%%%%%%%%%%%%%% - // First order differences + // First order differences based on WENO calculated values for(size_t i = 1; i < Ngrid+1; i++) { for(size_t j = 0; j < Ncomp; ++j) { - dydx[i * Ncomp + j] = (y_vec[i * Ncomp + j] - y_vec[(i-1) * Ncomp + j]) * idx; + // dydx[i * Ncomp + j] = (y_dum[i * Ncomp + j] - y_dum[(i-1) * Ncomp + j]) * idx; + dydx[i * Ncomp + j] = (yh[i * Ncomp + j] - yh[(i-1) * Ncomp + j]) * idx; } - dTdx[i] = (Temp[i] - Temp[i-1]) * idx; - dPdx[i] = (p_dum[i] - p_dum[i-1]) * idx; - - Pv[i] = (p_dum[i]*v[i] - p_dum[i-1]*v[i-1]) * idx; - PvT[i] = (p_dum[i]*v[i]/Temp[i] - p_dum[i-1]*v[i-1]/Temp[i-1]) * idx; + // dTdx[i] = (T_dum[i] - T_dum[i-1]) * idx; + dTdx[i] = (Th[i] - Th[i-1]) * idx; + + // Calculating the dPdz based on WENO Ph + // dPdx[i] = (P_dum[i] - P_dum[i-1]) * idx; + dPdx[i] = (Ph[i] - Ph[i-1]) * idx; + + // Pv[i] = (P_dum[i]*v[i] - P_dum[i-1]*v[i-1]) * idx; + // PvT[i] = (P_dum[i]*v[i]/T_dum[i] - P_dum[i-1]*v[i-1]/T_dum[i-1]) * idx; + Pv[i] = (Ph[i]*v[i] - Ph[i-1]*v[i-1]) * idx; + PvT[i] = (Ph[i]*v[i]/Th[i] - Ph[i-1]*v[i-1]/Th[i-1]) * idx; } // Second order differences - for(size_t i = 1; i < Ngrid; i++) // For middle nodes + // For 1st node, the differences have been calculated using half-cell approximations + for(size_t j = 0; j < Ncomp; ++j) + { + d2ydx2[1 * Ncomp + j] = (y_dum[2 * Ncomp + j] - 3.0*y_dum[1 * Ncomp + j] + 2.0*y_dum[0 * Ncomp + j]) * idx2; + } + d2Tdx2[1] = (T_dum[2] - 3.0*T_dum[1] + 2.0*T_dum[0]) * idx2; + + // For middle nodes + for(size_t i = 2; i < Ngrid; i++) { for(size_t j = 0; j < Ncomp; ++j) { - d2ydx2[i * Ncomp + j] = (y_vec[(i+1) * Ncomp + j] - 2.0*y_vec[i * Ncomp + j] + y_vec[(i-1) * Ncomp + j]) * idx2; + d2ydx2[i * Ncomp + j] = (y_dum[(i+1) * Ncomp + j] - 2.0*y_dum[i * Ncomp + j] + y_dum[(i-1) * Ncomp + j]) * idx2; } - d2Tdx2[i] = (Temp[i+1] - 2.0*Temp[i] + Temp[i-1]) * idx2; + d2Tdx2[i] = (T_dum[i+1] - 2.0*T_dum[i] + T_dum[i-1]) * idx2; } // For last node for(size_t j = 0; j < Ncomp; ++j) { - d2ydx2[Ngrid * Ncomp + j] = (y_vec[(Ngrid-1) * Ncomp + j] - y_vec[Ngrid * Ncomp + j]) * idx2; + d2ydx2[Ngrid * Ncomp + j] = (y_dum[(Ngrid-1) * Ncomp + j] - y_dum[Ngrid * Ncomp + j]) * idx2; } - d2Tdx2[Ngrid] = (Temp[Ngrid-1] - Temp[Ngrid]) * idx2; + d2Tdx2[Ngrid] = (T_dum[Ngrid-1] - T_dum[Ngrid]) * idx2; // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Balance equations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // first/inlet gridpoint, the conditions remains same @@ -762,11 +853,12 @@ void Breakthrough::computeFirstDerivatives(std::vector &dqdt, for(size_t j = 0; j < Ncomp; ++j) { dydt[0 * Ncomp + j] = 0.0; - dqdt[0 * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[0 * Ncomp + j]/Q_s0 - q[0 * Ncomp + j]); + // dqdt[0 * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[0 * Ncomp + j]/Q_s0 - q[0 * Ncomp + j]); + dqdt[0 * Ncomp + j] = 0.0; } - // middle gridpoints - for(size_t i = 1; i < Ngrid; i++) + // middle gridpoint and Ngrid node + for(size_t i = 1; i < Ngrid+1; i++) { // %%%%%%%%%%%%%%%%%%%%%%% Solid mass balance %%%%%%%%%%%%%%%%%%%%%%%%%% for(size_t j = 0; j < Ncomp; ++j) @@ -774,9 +866,9 @@ void Breakthrough::computeFirstDerivatives(std::vector &dqdt, dqdt[i * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[i * Ncomp + j]/Q_s0 - q[i * Ncomp + j]); } - // %%%%%%%%%%%%%%%%%%%%%%%%% Temperature balance %%%%%%%%%%%%%%%%%%%%%%%% + // %%%%%%%%%%%%%%%%%%%%%%%%% Energy balance %%%%%%%%%%%%%%%%%%%%%%%% dTdt1 = K_z/v_in/L * d2Tdx2[i] / sink_term[i]; - dTdt2 = -(epsilon*C_pg*p_total/R/T_gas) * (Pv[i] - Temp[i]*PvT[i]) / sink_term[i]; + dTdt2 = -(epsilon*C_pg*p_total/R/T_gas) * (Pv[i] - T_dum[i]*PvT[i]) / sink_term[i]; dTdt3 = 0.0; for(size_t j = 0; j < Ncomp; ++j) @@ -789,30 +881,31 @@ void Breakthrough::computeFirstDerivatives(std::vector &dqdt, // %%%%%%%%%%%%%%%%%%%%%%%%% Total Pressure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - dPdt1 = -Temp[i] * PvT[i]; - dPdt2 = p_dum[i] * dTdt[i] / Temp[i]; + dPdt1 = -T_dum[i] * PvT[i]; + dPdt2 = P_dum[i] * dTdt[i] / T_dum[i]; dPdt3 = 0.0; for(size_t j = 0; j < Ncomp; ++j) { - dPdt3 += -phi * Temp[i] * dqdt[i * Ncomp + j]; + dPdt3 += -phi * T_dum[i] * dqdt[i * Ncomp + j]; } dpdt[i] = dPdt1 + dPdt2 + dPdt3; // %%%%%%%%%%%%%%%%%%%%%%%%%% Mole balance %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% // For all components except carrier gas + dyPVT = 0.0; for(size_t j = 1; j < Ncomp; ++j) { - double dyPVT; - dyPVT = ((y_vec[i * Ncomp + j]*p_dum[i]*v[i]/Temp[i]) - - (y_vec[(i-1) * Ncomp + j]*p_dum[i-1]*v[i-1]/Temp[i-1])) * idx; - dydt1 = - (Temp[i]/p_dum[i]) * (dyPVT - y_vec[i * Ncomp + j]*PvT[i]); + dyPVT = ((yh[i * Ncomp + j]*Ph[i]*v[i]/Th[i]) + - (yh[(i-1) * Ncomp + j]*Ph[i-1]*v[i-1]/Th[i-1])) * idx; + + dydt1 = - (T_dum[i]/P_dum[i]) * (dyPVT - y_dum[i * Ncomp + j]*PvT[i]); dydt2 = (components[j].D/L/v_in) * (d2ydx2[i * Ncomp + j] - + (dPdx[i]*dydx[i * Ncomp + j])/p_dum[i] - - (dTdx[i]*dydx[i * Ncomp + j])/Temp[i]); - double res_dqdt = 0.0; + + (dPdx[i]*dydx[i * Ncomp + j])/P_dum[i] + - (dTdx[i]*dydx[i * Ncomp + j])/T_dum[i]); + res_dqdt = 0.0; for(size_t k = 0; k < Ncomp; ++k) { if (k != j) @@ -821,136 +914,159 @@ void Breakthrough::computeFirstDerivatives(std::vector &dqdt, } } - dydt3 = phi * Temp[i] / p_dum[i] - * ((y_vec[i * Ncomp + j] - 1)*dqdt[i * Ncomp + j] - + y_vec[i * Ncomp + j] * res_dqdt); + dydt3 = phi * T_dum[i] / P_dum[i] + * ((y_dum[i * Ncomp + j] - 1.0)*dqdt[i * Ncomp + j] + + y_dum[i * Ncomp + j] * res_dqdt); dydt[i * Ncomp + j] = dydt1 + dydt2 + dydt3; } } - // Outlet gridpoints - // %%%%%%%%%%%%%%%%%%%%%%% Solid mass balance %%%%%%%%%%%%%%%%%%%%%%%%%% - for(size_t j = 0; j < Ncomp; ++j) +} + +// Function to calculate UDS based values at node walls +std::vector Breakthrough::compute_UDS(std::vector &my_vec, bool is_pressure) +{ + // Vector to store computed values at the wall, the size of the vector is (Ngrid+1), including inlet and outlet. + std::vector my_vec_out(Ngrid+1, 0.0); + + if (!my_vec.empty()) { - dqdt[Ngrid * Ncomp + j] = (components[j].Kl * L/v_in) * (q_eq[Ngrid * Ncomp + j]/Q_s0 - q[Ngrid * Ncomp + j]); - } - - // %%%%%%%%%%%%%%%%%%%%%%%%% Temperature balance %%%%%%%%%%%%%%%%%%%%%%%% - dTdt[Ngrid] = dTdt[Ngrid-1]; + // For inlet node + my_vec_out[0] = my_vec[0]; - // %%%%%%%%%%%%%%%%%%%%%%%%% Total Pressure %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - dpdt[Ngrid] = dpdt[Ngrid-1]; + // For outlet node + if (is_pressure) + { + if (my_vec[Ngrid] >= 1.0) + { + my_vec_out[Ngrid] = 1.0; + } + else + { + my_vec_out[Ngrid] = my_vec[Ngrid]; + } + } + else + { + my_vec_out[Ngrid] = my_vec[Ngrid]; + } + } - // %%%%%%%%%%%%%%%%%%%%%%%%%% Mole balance %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - // For all components except carrier gas - for(size_t j = 1; j < Ncomp; ++j) + // For right walls of 2nd to Ngrid-1 node + for(size_t i=1; i Breakthrough::compute_WENO(std::vector &my_vec, bool is_pressure) { - double idx = 1.0 / dx; - // double mu = 1.13e-05; - // double r_p = 5.0e-03; + double tol = 1e-10; // Tolerance value in WENO scheme + double alpha_0 = 0.0; + double alpha_1 = 0.0; + double first_term = 0.0; + double second_term = 0.0; - double vis_term = 150.0 * mu * std::pow((1-epsilon), 2) - / 4.0 / std::pow(r_p, 2) / std::pow(epsilon, 2); - - std::vector ro_g(Ngrid+1); - std::vector kinetic_term(Ngrid+1); - std::vector dPdx(Ngrid+1); - std::vector p_dum(Ngrid+1); - std::copy(Pnew.begin(), Pnew.end(), p_dum.begin()); + // Vector to store computed values at the wall, the size of the vector is (Ngrid+1), including inlet and outlet. + std::vector my_vec_out(Ngrid+1, 0.0); - for (size_t i = 0; i < Ngrid+1; ++i) + if (!my_vec.empty()) { - double sum = 0.0; - for(size_t j = 0; j < Ncomp; ++j) + // For inlet node + my_vec_out[0] = my_vec[0]; + + // For outlet node + if (is_pressure) { - sum += (MW[j] * ynew[i * Ncomp + j]); + if (my_vec[Ngrid] >= 1.0) + { + my_vec_out[Ngrid] = 1.0; + } + else + { + my_vec_out[Ngrid] = my_vec[Ngrid]; + } } - if (p_dum[i]<=0.0){ - throw std::runtime_error("Error: Pressure becoming zero\n"); + else + { + my_vec_out[Ngrid] = my_vec[Ngrid]; } - ro_g[i] = p_dum[i]*p_total / R / Tnew[i] / T_gas; - kinetic_term[i] = (ro_g[i] * sum) * (1.75*(1-epsilon)) / 2.0 / r_p / epsilon; } + + // For right wall of 1st Node, alpha_1 and second term values are calculated using half-cell approximation + alpha_0 = (2.0/3.0) / std::pow((my_vec[2] - my_vec[1] + tol), 4.0); + alpha_1 = (1.0/3.0) / std::pow((2.0*(my_vec[1] - my_vec[0]) + tol), 4.0); - // Calculate Inlet pressure using inlet velocity and 2nd grid point pressure - // based on Ergun's equation - double sum = 0.0; - for(size_t j = 0; j < Ncomp; ++j) + first_term = (alpha_0 / (alpha_0 + alpha_1)) * ((1.0/2.0) * (my_vec[1] + my_vec[2])); + second_term = (alpha_1 / (alpha_0 + alpha_1)) * (2.0 * my_vec[1] - my_vec[0]); + my_vec_out[1] = first_term + second_term; + + // For right walls of 2nd to Ngrid-1 node + for(size_t i=2; i p_dum[p_dum.size()-2]){ - p_dum[p_dum.size()-1] = p_dum[p_dum.size()-2]; + alpha_0 = (2.0/3.0) / std::pow((my_vec[i+1] - my_vec[i] + tol), 4.0); + alpha_1 = (1.0/3.0) / std::pow((my_vec[i] - my_vec[i-1] + tol), 4.0); + + first_term = (alpha_0 / (alpha_0 + alpha_1)) * ((1.0/2.0) * (my_vec[i] + my_vec[i+1])); + second_term = (alpha_1 / (alpha_0 + alpha_1)) + * ((3.0/2.0) * my_vec[i] - (1.0/2.0) * my_vec[i-1]); + my_vec_out[i] = first_term + second_term; } - // Pressure gradient - dPdx[0] = 0.0; // Inlet grid point - for(size_t i = 1; i < Ngrid+1; ++i) + return my_vec_out; +} + +// Function to calculate TVD Van Leer based values at node walls +std::vector Breakthrough::compute_TVD(std::vector &my_vec, bool is_pressure) +{ + double tol = 1e-10; // Tolerance value in TVD scheme + double r_value = 0.0; + double flux_limiter = 0.0; + + // Vector to store computed values at the wall, the size of the vector is (Ngrid+1), including inlet and outlet. + std::vector my_vec_out(Ngrid+1, 0.0); + + if (!my_vec.empty()) { - dPdx[i] = (p_dum[i] - p_dum[i-1]) * idx; - } + // For inlet node + my_vec_out[0] = my_vec[0]; - // Calculate Velocity based on Ergun's equation - Vnew[0] = v_in/v_in; // first grid point - - int v_sign; - for(size_t i = 1; i < Ngrid+1; ++i) - { - if (dPdx[i] <= 0.0) + // For outlet node + if (is_pressure) { - v_sign = 1.0; + if (my_vec[Ngrid] >= 1.0) + { + my_vec_out[Ngrid] = 1.0; + } + else + { + my_vec_out[Ngrid] = my_vec[Ngrid]; + } } else { - v_sign = -1.0; + my_vec_out[Ngrid] = my_vec[Ngrid]; } - - Vnew[i] = v_sign * (-vis_term + std::pow( - (std::abs(std::pow(vis_term, 2) + 4.0 * kinetic_term[i] * std::abs(dPdx[i]*p_total/L))), 0.5)) - / 2.0 / kinetic_term[i] / v_in; - if (std::isnan(Vnew[i])) - { - std::cout << "Nan encountered" << std::endl; - } } - // middle gridpoints - // for(size_t i = 1; i < Ngrid; ++i) - // { - - // // sum = derivative at the actual gridpoint i - // double sum = 0.0; - // for(size_t j = 0; j < Ncomp; ++j) - // { - // sum = sum - prefactor[j] * (Qeqnew[i * Ncomp + j] - Qnew[i * Ncomp + j]) + - // components[j].D * (Pnew[(i - 1) * Ncomp + j] - 2.0 * Pnew[i * Ncomp + j] + Pnew[(i + 1) * Ncomp + j]) * idx2; - // } - - // // explicit version - // Vnew[i] = Vnew[i - 1] + dx * (sum - Vnew[i - 1] * dptdx) / Pt[i]; - // } - - // last grid point - // double sum = 0.0; - // for(size_t j = 0; j < Ncomp; ++j) - // { - // sum = sum - prefactor[j] * (Qeqnew[Ngrid * Ncomp + j] - Qnew[Ngrid * Ncomp + j]) + - // components[j].D * (Pnew[(Ngrid - 1) * Ncomp + j] - Pnew[Ngrid * Ncomp + j]) * idx2; - // } - - // // explicit version - // Vnew[Ngrid] = Vnew[Ngrid-1] + dx * (sum - Vnew[Ngrid - 1] * dptdx) / Pt[Ngrid]; + // For right wall of 1st Node, r_value is calculated using half-cell approximation + r_value = (2.0*(my_vec[1] - my_vec[0]) + tol) / ((my_vec[2] - my_vec[1]) + tol); + flux_limiter = (r_value + std::abs(r_value)) / (1.0 + std::abs(r_value)); + + my_vec_out[1] = my_vec[1] + 0.5 * flux_limiter * (my_vec[2] - my_vec[1]); + + // For right walls of 2nd to Ngrid-1 node + for(size_t i=2; i cachedP0; // cached hypothetical pressure std::vector cachedPsi; // cached reduced grand potential over the column + // Vectors of size (Ngrid+1), used as a dummy variable for balance equations + std::vector P_dum; + std::vector y_dum; + std::vector T_dum; + + // Vectors of size (Ngrid), used to store values at the walls of the nodes + std::vector Ph; + std::vector yh; + std::vector Th; + // Properties and Parameters double K_z; // Thermal conductivity of gas [J/mol/K] double C_ps; // Heat capacity of adsorbent [J/kg/K] @@ -127,22 +137,33 @@ struct Breakthrough Iterative = 1 }; + // void computeFirstDerivatives(std::vector &dqdt, + // std::vector &dpdt, + // std::vector &dTdt, + // std::vector &dydt, + // const std::vector &q_eq, + // const std::vector &q, + // const std::vector &v, + // const std::vector &p, + // const std::vector &T_vec, + // const std::vector &y_vec); + void computeFirstDerivatives(std::vector &dqdt, std::vector &dpdt, std::vector &dTdt, std::vector &dydt, const std::vector &q_eq, const std::vector &q, - const std::vector &v, const std::vector &p, - const std::vector &Temp, + const std::vector &T_vec, const std::vector &y_vec); - // const std::vector &T_arg, - // const std::vector &y_arg); - void computeEquilibriumLoadings(); - void computeVelocity(); + void computeEquilibriumLoadings(); + + std::vector compute_UDS(std::vector &my_vec, bool is_pressure); + std::vector compute_WENO(std::vector &my_vec, bool is_pressure); + std::vector compute_TVD(std::vector &my_vec, bool is_pressure); void createMovieScriptColumnV(); void createMovieScriptColumnPt(); From f62c85f21eb70a154ad025f328b2fe3438c4e8a0 Mon Sep 17 00:00:00 2001 From: Hassan Date: Fri, 10 Jan 2025 20:03:57 +0900 Subject: [PATCH 3/3] Changed timestep size --- src/simulation.input | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/simulation.input b/src/simulation.input index 471d100..c44ab3d 100644 --- a/src/simulation.input +++ b/src/simulation.input @@ -6,16 +6,16 @@ Temperature 298.15 // [K] Feed Gas Temperature, scalin ColumnVoidFraction 0.4 // [-] ParticleDensity 520 // [kg/m^3] TotalPressure 10e05 // [Pa] Total pressure, scaling variable for Pressure -PressureGradient 0 // [Pa/m] +PressureGradient -100 // [Pa/m] ColumnEntranceVelocity 0.50 // [m/s] Interstitial velocity, scaling variable for velocity and time ColumnLength 0.50 // [m] Total pressure, scaling variable for step length and time // Run settings -NumberOfTimeSteps 1000000 -PrintEvery 100 -WriteEvery 100 -TimeStep 1e-8 -NumberOfGridPoints 20 +NumberOfTimeSteps auto +PrintEvery 10000 +WriteEvery 100000 +TimeStep 3e-5 +NumberOfGridPoints 30 MixturePredictionMethod IAST // Not used Component 0 MoleculeName Helium