diff --git a/src/README.pdf b/src/README.pdf new file mode 100644 index 0000000..87d1d69 Binary files /dev/null and b/src/README.pdf differ diff --git a/src/bindings.cpp b/src/bindings.cpp index 8047029..b0cbe58 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -17,29 +17,24 @@ PYBIND11_MODULE(_ruptura, m) { py::class_(m, "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..39e3a9d 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,181 @@ 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: 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; + 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); + + // 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; + 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) + for(size_t i = 0; i < Ngrid + 1; ++i) { - V[i] = v_in * p_total / pt_init[i]; - } - - // set the partial pressure of the carrier gas to the total initial pressure - // for the column except for the entrance (i=0) - for (size_t i = 1; i < Ngrid + 1; ++i) - { - P[i * Ncomp + carrierGasComponent] = pt_init[i]; + // V[i] = v_in * p_total / pt_init[i]; + // V[i] = (v_in * 1 / pt_init[i]) / v_in; + V[i] = v_in/v_in; } // 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) - { - Yi[j] = std::max(P[i * Ncomp + j] / pt_init[i], 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(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]); - } + // 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 +323,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 +352,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 +385,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 +394,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 +409,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 +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, Qeq, Q, V, P); + 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 @@ -442,77 +435,139 @@ 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(); - - computeVelocity(); + for (size_t i = 0; 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 +576,498 @@ 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 &p, + const std::vector &T_vec, + const std::vector &y_vec) { double idx = 1.0 / dx; double idx2 = 1.0 / (dx * dx); + + // %%%%%%%%%%%%%%%%%%%% Variables required for balances equations %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + std::vector ro_g(Ngrid+1); + std::vector sink_term(Ngrid+1); + double vis_term; + std::vector kinetic_term_h(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); + 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; + + //%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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); + + 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 + } + 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); + } - // first gridpoint - for (size_t j = 0; j < Ncomp; ++j) + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% Inlet Boundary Pressure correction %%%%%%%%%%%%%% + sum_y = 0.0; + for(size_t j = 0; j < Ncomp; ++j) { - dqdt[0 * Ncomp + j] = components[j].Kl * (q_eq[0 * Ncomp + j] - q[0 * Ncomp + j]); - dpdt[0 * Ncomp + j] = 0.0; + sum_y += (MW[j] * y_dum[0 * Ncomp + j]); // sum of molar masses at the inlet node } + + // 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)); - // middle gridpoints - for (size_t i = 1; i < Ngrid; i++) + // %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 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 Breakthrough::compute_UDS(std::vector &my_vec, bool is_pressure) { - double idx2 = 1.0 / (dx * dx); + // 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); - // first grid point - Vnew[0] = v_in; - - // middle gridpoints - for (size_t i = 1; i < Ngrid; ++i) + if (!my_vec.empty()) { - // sum = derivative at the actual gridpoint i - 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 = - 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; + 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]; + } + } + + // 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 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; + + // 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); - // explicit version - Vnew[i] = Vnew[i - 1] + dx * (sum - Vnew[i - 1] * dptdx) / Pt[i]; + if (!my_vec.empty()) + { + // For inlet node + my_vec_out[0] = my_vec[0]; + + // 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]; + } } + + // 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); - // last grid point - 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 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()) + { + // For inlet node + my_vec_out[0] = my_vec[0]; + + // 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]; + } + } + + // 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= 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 +1167,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 +1230,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 +1321,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 +1335,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 +1384,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 +1400,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 +1446,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 +1463,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 +1523,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 +1540,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 +1600,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 +1617,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 +1677,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 +1694,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 +1754,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 +1775,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 +1835,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 +1857,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..f7dd189 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,165 @@ 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 + + // 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] + 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 &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 &p, + const std::vector &T_vec, + const std::vector &y_vec); + + + 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(); + 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..c44ab3d --- /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 -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 auto +PrintEvery 10000 +WriteEvery 100000 +TimeStep 3e-5 +NumberOfGridPoints 30 +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; }