From d5ff5d521fba9a135d8974d1bcb58f3f4728fa02 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Tue, 13 May 2025 13:28:04 +0200 Subject: [PATCH 01/48] initial concept --- .../reaction/ActivatedSludgeModelDrei.cpp | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp new file mode 100644 index 000000000..ff8da5126 --- /dev/null +++ b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp @@ -0,0 +1,298 @@ +// ============================================================================= +// CADET - The Chromatography Analysis and Design Toolkit +// +// Copyright © 2008-present: The CADET-Core Authors +// Please see the AUTHORS.md file. +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the GNU Public License v3.0 (or, at +// your option, any later version) which accompanies this distribution, and +// is available at http://www.gnu.org/licenses/gpl.html +// ============================================================================= + +#include "model/reaction/ReactionModelBase.hpp" +#include "model/ExternalFunctionSupport.hpp" +#include "model/ModelUtils.hpp" +#include "cadet/Exceptions.hpp" +#include "model/Parameters.hpp" +#include "LocalVector.hpp" +#include "SimulationTypes.hpp" +#include "linalg/ActiveDenseMatrix.hpp" +#include "MathUtil.hpp" +#include "Memory.hpp" + +#include +#include +#include +#include + +/* +{ + "name": "MichaelisMentenParamHandler", + "externalName": "ExtMichaelisMentenParamHandler", + "parameters": + [ + { "type": "ScalarReactionDependentParameter", "varName": "vMax", "confName": "MM_VMAX"}, + { "type": "ScalarReactionDependentParameter", "varName": "kMM", "confName": "MM_KMM"}, + { "type": "ComponentDependentReactionDependentParameter", "varName": "kInhibit", "confName": "MM_KI"} + ] +} +*/ + +/* Parameter description + ------------------------ +*/ + + +namespace cadet +{ + +namespace model +{ + +inline const char* MichaelisMentenParamHandler::identifier() CADET_NOEXCEPT { return "MICHAELIS_MENTEN"; } + +inline bool MichaelisMentenParamHandler::validateConfig(unsigned int nReactions, unsigned int nComp, unsigned int const* nBoundStates) +{ + return true; +} + +inline const char* ExtMichaelisMentenParamHandler::identifier() CADET_NOEXCEPT { return "EXT_MICHAELIS_MENTEN"; } + +inline bool ExtMichaelisMentenParamHandler::validateConfig(unsigned int nReactions, unsigned int nComp, unsigned int const* nBoundStates) +{ + return true; +} + +namespace +{ + /** + * @brief Registers a matrix-valued parameter (row-major storage) with components as rows + * @details The matrix-valued parameter has as many rows as there are components in the system. + * @param [in,out] parameters Parameter map + * @param [in] unitOpIdx Unit operation id + * @param [in] parTypeIdx Particle type index + * @param [in] paramName Name of the parameter + * @param [in] mat Matrix to register + */ + inline void registerCompRowMatrix(std::unordered_map& parameters, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, const std::string& paramName, cadet::linalg::ActiveDenseMatrix& mat) + { + const cadet::StringHash hashName = cadet::hashStringRuntime(paramName); + cadet::registerParam2DArray(parameters, mat.data(), mat.elements(), [=](bool multi, unsigned int row, unsigned int col) + { + return cadet::makeParamId(hashName, unitOpIdx, row, parTypeIdx, cadet::BoundStateIndep, col, cadet::SectionIndep); + }, + mat.columns() + ); + } +} + + +/** + * @brief Defines a Michaelis-Menten reaction kinetic with simple inhibition + * @details Implements the Michaelis-Menten kinetics: \f[ \begin{align} + * S \nu, + * \end{align} \f] + * where \f$ S \f$ is the stoichiometric matrix and the fluxes are given by + * \f[ \begin{align} + * \nu_i = \frac{\mu_{\mathrm{max},i} c_S}{k_{\mathrm{MM},i} + c_S}. + * \end{align} \f] + * The substrate component \f$ c_S \f$ is identified by the index of the + * first negative entry in the stoichiometry of this reaction. + * In addition, the reaction might be inhibited by other components. In this + * case, the flux has the form + * \f[ \begin{align} + * \nu_i = \frac{\mu_{\mathrm{max},i} c_S}{k_{\mathrm{MM},i} + c_S} \cdot \frac{1}{1 + \sum_i \frac{1+ k_{\mathrm{I},i,j}}{k_{\mathrm{I},i,j} + c_{\mathrm{I},j}}}. + * \end{align} \f] + * The value of \f$ k_{\mathrm{I},i,j} \f$ decides whether component \f$ j \f$ + * inhibits reaction \f$ i \f$. If \f$ k_{\mathrm{I},i,j} \leq 0 \f$, the component + * does not inhibit the reaction. + * Only reactions in liquid phase are supported (no solid phase or cross-phase reactions). + * @tparam ParamHandler_t Type that can add support for external function dependence + */ +template +class MichaelisMentenReactionBase : public DynamicReactionModelBase +{ +public: + + MichaelisMentenReactionBase() : _idxSubstrate(0) { } + virtual ~MichaelisMentenReactionBase() CADET_NOEXCEPT { } + + static const char* identifier() { return ParamHandler_t::identifier(); } + virtual const char* name() const CADET_NOEXCEPT { return ParamHandler_t::identifier(); } + + virtual void setExternalFunctions(IExternalFunction** extFuns, unsigned int size) { _paramHandler.setExternalFunctions(extFuns, size); } + virtual bool dependsOnTime() const CADET_NOEXCEPT { return ParamHandler_t::dependsOnTime(); } + virtual bool requiresWorkspace() const CADET_NOEXCEPT { return true; } + virtual unsigned int workspaceSize(unsigned int nComp, unsigned int totalNumBoundStates, unsigned int const* nBoundStates) const CADET_NOEXCEPT + { + return _paramHandler.cacheSize(_stoichiometryBulk.columns(), nComp, totalNumBoundStates) + std::max(_stoichiometryBulk.columns() * sizeof(active), 2 * (_nComp + totalNumBoundStates) * sizeof(double)); + } + + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) + { + DynamicReactionModelBase::configureModelDiscretization(paramProvider, nComp, nBound, boundOffset); + + if (paramProvider.exists("MM_STOICHIOMETRY_BULK")) + { + const std::size_t numElements = paramProvider.numElements("MM_STOICHIOMETRY_BULK"); + if (numElements % nComp != 0) + throw InvalidParameterException("Size of field MM_STOICHIOMETRY_BULK must be a positive multiple of NCOMP (" + std::to_string(nComp) + ")"); + + const unsigned int nReactions = numElements / nComp; + + _stoichiometryBulk.resize(nComp, nReactions); + _idxSubstrate = std::vector(nReactions, -1); + } + + return true; + } + + virtual unsigned int numReactionsLiquid() const CADET_NOEXCEPT { return _stoichiometryBulk.columns(); } + virtual unsigned int numReactionsCombined() const CADET_NOEXCEPT { return 0; } + + CADET_DYNAMICREACTIONMODEL_BOILERPLATE + +protected: + ParamHandler_t _paramHandler; //!< Handles parameters and their dependence on external functions + + linalg::ActiveDenseMatrix _stoichiometry; + + + + virtual bool configureStoich(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx) + { + return true; + } + + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx) + { + _paramHandler.configure(paramProvider, _stoichiometryBulk.columns(), _nComp, _nBoundStates); + _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); + + // configure stoichiometric matrix from given parameters + //configureStoich(paramProvider,unitOpIdx, parTypeIdx) + + //registerCompRowMatrix(_parameters, unitOpIdx, parTypeIdx, "MM_STOICHIOMETRY_BULK", _stoichiometryBulk); todo + + return true; + } + + template + int residualLiquidImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, + StateType const* y, ResidualType* res, const FactorType& factor, LinearBufferAllocator workSpace) const + { + typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); + + // Calculate fluxes + typedef typename DoubleActivePromoter::type flux_t; + BufferedArray fluxes = workSpace.array(_stoichiometry.columns()); + /* | Component | Index | + | -------- - | ---- - | + | SO | 0 | + | SS | 1 | + | SNH | 2 | + | SNO | 3 | + | SN2 | 4 | + | SALK | 5 | + | SI | 6 | + | XI | 7 | + | XS | 8 | + | XH | 9 | + | XSTO | 10 | + | XA | 11 | + | XMI | 12 | */ + + // flux 0: kh2o fto(T) HS/XH/(... + ParamType kh2o = 0.0; + ParamType ft04 = 0.0; // eigentlich abhännig von temperatur (später Parameter-Parameter Dependencies) + ParamType kX = 0.0; + + StateType XS = y[8]; + StateType XH = y[9]; + + fluxes[0] = kh2o * ft0 * (XS / XH) / (XS / XH) + kX * XH; + + // flux 1: + fluxes[1] = + fluxes[2] = + fluxes[3] = + fluxes[4] = + fluxes[5] = + fluxes[6] = + fluxes[7] = + fluxes[8] = + fluxes[9] = + fluxes[10] = + fluxes[11] = + + + // Add reaction terms to residual + _stoichiometry.multiplyVector(static_cast(fluxes), factor, res); + + return 0; + } + + template + int residualCombinedImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, + StateType const* yLiquid, StateType const* ySolid, ResidualType* resLiquid, ResidualType* resSolid, double factor, LinearBufferAllocator workSpace) const + { + std::fill_n(resLiquid, _nComp, 0.0); + + if (_nTotalBoundStates == 0) + return 0; + + std::fill_n(resSolid, _nTotalBoundStates, 0.0); + + return 0; + } + + template + void jacobianLiquidImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, double const* y, double factor, const RowIterator& jac, LinearBufferAllocator workSpace) const + { + typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); + + + + + + // for each inhibitor component + curJac = jac; + for (int row = 0; row < _nComp; ++row, ++curJac) + { + const double colFactor = static_cast(_stoichiometryBulk.native(row, r)) * factor; + for (int comp = 0; comp < _nComp; ++comp) + { + const double kI = static_cast(p->kInhibit[_nComp * r + comp]); + if (kI <= 0.0) + continue; + + double dvdi = - (vMax * y[idxSubs] * (kMM + y[idxSubs])) / (denom * denom * kI); + curJac[comp - static_cast(row)] += colFactor * dvdi; + } + } + + } + + template + void jacobianCombinedImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, double const* yLiquid, double const* ySolid, double factor, const RowIteratorLiquid& jacLiquid, const RowIteratorSolid& jacSolid, LinearBufferAllocator workSpace) const + { + } +}; + +typedef MichaelisMentenReactionBase MichaelisMentenReaction; +typedef MichaelisMentenReactionBase ExternalMichaelisMentenReaction; + +namespace reaction +{ + void registerMichaelisMentenReaction(std::unordered_map>& reactions) + { + reactions[MichaelisMentenReaction::identifier()] = []() { return new MichaelisMentenReaction(); }; + reactions[ExternalMichaelisMentenReaction::identifier()] = []() { return new ExternalMichaelisMentenReaction(); }; + } +} // namespace reaction + +} // namespace model + +} // namespace cadet From cd09ef8f908ac657a57f1cd78bcd06b3808f87f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Tue, 13 May 2025 14:29:19 +0200 Subject: [PATCH 02/48] ASM3: compute p2-5 --- .../reaction/ActivatedSludgeModelDrei.cpp | 53 +++++++++++++------ 1 file changed, 37 insertions(+), 16 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp index ff8da5126..53f86875f 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp @@ -205,27 +205,48 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase | XMI | 12 | */ // flux 0: kh2o fto(T) HS/XH/(... - ParamType kh2o = 0.0; - ParamType ft04 = 0.0; // eigentlich abhännig von temperatur (später Parameter-Parameter Dependencies) - ParamType kX = 0.0; + ParamType Kh20 = 0.0; + ParamType ft04 = 0.0; // eigentlich abhängig von temperatur (später Parameter-Parameter Dependencies) + //ParamType KX = 0.0; // TODO: is this a param? StateType XS = y[8]; StateType XH = y[9]; - fluxes[0] = kh2o * ft0 * (XS / XH) / (XS / XH) + kX * XH; + // p1: Hydrolysis of organic structures + fluxes[0] = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - // flux 1: - fluxes[1] = - fluxes[2] = - fluxes[3] = - fluxes[4] = - fluxes[5] = - fluxes[6] = - fluxes[7] = - fluxes[8] = - fluxes[9] = - fluxes[10] = - fluxes[11] = + // p2: Aerobic storage of SS + fluxes[1] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + + // p3: Anoxic storage of SS + fluxes[2] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + + // p4: Aerobic growth of heterotrophic biomass (XH) + fluxes[3] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; + + // p5: Anoxic growth of heterotrophic biomass (XH, denitrification) + fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + + // r6: Aerobic endogenous respiration of heterotroph microorganisms (XH) + //fluxes[5] = + + // r7: Anoxic endogenous respiration of heterotroph microorganisms (XH) + //fluxes[6] = + + // r8: Aerobic respiration of internal cell storage products + //fluxes[7] = + + // r9: Anoxic respiration of internal cell storage products + //fluxes[8] = + + // r10: Aerobic growth of autotrophic biomass (XAUT, nitrification) + //fluxes[9] = + + // r11: Aerobic endogenous respiration of autotrophic biomass (XAUT) + //fluxes[10] = + + // r12: Anoxic endogenous respiration of autotrophic biomass (XAUT) + //fluxes[11] = // Add reaction terms to residual From cd8d38842aa9775a0421c582b32a48f5cdd12922 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Tue, 13 May 2025 15:01:38 +0200 Subject: [PATCH 03/48] ASM3: compute r6-12 --- .../reaction/ActivatedSludgeModelDrei.cpp | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp index 53f86875f..10c0f30f6 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp @@ -209,6 +209,11 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase ParamType ft04 = 0.0; // eigentlich abhängig von temperatur (später Parameter-Parameter Dependencies) //ParamType KX = 0.0; // TODO: is this a param? + StateType SO = y[0]; + StateType SS = y[1]; + StateType SNH = y[2]; + StateType SNO = y[3]; + StateType XS = y[8]; StateType XH = y[9]; @@ -228,25 +233,25 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; // r6: Aerobic endogenous respiration of heterotroph microorganisms (XH) - //fluxes[5] = + fluxes[5] = bH * SO / (SO + KHO2) * XH; // r7: Anoxic endogenous respiration of heterotroph microorganisms (XH) - //fluxes[6] = + fluxes[6] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; // r8: Aerobic respiration of internal cell storage products - //fluxes[7] = + fluxes[7] = bH * SO / (SO + KHO2) * XSTO; // r9: Anoxic respiration of internal cell storage products - //fluxes[8] = + fluxes[8] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; // r10: Aerobic growth of autotrophic biomass (XAUT, nitrification) - //fluxes[9] = + fluxes[9] = muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; // r11: Aerobic endogenous respiration of autotrophic biomass (XAUT) - //fluxes[10] = + fluxes[10] = bAUT * SO / (SO + KHO2) * XA; // r12: Anoxic endogenous respiration of autotrophic biomass (XAUT) - //fluxes[11] = + fluxes[11] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; // Add reaction terms to residual From 3c56f72c917623dd7c6566db36acb35f4d4295fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Tue, 13 May 2025 15:52:16 +0200 Subject: [PATCH 04/48] ASM3: set up parameters and derived variables --- .../reaction/ActivatedSludgeModelDrei.cpp | 61 ++++++++++++------- 1 file changed, 40 insertions(+), 21 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp index 10c0f30f6..798b5b5db 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp @@ -188,34 +188,54 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase // Calculate fluxes typedef typename DoubleActivePromoter::type flux_t; BufferedArray fluxes = workSpace.array(_stoichiometry.columns()); - /* | Component | Index | - | -------- - | ---- - | - | SO | 0 | - | SS | 1 | - | SNH | 2 | - | SNO | 3 | - | SN2 | 4 | - | SALK | 5 | - | SI | 6 | - | XI | 7 | - | XS | 8 | - | XH | 9 | - | XSTO | 10 | - | XA | 11 | - | XMI | 12 | */ - // flux 0: kh2o fto(T) HS/XH/(... - ParamType Kh20 = 0.0; - ParamType ft04 = 0.0; // eigentlich abhängig von temperatur (später Parameter-Parameter Dependencies) - //ParamType KX = 0.0; // TODO: is this a param? + ParamType Kh20 = 10.2; + ParamType T = 20; + ParamType k_sto20 = 13.68; + ParamType KX = 1; + ParamType KHO2 = 0.2; + ParamType KHSS = 3; + ParamType KHNO3 = 0.5; + ParamType etaHNO3 = 0.5; + ParamType KHNH4 = 0.01; + ParamType KHALK = 0.1; + ParamType KHSTO = 0.11; + ParamType muH20 = 3; + ParamType etaHend = 0.5; + ParamType bH20 = 0.33; + ParamType muAUT20 = 1.12; + ParamType KNO2 = 0.5; + ParamType KNNH4 = 0.7; + ParamType KNALK = 0.5; + ParamType bAUT20 = 0.18; + ParamType etaNend = 0.5; + + // derived parameters + // TODO use parameter-parameter dependency + double ft04 = exp(-0.04 * (20.0 - static_cast(T))); + double ft07 = exp(-0.06952 * (20 - static_cast(T))); + double ft105 = exp(-0.105 * (20 - static_cast(T))); + double k_sto = static_cast(k_sto20) * ft07; + double muH = static_cast(muH20) * ft07; + double bH = static_cast(bH20) * ft07; + double muAUT = static_cast(muAUT20) * ft105; + double bAUT = static_cast(bAUT20) * ft105; StateType SO = y[0]; StateType SS = y[1]; StateType SNH = y[2]; StateType SNO = y[3]; - + StateType SN2 = y[4]; + StateType SALK = y[5]; + // StateType SI = y[6]; // unused + // StateType XI = y[7]; // unused StateType XS = y[8]; StateType XH = y[9]; + StateType XH_S = XH; // ASM3hC: XH_S = max(XH, 0.1) + StateType XSTO = y[10]; + StateType XA = y[11]; + // StateType XMI = y[12]; // unused + // p1: Hydrolysis of organic structures fluxes[0] = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; @@ -252,7 +272,6 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase // r12: Anoxic endogenous respiration of autotrophic biomass (XAUT) fluxes[11] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - // Add reaction terms to residual _stoichiometry.multiplyVector(static_cast(fluxes), factor, res); From 99713585b087f1a96de24bb262308ede5572c5c9 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 14 May 2025 08:39:06 +0200 Subject: [PATCH 05/48] register model and jacobian equation as pseudo code --- src/libcadet/CMakeLists.txt | 1 + src/libcadet/ReactionModelFactory.cpp | 1 + ...Drei.cpp => ActivatedSludgeModelThree.cpp} | 127 ++++++++++++++---- 3 files changed, 100 insertions(+), 29 deletions(-) rename src/libcadet/model/reaction/{ActivatedSludgeModelDrei.cpp => ActivatedSludgeModelThree.cpp} (62%) diff --git a/src/libcadet/CMakeLists.txt b/src/libcadet/CMakeLists.txt index 3c5e41a91..db77fbdc9 100644 --- a/src/libcadet/CMakeLists.txt +++ b/src/libcadet/CMakeLists.txt @@ -114,6 +114,7 @@ set(LIBCADET_REACTIONMODEL_SOURCES ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/DummyReaction.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/MassActionLawReaction.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/MichaelisMentenReaction.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/CrystallizationReaction.cpp ) diff --git a/src/libcadet/ReactionModelFactory.cpp b/src/libcadet/ReactionModelFactory.cpp index 598662c47..63b041043 100644 --- a/src/libcadet/ReactionModelFactory.cpp +++ b/src/libcadet/ReactionModelFactory.cpp @@ -33,6 +33,7 @@ namespace cadet model::reaction::registerDummyReaction(_dynamicModels); model::reaction::registerMassActionLawReaction(_dynamicModels); model::reaction::registerMichaelisMentenReaction(_dynamicModels); + model::reaction::registerActivatedSludgeModelThreeReaction(_dynamicModels); model::reaction::registerCrystallizationReaction(_dynamicModels); } diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp similarity index 62% rename from src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp rename to src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 798b5b5db..4c172794a 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelDrei.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -28,8 +28,8 @@ /* { - "name": "MichaelisMentenParamHandler", - "externalName": "ExtMichaelisMentenParamHandler", + "name": "ActivatedSludgeModelThreeParamHandler", + "externalName": "ExtActivatedSludgeModelThreeParamHandler", "parameters": [ { "type": "ScalarReactionDependentParameter", "varName": "vMax", "confName": "MM_VMAX"}, @@ -50,16 +50,16 @@ namespace cadet namespace model { -inline const char* MichaelisMentenParamHandler::identifier() CADET_NOEXCEPT { return "MICHAELIS_MENTEN"; } +inline const char* ActivatedSludgeModelThreeParamHandler::identifier() CADET_NOEXCEPT { return "ACTIVATED_SLUDGE_MODEL3"; } -inline bool MichaelisMentenParamHandler::validateConfig(unsigned int nReactions, unsigned int nComp, unsigned int const* nBoundStates) +inline bool ActivatedSludgeModelThreeParamHandler::validateConfig(unsigned int nReactions, unsigned int nComp, unsigned int const* nBoundStates) { return true; } -inline const char* ExtMichaelisMentenParamHandler::identifier() CADET_NOEXCEPT { return "EXT_MICHAELIS_MENTEN"; } +inline const char* ExtActivatedSludgeModelThreeParamHandler::identifier() CADET_NOEXCEPT { return "EXT_ACTIVATED_SLUDGE_MODEL3"; } -inline bool ExtMichaelisMentenParamHandler::validateConfig(unsigned int nReactions, unsigned int nComp, unsigned int const* nBoundStates) +inline bool ExtActivatedSludgeModelThreeParamHandler::validateConfig(unsigned int nReactions, unsigned int nComp, unsigned int const* nBoundStates) { return true; } @@ -111,12 +111,12 @@ namespace * @tparam ParamHandler_t Type that can add support for external function dependence */ template -class MichaelisMentenReactionBase : public DynamicReactionModelBase +class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { public: - MichaelisMentenReactionBase() : _idxSubstrate(0) { } - virtual ~MichaelisMentenReactionBase() CADET_NOEXCEPT { } + ActivatedSludgeModelThreeBase() : _idxSubstrate(0) { } + virtual ~ActivatedSludgeModelThreeBase() CADET_NOEXCEPT { } static const char* identifier() { return ParamHandler_t::identifier(); } virtual const char* name() const CADET_NOEXCEPT { return ParamHandler_t::identifier(); } @@ -211,7 +211,6 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase ParamType etaNend = 0.5; // derived parameters - // TODO use parameter-parameter dependency double ft04 = exp(-0.04 * (20.0 - static_cast(T))); double ft07 = exp(-0.06952 * (20 - static_cast(T))); double ft105 = exp(-0.105 * (20 - static_cast(T))); @@ -297,26 +296,96 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase void jacobianLiquidImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, double const* y, double factor, const RowIterator& jac, LinearBufferAllocator workSpace) const { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); + curJac = jac; + + // reaction 1: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; + // dr1/XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX)^2 * XH; + // dr1/XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / XH_S^2 * XH; + // dr1/HX = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); + + //curJac = jac; + // curJac[8] = dr1/XS; + // curJac[X] = dr1/XH_S; ?? + // curJac[9] = dr1/XH; + + // curjac++; + + // reaction2: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + // dr2/S0 = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / (SO + KHO2)^2 * XH; + // dr2/SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / (SS + KHSS)^2 * XH; + // dr2/SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; + // dr2/XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + + //curJac[-1] = dr2/S0; + //curJac[0] = dr2/SS; + //curJac[1] = dr2/SNO; + //curJac[8] = dr2/XH; + + // curJac++; + + //reaction3: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + // dr3/S0 = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2)^2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + // dr3/SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS)^2 * SNO / (SNO + KHNO3) * XH; + // dr3/SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; + // dr3/XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + + //reaction4: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; + // dr4/S0 = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SO + KHO2)^2 * XH; + // dr4/SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SNH + KHNH4)^2 * XH; + // dr4/SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SALK + KHALK)^2 * XH; + // dr4/XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S + KHSTO)^2 * XH; + // dr4/XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / XSTO/XH_S^2 * XH; + // dr4/XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); + + //reaction5: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dr5/S0 = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO)^2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dr5/SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH)^2 * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dr5/SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK)^2 * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dr5/XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S + KHSTO)^2 * SNO / (KHNO3 + SNO) * XH; + // dr5/XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / XSTO/XH_S^2 * SNO / (KHNO3 + SNO) * XH; + // dr5/SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / (KHNO3 + SNO)^2 * XH; + // dr5/XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); + + + //reaction6: bH * SO / (SO + KHO2) * XH; + // dr6/S0 = bH * ft07 * XH / (SO + KHO2)^2; + // dr6/XH = bH * ft07 * SO / (SO + KHO2); + + //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + // dr7/S0 = bH * ft07 * etaHend * KHO2 / (SO + KHO2)^2 * SNO / (SNO + KHNO3) * XH; + // dr7/SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3)^2 * XH; + // dr7/XH = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + //reaction8: bH * SO / (SO + KHO2) * XSTO; + // dr8/S0 = bH * ft07 * XSTO / (SO + KHO2)^2; + // dr8/XSTO = bH * ft07 * SO / (SO + KHO2); + //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; + // dr9/S0 = bH * ft07 * etaHend * KHO2 / (SO + KHO2)^2 * SNO / (SNO + KHNO3) * XSTO; + // dr9/SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3)^2 * XSTO; + //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; + // dr10/S0 = muAUT * ft105 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / (SO + KNO2)^2; + // dr10/SNH = muAUT * ft105 * SO / (SO + KNO2) * SALK / (SALK + KNALK) / (SNH + KNNH4)^2 * XA; + // dr10/SALK = muAUT * ft105 * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / (SALK + KNALK)^2 * XA; + //reaction11: bAUT * SO / (SO + KHO2) * XA; + // dr11/S0 = bAUT * ft105 * XA / (SO + KHO2)^2; + // dr11/XA = bAUT * ft105 * SO / (SO + KHO2); - // for each inhibitor component - curJac = jac; - for (int row = 0; row < _nComp; ++row, ++curJac) - { - const double colFactor = static_cast(_stoichiometryBulk.native(row, r)) * factor; - for (int comp = 0; comp < _nComp; ++comp) - { - const double kI = static_cast(p->kInhibit[_nComp * r + comp]); - if (kI <= 0.0) - continue; - double dvdi = - (vMax * y[idxSubs] * (kMM + y[idxSubs])) / (denom * denom * kI); - curJac[comp - static_cast(row)] += colFactor * dvdi; - } - } + //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + // dr12/S0 = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2)^2 * XA; + // dr12/SNO = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3)^2 * XA; + // dr12/XA = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + + + + // StateType SI = y[6]; // unused + // StateType XI = y[7]; // unused + // StateType XMI = y[12]; // unused + + } @@ -326,15 +395,15 @@ class MichaelisMentenReactionBase : public DynamicReactionModelBase } }; -typedef MichaelisMentenReactionBase MichaelisMentenReaction; -typedef MichaelisMentenReactionBase ExternalMichaelisMentenReaction; +typedef ActivatedSludgeModelThreeBase ActivatedSludgeModelThreeReaction; +typedef ActivatedSludgeModelThreeBase ExternalActivatedSludgeModelThreeReaction; namespace reaction { - void registerMichaelisMentenReaction(std::unordered_map>& reactions) + void registerActivatedSludgeModelThreeReaction(std::unordered_map>& reactions) { - reactions[MichaelisMentenReaction::identifier()] = []() { return new MichaelisMentenReaction(); }; - reactions[ExternalMichaelisMentenReaction::identifier()] = []() { return new ExternalMichaelisMentenReaction(); }; + reactions[ActivatedSludgeModelThreeReaction::identifier()] = []() { return new ActivatedSludgeModelThreeReaction(); }; + reactions[ExternalActivatedSludgeModelThreeReaction::identifier()] = []() { return new ExternalActivatedSludgeModelThreeReaction(); }; } } // namespace reaction From 12a5b67de181191a080857096b10f0e9d7ea0424 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 09:26:27 +0200 Subject: [PATCH 06/48] ASM3: add Aeration --- src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 4c172794a..e98f17cc6 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -191,6 +191,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase ParamType Kh20 = 10.2; ParamType T = 20; + ParamType iO2 = 0; + ParamType V = 1; ParamType k_sto20 = 13.68; ParamType KX = 1; ParamType KHO2 = 0.2; @@ -271,6 +273,9 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // r12: Anoxic endogenous respiration of autotrophic biomass (XAUT) fluxes[11] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + + // r13: Aeration + fluxes[12] = iO2 / V * 1000; // Add reaction terms to residual _stoichiometry.multiplyVector(static_cast(fluxes), factor, res); From 5e65317124c4d221d0fec29ab2d2d80c86688e9a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 10:00:14 +0200 Subject: [PATCH 07/48] ASM3: basic stoichiometry definition --- .../reaction/ActivatedSludgeModelThree.cpp | 96 ++++++++++++++++++- 1 file changed, 95 insertions(+), 1 deletion(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index e98f17cc6..594e76360 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -168,7 +168,101 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx) { - _paramHandler.configure(paramProvider, _stoichiometryBulk.columns(), _nComp, _nBoundStates); + _stoichiometry.resize(_nComp, 13); + _stoichiometry.setAll(0); + + // SO + _stoichiometry.native(0, 1) = YSTO_aer - 1; + _stoichiometry.native(0, 3) = 1 - 1 / YH_aer; + _stoichiometry.native(0, 5) = -1 * (1 - fXI); + _stoichiometry.native(0, 7) = -1; + _stoichiometry.native(0, 9 = -64/14 * 1/YA + 1; + _stoichiometry.native(0, 10) = -1 * (1 - fXI); + _stoichiometry.native(0, 12) = 1; + + // SS + _stoichiometry.native(1, 0) = 1 - fSI; + _stoichiometry.native(1, 1) = -1; + _stoichiometry.native(1, 2) = -1; + + // SNH + _stoichiometry.native(2, 0) = c1n; + _stoichiometry.native(2, 1) = c2n; + _stoichiometry.native(2, 2) = c3n; + _stoichiometry.native(2, 3) = c4n; + _stoichiometry.native(2, 4) = c5n; + _stoichiometry.native(2, 5) = c6n; + _stoichiometry.native(2, 6) = c7n; + _stoichiometry.native(2, 9) = c10n; + _stoichiometry.native(2, 10) = c11n; + _stoichiometry.native(2, 11) = c12n; + + // SNO + _stoichiometry.native(3, 2) = c3no; + _stoichiometry.native(3, 4) = c5no; + _stoichiometry.native(3, 6) = c7no; + _stoichiometry.native(3, 8) = c9no; + _stoichiometry.native(3, 9) = c10no; + _stoichiometry.native(3, 11) = c12no; + + // SN2 + _stoichiometry.native(4, 2) = -c3no; + _stoichiometry.native(4, 4) = -c5no; + _stoichiometry.native(4, 6) = -c7no; + _stoichiometry.native(4, 8) = -c9no; + _stoichiometry.native(4, 11) = -c12no; + + // SALK + _stoichiometry.native(5, 0) = c1a; + _stoichiometry.native(5, 1) = c2a; + _stoichiometry.native(5, 2) = c3a; + _stoichiometry.native(5, 3) = c4a; + _stoichiometry.native(5, 4) = c5a; + _stoichiometry.native(5, 5) = c6a; + _stoichiometry.native(5, 6) = c7a; + _stoichiometry.native(5, 8) = c9a; + _stoichiometry.native(5, 9) = c10a; + _stoichiometry.native(5, 10) = c11a; + _stoichiometry.native(5, 11) = c12a; + + // SI + _stoichiometry.native(6, 0) = fSI; + + // XI + _stoichiometry.native(7, 5) = fXI; + _stoichiometry.native(7, 6) = fXI; + _stoichiometry.native(7, 10) = fXI; + _stoichiometry.native(7, 11) = fXI; + + // XS + _stoichiometry.native(8, 0) = -1; + + // XH + _stoichiometry.native(9, 3) = 1; + _stoichiometry.native(9, 4) = 1; + _stoichiometry.native(9, 5) = -1; + _stoichiometry.native(9, 6) = -1; + + // XSTO + _stoichiometry.native(10, 1) = YSO_aer; + _stoichiometry.native(10, 2) = YSTO_anox; + _stoichiometry.native(10, 3) = -1 / YH_aer; + _stoichiometry.native(10, 4) = -1 / YH_anox; + _stoichiometry.native(10, 7) = -1; + _stoichiometry.native(10, 8) = -1; + + // XA + _stoichiometry.native(11, 9) = 1; + _stoichiometry.native(11, 10) = -1; + _stoichiometry.native(11, 11) = -1; + + // XMI + _stoichiometry.native(12, 5) = fXMI_BM; + _stoichiometry.native(12, 6) = fXMI_BM; + _stoichiometry.native(12, 10) = fXMI_BM; + _stoichiometry.native(12, 11) = fXMI_BM; + + //_paramHandler.configure(paramProvider, _stoichiometry.columns(), _nComp, _nBoundStates); _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); // configure stoichiometric matrix from given parameters From 26d0ff161e797a055891a5e56091ea50a28123e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 10:31:46 +0200 Subject: [PATCH 08/48] ASM3: parameter set required for stoichiometry --- .../reaction/ActivatedSludgeModelThree.cpp | 57 +++++++++++++++++-- 1 file changed, 52 insertions(+), 5 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 594e76360..bf2245ad1 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -126,7 +126,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase virtual bool requiresWorkspace() const CADET_NOEXCEPT { return true; } virtual unsigned int workspaceSize(unsigned int nComp, unsigned int totalNumBoundStates, unsigned int const* nBoundStates) const CADET_NOEXCEPT { - return _paramHandler.cacheSize(_stoichiometryBulk.columns(), nComp, totalNumBoundStates) + std::max(_stoichiometryBulk.columns() * sizeof(active), 2 * (_nComp + totalNumBoundStates) * sizeof(double)); + return _paramHandler.cacheSize(_stoichiometry.columns(), nComp, totalNumBoundStates) + std::max(_stoichiometry.columns() * sizeof(active), 2 * (_nComp + totalNumBoundStates) * sizeof(double)); } virtual bool configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) @@ -141,14 +141,14 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const unsigned int nReactions = numElements / nComp; - _stoichiometryBulk.resize(nComp, nReactions); + _stoichiometry.resize(nComp, nReactions); _idxSubstrate = std::vector(nReactions, -1); } return true; } - virtual unsigned int numReactionsLiquid() const CADET_NOEXCEPT { return _stoichiometryBulk.columns(); } + virtual unsigned int numReactionsLiquid() const CADET_NOEXCEPT { return _stoichiometry.columns(); } virtual unsigned int numReactionsCombined() const CADET_NOEXCEPT { return 0; } CADET_DYNAMICREACTIONMODEL_BOILERPLATE @@ -171,12 +171,59 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.resize(_nComp, 13); _stoichiometry.setAll(0); + // parameter set ASM3hC + double iNSI = 0.03; + double iNSS = 0.1; + double iNXI = 0.12; + double iNXS = 0.0975; + double iNBM = 0.2; + double fSI = 0; + double YH_aer = 0.850793651; + double YH_anox = 0.698148148; + double YSTO_aer = 0.8966167647; + double YSTO_anox = 0.74375; + double fXI = 0.2; + double YA = 0.09; + double fiSS_BM_prod = 1; + double iVSS_BM = 1.956181534; + double iTSS_VSS_BM = 1.086956522; + + // internal variables + double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); + double c1n = iNXS - iNSI * fSI - (1 - fSI) * iNSS; + double c2n = iNSS; + double c3n = iNSS; + double c4n = -iNBM; + double c5n = c4n; + double c6n = -fXI * iNXI + iNBM; + double c7n = c6n; + double c10n = -1 / YA - iNBM; + double c11n = -fXI * iNXI + iNBM; + double c12n = c11n; + double c3no = (YSTO_anox - 1) / (40 / 14); + double c5no = (1 - 1/YH_anox) / (40 / 14); + double c7no = (fXI - 1) / (40 / 14); + double c9no = -14 / 40; + double c10no = 1 / YA; + double c12no = c7no; + double c1a = c1n / 14; + double c2a = c2n / 14; + double c3a = (c3n - c3no) / 14; + double c4a = c4n / 14; + double c5a = (c5n - c5no) / 14; + double c6a = c6n / 14; + double c7a = (c7n - c7no) / 14; + double c9a = 1 / 40; + double c10a = (c10n - c10no) / 14; + double c11a = c11n / 14; + double c12a = (c12n - c12no) / 14; + // SO _stoichiometry.native(0, 1) = YSTO_aer - 1; _stoichiometry.native(0, 3) = 1 - 1 / YH_aer; _stoichiometry.native(0, 5) = -1 * (1 - fXI); _stoichiometry.native(0, 7) = -1; - _stoichiometry.native(0, 9 = -64/14 * 1/YA + 1; + _stoichiometry.native(0, 9) = -64/14 * 1/YA + 1; _stoichiometry.native(0, 10) = -1 * (1 - fXI); _stoichiometry.native(0, 12) = 1; @@ -244,7 +291,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(9, 6) = -1; // XSTO - _stoichiometry.native(10, 1) = YSO_aer; + _stoichiometry.native(10, 1) = YSTO_aer; _stoichiometry.native(10, 2) = YSTO_anox; _stoichiometry.native(10, 3) = -1 / YH_aer; _stoichiometry.native(10, 4) = -1 / YH_anox; From d07851e707595aff5ca77a9d212a3ad1fd7def43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 11:15:54 +0200 Subject: [PATCH 09/48] ASM3: differentiate p1-5 --- .../reaction/ActivatedSludgeModelThree.cpp | 129 ++++++++++++++---- 1 file changed, 100 insertions(+), 29 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index bf2245ad1..e0c367882 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -312,8 +312,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase //_paramHandler.configure(paramProvider, _stoichiometry.columns(), _nComp, _nBoundStates); _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); - // configure stoichiometric matrix from given parameters - //configureStoich(paramProvider,unitOpIdx, parTypeIdx) //registerCompRowMatrix(_parameters, unitOpIdx, parTypeIdx, "MM_STOICHIOMETRY_BULK", _stoichiometryBulk); todo @@ -416,7 +414,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase fluxes[11] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; // r13: Aeration - fluxes[12] = iO2 / V * 1000; + // TODO: is V in litres? + fluxes[12] = iO2 / V; // Add reaction terms to residual _stoichiometry.multiplyVector(static_cast(fluxes), factor, res); @@ -442,12 +441,63 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase void jacobianLiquidImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, double const* y, double factor, const RowIterator& jac, LinearBufferAllocator workSpace) const { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); - curJac = jac; - + RowIterator curJac = jac; + + double Kh20 = 10.2; + double T = 20; + double iO2 = 0; + double V = 1; + double k_sto20 = 13.68; + double KX = 1; + double KHO2 = 0.2; + double KHSS = 3; + double KHNO3 = 0.5; + double etaHNO3 = 0.5; + double KHNH4 = 0.01; + double KHALK = 0.1; + double KHSTO = 0.11; + double muH20 = 3; + double etaHend = 0.5; + double bH20 = 0.33; + double muAUT20 = 1.12; + double KNO2 = 0.5; + double KNNH4 = 0.7; + double KNALK = 0.5; + double bAUT20 = 0.18; + double etaNend = 0.5; + + // derived parameters + double ft04 = exp(-0.04 * (20.0 - static_cast(T))); + double ft07 = exp(-0.06952 * (20 - static_cast(T))); + double ft105 = exp(-0.105 * (20 - static_cast(T))); + double k_sto = static_cast(k_sto20) * ft07; + double muH = static_cast(muH20) * ft07; + double bH = static_cast(bH20) * ft07; + double muAUT = static_cast(muAUT20) * ft105; + double bAUT = static_cast(bAUT20) * ft105; + + double SO = y[0]; + double SS = y[1]; + double SNH = y[2]; + double SNO = y[3]; + double SN2 = y[4]; + double SALK = y[5]; + double SI = y[6]; // unused + double XI = y[7]; // unused + double XS = y[8]; + double XH = y[9]; + double XH_S = XH; // ASM3hC: XH_S = max(XH, 0.1) + double XSTO = y[10]; + double XA = y[11]; + double XMI = y[12]; // unused + // reaction 1: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - // dr1/XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX)^2 * XH; - // dr1/XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / XH_S^2 * XH; - // dr1/HX = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); + // dp1/XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX)^2 * XH; + double dp1_XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / ((XS/XH_S + KX) * (XS/XH_S + KX)) * XH; + // dp1/XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / XH_S^2 * XH; + double dp1_XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / (XH_S * XH_S) * XH; + // dp1/XH = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); + double dp1_XH = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); //curJac = jac; // curJac[8] = dr1/XS; @@ -457,10 +507,14 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // curjac++; // reaction2: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - // dr2/S0 = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / (SO + KHO2)^2 * XH; - // dr2/SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / (SS + KHSS)^2 * XH; - // dr2/SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; - // dr2/XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + // dp2/SO = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / (SO + KHO2)^2 * XH; + double dp2_SO = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; + // dp2/SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / (SS + KHSS)^2 * XH; + double dp2_SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; + // dp2/SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; + double dp2_SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + // dp2/XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + double dp2_XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); //curJac[-1] = dr2/S0; //curJac[0] = dr2/SS; @@ -470,27 +524,44 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // curJac++; //reaction3: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - // dr3/S0 = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2)^2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - // dr3/SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS)^2 * SNO / (SNO + KHNO3) * XH; - // dr3/SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; - // dr3/XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + // dp3/SO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2)^2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + double dp3_SO = k_sto * ft07 * etaHNO3 * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + // dp3/SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS)^2 * SNO / (SNO + KHNO3) * XH; + double dp3_SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / ((SS + KHSS) * (SS + KHSS)) * SNO / (SNO + KHNO3) * XH; + // dp3/SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; + double dp3_SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + // dp3/XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + double dp3_XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); //reaction4: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; - // dr4/S0 = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SO + KHO2)^2 * XH; - // dr4/SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SNH + KHNH4)^2 * XH; - // dr4/SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SALK + KHALK)^2 * XH; - // dr4/XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S + KHSTO)^2 * XH; - // dr4/XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / XSTO/XH_S^2 * XH; - // dr4/XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); + // dp4/SO = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SO + KHO2)^2 * XH; + double dp4_SO = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; + // dp4/SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SNH + KHNH4)^2 * XH; + double dp4_SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; + // dp4/SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SALK + KHALK)^2 * XH; + double dp4_SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; + // dp4/XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S + KHSTO)^2 * XH; + double dp4_XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; + // dp4/XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / XSTO/XH_S^2 * XH; + double dp4_XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S * XSTO/XH_S) * XH; + // dp4/XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); + double dp4_XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); //reaction5: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dr5/S0 = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO)^2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dr5/SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH)^2 * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dr5/SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK)^2 * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dr5/XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S + KHSTO)^2 * SNO / (KHNO3 + SNO) * XH; - // dr5/XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / XSTO/XH_S^2 * SNO / (KHNO3 + SNO) * XH; - // dr5/SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / (KHNO3 + SNO)^2 * XH; - // dr5/XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); + // dp5/SO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO)^2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + double dp5_SO = muH * ft07 * etaHNO3 * KHO2 / ((KHO2 + SO) * (KHO2 + SO)) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dp5/SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH)^2 * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + double dp5_SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / ((KHNH4 + SNH) * (KHNH4 + SNH)) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dp5/SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK)^2 * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + double dp5_SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / ((KHALK + SALK) * (KHALK + SALK)) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dp5/XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S + KHSTO)^2 * SNO / (KHNO3 + SNO) * XH; + double dp5_XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * SNO / (KHNO3 + SNO) * XH; + // dp5/XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / XSTO/XH_S^2 * SNO / (KHNO3 + SNO) * XH; + double dp5_XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S * XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + // dp5/SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / (KHNO3 + SNO)^2 * XH; + double dp5_SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; + // dp5/XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); + double dp5_XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); //reaction6: bH * SO / (SO + KHO2) * XH; From 8bb2dff0a60a5d1d0510464f567788a5286a7014 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 11:30:46 +0200 Subject: [PATCH 10/48] ASM3: differentiate r6-12 --- .../reaction/ActivatedSludgeModelThree.cpp | 88 +++++++------------ 1 file changed, 33 insertions(+), 55 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index e0c367882..6c44ec246 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -491,110 +491,88 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double XA = y[11]; double XMI = y[12]; // unused + /* + size_t idxSO = 0; + size_t idxSS = 1; + size_t idxSNH = 2; + size_t idxSNO = 3; + size_t idxSN2 = 4; + size_t idxSALK = 5; + size_t idxSI = 6; + size_t idxXI = 7; + size_t idxXS = 8; + size_t idxXH = 9; + size_t idxXSTO = 10; + size_t idxXA = 11; + size_t idxXMI = 12; + */ + // reaction 1: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - // dp1/XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX)^2 * XH; double dp1_XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / ((XS/XH_S + KX) * (XS/XH_S + KX)) * XH; - // dp1/XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / XH_S^2 * XH; double dp1_XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / (XH_S * XH_S) * XH; - // dp1/XH = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); double dp1_XH = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); - //curJac = jac; - // curJac[8] = dr1/XS; - // curJac[X] = dr1/XH_S; ?? - // curJac[9] = dr1/XH; - - // curjac++; - // reaction2: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - // dp2/SO = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / (SO + KHO2)^2 * XH; double dp2_SO = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; - // dp2/SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / (SS + KHSS)^2 * XH; double dp2_SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; - // dp2/SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; double dp2_SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - // dp2/XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); double dp2_XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); - //curJac[-1] = dr2/S0; - //curJac[0] = dr2/SS; - //curJac[1] = dr2/SNO; - //curJac[8] = dr2/XH; - - // curJac++; - //reaction3: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - // dp3/SO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2)^2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; double dp3_SO = k_sto * ft07 * etaHNO3 * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - // dp3/SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS)^2 * SNO / (SNO + KHNO3) * XH; double dp3_SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / ((SS + KHSS) * (SS + KHSS)) * SNO / (SNO + KHNO3) * XH; - // dp3/SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / (SNO + KHNO3)^2 * XH; double dp3_SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - // dp3/XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); double dp3_XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); //reaction4: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; - // dp4/SO = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SO + KHO2)^2 * XH; double dp4_SO = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; - // dp4/SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SNH + KHNH4)^2 * XH; double dp4_SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; - // dp4/SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / (SALK + KHALK)^2 * XH; double dp4_SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; - // dp4/XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S + KHSTO)^2 * XH; double dp4_XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; - // dp4/XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / XSTO/XH_S^2 * XH; double dp4_XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S * XSTO/XH_S) * XH; - // dp4/XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); double dp4_XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); //reaction5: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dp5/SO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO)^2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; double dp5_SO = muH * ft07 * etaHNO3 * KHO2 / ((KHO2 + SO) * (KHO2 + SO)) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dp5/SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH)^2 * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; double dp5_SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / ((KHNH4 + SNH) * (KHNH4 + SNH)) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dp5/SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK)^2 * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; double dp5_SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / ((KHALK + SALK) * (KHALK + SALK)) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dp5/XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S + KHSTO)^2 * SNO / (KHNO3 + SNO) * XH; double dp5_XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * SNO / (KHNO3 + SNO) * XH; - // dp5/XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / XSTO/XH_S^2 * SNO / (KHNO3 + SNO) * XH; double dp5_XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S * XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - // dp5/SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / (KHNO3 + SNO)^2 * XH; double dp5_SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; - // dp5/XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); double dp5_XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); //reaction6: bH * SO / (SO + KHO2) * XH; - // dr6/S0 = bH * ft07 * XH / (SO + KHO2)^2; - // dr6/XH = bH * ft07 * SO / (SO + KHO2); + double dr6_SO = bH * ft07 * XH / ((SO + KHO2) * (SO + KHO2)); + double dr6_XH = bH * ft07 * SO / (SO + KHO2); //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; - // dr7/S0 = bH * ft07 * etaHend * KHO2 / (SO + KHO2)^2 * SNO / (SNO + KHNO3) * XH; - // dr7/SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3)^2 * XH; - // dr7/XH = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + double dr7_SO = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XH; + double dr7_SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + double dr7_XH = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; //reaction8: bH * SO / (SO + KHO2) * XSTO; - // dr8/S0 = bH * ft07 * XSTO / (SO + KHO2)^2; - // dr8/XSTO = bH * ft07 * SO / (SO + KHO2); + double dr8_SO = bH * ft07 * XSTO / ((SO + KHO2) * (SO + KHO2)); + double dr8_XSTO = bH * ft07 * SO / (SO + KHO2); //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; - // dr9/S0 = bH * ft07 * etaHend * KHO2 / (SO + KHO2)^2 * SNO / (SNO + KHNO3) * XSTO; - // dr9/SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3)^2 * XSTO; + double dr9_SO = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XSTO; + double dr9_SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; - // dr10/S0 = muAUT * ft105 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / (SO + KNO2)^2; - // dr10/SNH = muAUT * ft105 * SO / (SO + KNO2) * SALK / (SALK + KNALK) / (SNH + KNNH4)^2 * XA; - // dr10/SALK = muAUT * ft105 * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / (SALK + KNALK)^2 * XA; + double dr10_SO = muAUT * ft105 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / ((SO + KNO2) * (SO + KNO2)); + double dr10_SNH = muAUT * ft105 * SO / (SO + KNO2) * SALK / (SALK + KNALK) / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; + double dr10_SALK = muAUT * ft105 * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / ((SALK + KNALK) * (SALK + KNALK)) * XA; //reaction11: bAUT * SO / (SO + KHO2) * XA; - // dr11/S0 = bAUT * ft105 * XA / (SO + KHO2)^2; - // dr11/XA = bAUT * ft105 * SO / (SO + KHO2); + double dr11_SO = bAUT * ft105 * XA / ((SO + KHO2) * (SO + KHO2)); + double dr11_XA = bAUT * ft105 * SO / (SO + KHO2); //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - // dr12/S0 = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2)^2 * XA; - // dr12/SNO = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3)^2 * XA; - // dr12/XA = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + double dr12_SO = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; + double dr12_SNO = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; + double dr12_XA = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; From acabe81efe3f83de9bc4975123f3ccd11e27c85e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 12:12:03 +0200 Subject: [PATCH 11/48] ASM3: compute jacobian --- .../reaction/ActivatedSludgeModelThree.cpp | 117 ++++++++++-------- 1 file changed, 65 insertions(+), 52 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 6c44ec246..8d93b4ade 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -491,7 +491,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double XA = y[11]; double XMI = y[12]; // unused - /* size_t idxSO = 0; size_t idxSS = 1; size_t idxSNH = 2; @@ -505,82 +504,96 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase size_t idxXSTO = 10; size_t idxXA = 11; size_t idxXMI = 12; - */ - // reaction 1: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - double dp1_XS = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / ((XS/XH_S + KX) * (XS/XH_S + KX)) * XH; - double dp1_XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / (XH_S * XH_S) * XH; - double dp1_XH = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); + double d[13][13] = {}; + //memset(d, 0, 13 * 13 * sizeof(double)); + // p1: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; + d[0][idxXS] = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / ((XS/XH_S + KX) * (XS/XH_S + KX)) * XH; + //double dp1_XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / (XH_S * XH_S) * XH; + d[0][idxXH] = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); + + /* + curJac = jac; + size_t curIdxXS = idxXS; + for (size_t i = 0; i < _stoichiometry.rows(); i++) { + curJac[idxXS] += _stoichiometry.native(i, 0) * dp1_XS; + curJac[idxXH] += _stoichiometry.native(i, 0) * dp1_XH; + curJac++; + curIdxXS--; + } + */ // reaction2: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - double dp2_SO = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; - double dp2_SS = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; - double dp2_SNO = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - double dp2_XH = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + d[1][idxSO] = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[1][idxSS] = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; + d[1][idxSNO] = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[1][idxXH] = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); //reaction3: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - double dp3_SO = k_sto * ft07 * etaHNO3 * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - double dp3_SS = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / ((SS + KHSS) * (SS + KHSS)) * SNO / (SNO + KHNO3) * XH; - double dp3_SNO = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - double dp3_XH = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + d[2][idxSO] = k_sto * ft07 * etaHNO3 * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + d[2][idxSS] = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / ((SS + KHSS) * (SS + KHSS)) * SNO / (SNO + KHNO3) * XH; + d[2][idxSNO] = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[2][idxXH] = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); //reaction4: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; - double dp4_SO = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; - double dp4_SNH = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; - double dp4_SALK = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; - double dp4_XSTO = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; - double dp4_XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S * XSTO/XH_S) * XH; - double dp4_XH = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); + d[3][idxSO] = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[3][idxSNH] = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; + d[3][idxSALK] = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; + d[3][idxXSTO] = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; + //double dp4_XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S * XSTO/XH_S) * XH; + d[3][idxXH] = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); //reaction5: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - double dp5_SO = muH * ft07 * etaHNO3 * KHO2 / ((KHO2 + SO) * (KHO2 + SO)) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - double dp5_SNH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / ((KHNH4 + SNH) * (KHNH4 + SNH)) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - double dp5_SALK = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / ((KHALK + SALK) * (KHALK + SALK)) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - double dp5_XSTO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * SNO / (KHNO3 + SNO) * XH; - double dp5_XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S * XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - double dp5_SNO = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; - double dp5_XH = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); + d[4][idxSO] = muH * ft07 * etaHNO3 * KHO2 / ((KHO2 + SO) * (KHO2 + SO)) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + d[4][idxSNH] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / ((KHNH4 + SNH) * (KHNH4 + SNH)) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + d[4][idxSALK] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / ((KHALK + SALK) * (KHALK + SALK)) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + d[4][idxXSTO] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * SNO / (KHNO3 + SNO) * XH; + //double dp5_XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S * XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + d[4][idxSNO] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; + d[4][idxXH] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); //reaction6: bH * SO / (SO + KHO2) * XH; - double dr6_SO = bH * ft07 * XH / ((SO + KHO2) * (SO + KHO2)); - double dr6_XH = bH * ft07 * SO / (SO + KHO2); + d[5][idxSO] = bH * ft07 * XH / ((SO + KHO2) * (SO + KHO2)); + d[5][idxXH] = bH * ft07 * SO / (SO + KHO2); //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; - double dr7_SO = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XH; - double dr7_SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - double dr7_XH = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + d[6][idxSO] = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XH; + d[6][idxSNO] = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[6][idxXH] = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; //reaction8: bH * SO / (SO + KHO2) * XSTO; - double dr8_SO = bH * ft07 * XSTO / ((SO + KHO2) * (SO + KHO2)); - double dr8_XSTO = bH * ft07 * SO / (SO + KHO2); + d[7][idxSO] = bH * ft07 * XSTO / ((SO + KHO2) * (SO + KHO2)); + d[7][idxXSTO] = bH * ft07 * SO / (SO + KHO2); //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; - double dr9_SO = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XSTO; - double dr9_SNO = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; + d[8][idxSO] = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XSTO; + d[8][idxSNO] = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; - double dr10_SO = muAUT * ft105 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / ((SO + KNO2) * (SO + KNO2)); - double dr10_SNH = muAUT * ft105 * SO / (SO + KNO2) * SALK / (SALK + KNALK) / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; - double dr10_SALK = muAUT * ft105 * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / ((SALK + KNALK) * (SALK + KNALK)) * XA; + d[9][idxSO] = muAUT * ft105 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / ((SO + KNO2) * (SO + KNO2)); + d[9][idxSNH] = muAUT * ft105 * SO / (SO + KNO2) * SALK / (SALK + KNALK) / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; + d[9][idxSALK] = muAUT * ft105 * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / ((SALK + KNALK) * (SALK + KNALK)) * XA; //reaction11: bAUT * SO / (SO + KHO2) * XA; - double dr11_SO = bAUT * ft105 * XA / ((SO + KHO2) * (SO + KHO2)); - double dr11_XA = bAUT * ft105 * SO / (SO + KHO2); + d[10][idxSO] = bAUT * ft105 * XA / ((SO + KHO2) * (SO + KHO2)); + d[10][idxXA] = bAUT * ft105 * SO / (SO + KHO2); //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - double dr12_SO = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; - double dr12_SNO = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; - double dr12_XA = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - - - - // StateType SI = y[6]; // unused - // StateType XI = y[7]; // unused - // StateType XMI = y[12]; // unused - - + d[11][idxSO] = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; + d[11][idxSNO] = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; + d[11][idxXA] = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + + for (size_t rIdx = 0; rIdx < 13; rIdx++) { + for (size_t compIdx = 0; compIdx < 13; compIdx++) { + // TODO consider configurable component indices + for (size_t i = 0; i < _stoichiometry.rows(); i++) { + curJac = &jac[i]; + curJac[compIdx] += _stoichiometry.native(i, rIdx) * d[rIdx][compIdx]; + } + } + } } From 5af98cfeeafea491b3a6853bc57b77d26d6af741 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 14 May 2025 15:39:25 +0200 Subject: [PATCH 12/48] add ad test --- test/ReactionModels.cpp | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test/ReactionModels.cpp b/test/ReactionModels.cpp index 6d8cbfdc0..1b26e5d0a 100644 --- a/test/ReactionModels.cpp +++ b/test/ReactionModels.cpp @@ -145,3 +145,17 @@ TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD with inhibition", "[M point, 1e-15, 1e-15 ); } +TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD]") +{ + const unsigned int nBound[] = {0}; + unsigned int ncomp = 13; + const double point[] = { 1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8 }; + cadet::test::reaction::testDynamicJacobianAD("ACTIVATES_SLUDGE_MODEL3", ncomp, nBound, + R"json({ + "ACSM3_p1": [1.0, 2.0, 0.4], + "ACSM3_p2": [-1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 3.0, 2.0, -1.0] + })json", + point, 1e-15, 1e-15 + ); +} + From 055af7fc55585e1f6dec363bd35b1cbbcf9f94ec Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 14 May 2025 16:02:32 +0200 Subject: [PATCH 13/48] get parameter from parameterprovider --- .../reaction/ActivatedSludgeModelThree.cpp | 121 +++++++++--------- 1 file changed, 62 insertions(+), 59 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 8d93b4ade..8b9d10612 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -172,21 +172,24 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.setAll(0); // parameter set ASM3hC - double iNSI = 0.03; - double iNSS = 0.1; - double iNXI = 0.12; - double iNXS = 0.0975; - double iNBM = 0.2; - double fSI = 0; - double YH_aer = 0.850793651; - double YH_anox = 0.698148148; - double YSTO_aer = 0.8966167647; - double YSTO_anox = 0.74375; - double fXI = 0.2; - double YA = 0.09; - double fiSS_BM_prod = 1; - double iVSS_BM = 1.956181534; - double iTSS_VSS_BM = 1.086956522; + double iNSI = paramProvider.getDouble("ASM_iNSI"); + double iNSS = paramProvider.getDouble("ASM_iNSS"); + double iNXI = paramProvider.getDouble("ASM_iNXI"); + double iNXS = paramProvider.getDouble("ASM_iNXS"); + double iNBM = paramProvider.getDouble("ASM_iNBM"); + + double fSI = paramProvider.getDouble("ASM_fSI"); + double fXI = paramProvider.getDouble("ASM_fXI"); + + double YH_aer = paramProvider.getDouble("ASM_YH_aer"); + double YH_anox = paramProvider.getDouble("ASM_YH_anox"); + double YSTO_aer = paramProvider.getDouble("ASM_YSTO_aer"); + double YSTO_anox = paramProvider.getDouble("ASM_YSTO_anox"); + double YA = paramProvider.getDouble("ASM_YA"); + + double fiSS_BM_prod = paramProvider.getDouble("ASM_fiSS_BM_prod"); + double iVSS_BM = paramProvider.getDouble("ASM_iVSS_BM"); + double iTSS_VSS_BM = paramProvider.getDouble("ASM_iTSS_VSS_BM"); // internal variables double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); @@ -328,28 +331,28 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase typedef typename DoubleActivePromoter::type flux_t; BufferedArray fluxes = workSpace.array(_stoichiometry.columns()); - ParamType Kh20 = 10.2; - ParamType T = 20; - ParamType iO2 = 0; - ParamType V = 1; - ParamType k_sto20 = 13.68; - ParamType KX = 1; - ParamType KHO2 = 0.2; - ParamType KHSS = 3; - ParamType KHNO3 = 0.5; - ParamType etaHNO3 = 0.5; - ParamType KHNH4 = 0.01; - ParamType KHALK = 0.1; - ParamType KHSTO = 0.11; - ParamType muH20 = 3; - ParamType etaHend = 0.5; - ParamType bH20 = 0.33; - ParamType muAUT20 = 1.12; - ParamType KNO2 = 0.5; - ParamType KNNH4 = 0.7; - ParamType KNALK = 0.5; - ParamType bAUT20 = 0.18; - ParamType etaNend = 0.5; + flux_t Kh20 = static_cast::type>(p->Kh20); + flux_t T = static_cast::type>(p->T); + flux_t iO2 = static_cast::type>(p->iO2); + flux_t V = static_cast::type>(p->V); + flux_t k_sto20 = static_cast::type>(p->k_sto20); + flux_t KX = static_cast::type>(p->KX); + flux_t KHO2 = static_cast::type>(p->KHO2); + flux_t KHSS = static_cast::type>(p->KHSS); + flux_t KHNO3 = static_cast::type>(p->KHNO3); + flux_t etaHNO3 = static_cast::type>(p->etaHNO3); + flux_t KHNH4 = static_cast::type>(p->KHNH4); + flux_t KHALK = static_cast::type>(p->KHALK); + flux_t KHSTO = static_cast::type>(p->KHSTO); + flux_t muH20 = static_cast::type>(p->muH20); + flux_t etaHend = static_cast::type>(p->etaHend); + flux_t bH20 = static_cast::type>(p->bH20); + flux_t muAUT20 = static_cast::type>(p->muAUT20); + flux_t KNO2 = static_cast::type>(p->KNO2); + flux_t KNNH4 = static_cast::type>(p->KNNH4); + flux_t KNALK = static_cast::type>(p->KNALK); + flux_t bAUT20 = static_cast::type>(p->bAUT20); + flux_t etaNend = static_cast::type>(p->etaNend); // derived parameters double ft04 = exp(-0.04 * (20.0 - static_cast(T))); @@ -443,28 +446,28 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); RowIterator curJac = jac; - double Kh20 = 10.2; - double T = 20; - double iO2 = 0; - double V = 1; - double k_sto20 = 13.68; - double KX = 1; - double KHO2 = 0.2; - double KHSS = 3; - double KHNO3 = 0.5; - double etaHNO3 = 0.5; - double KHNH4 = 0.01; - double KHALK = 0.1; - double KHSTO = 0.11; - double muH20 = 3; - double etaHend = 0.5; - double bH20 = 0.33; - double muAUT20 = 1.12; - double KNO2 = 0.5; - double KNNH4 = 0.7; - double KNALK = 0.5; - double bAUT20 = 0.18; - double etaNend = 0.5; + double Kh20 = static_cast(p->Kh20); + double T = static_cast(p->T); + double iO2 = static_cast(p->iO2); + double V = static_cast(p->V); + double k_sto20 = static_cast(p->k_sto20); + double KX = static_cast(p->KX); + double KHO2 = static_cast(p->KHO2); + double KHSS = static_cast(p->KHSS); + double KHNO3 = static_cast(p->KHNO3); + double etaHNO3 = static_cast(p->etaHNO3); + double KHNH4 = static_cast(p->KHNH4); + double KHALK = static_cast(p->KHALK); + double KHSTO = static_cast(p->KHSTO); + double muH20 = static_cast(p->muH20); + double etaHend = static_cast(p->etaHend); + double bH20 = static_cast(p->bH20); + double muAUT20 = static_cast(p->muAUT20); + double KNO2 = static_castp->KNO2); + double KNNH4 = static_cast(p->KNNH4); + double KNALK = static_cast(p->KNALK); + double bAUT20 = static_cast(p->bAUT20); + double etaNend = static_cast(p->etaNend); // derived parameters double ft04 = exp(-0.04 * (20.0 - static_cast(T))); From ff508ae37170477d81bc96836bc55f6824044c64 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 16:06:12 +0200 Subject: [PATCH 14/48] ASM3: set up params for codegen --- src/libcadet/CMakeLists.txt | 1 - .../reaction/ActivatedSludgeModelThree.cpp | 25 ++++++++++++++++--- 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/src/libcadet/CMakeLists.txt b/src/libcadet/CMakeLists.txt index db77fbdc9..80c074745 100644 --- a/src/libcadet/CMakeLists.txt +++ b/src/libcadet/CMakeLists.txt @@ -115,7 +115,6 @@ set(LIBCADET_REACTIONMODEL_SOURCES ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/MassActionLawReaction.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/MichaelisMentenReaction.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp - ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/CrystallizationReaction.cpp ) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 8b9d10612..654add3b5 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -32,9 +32,28 @@ "externalName": "ExtActivatedSludgeModelThreeParamHandler", "parameters": [ - { "type": "ScalarReactionDependentParameter", "varName": "vMax", "confName": "MM_VMAX"}, - { "type": "ScalarReactionDependentParameter", "varName": "kMM", "confName": "MM_KMM"}, - { "type": "ComponentDependentReactionDependentParameter", "varName": "kInhibit", "confName": "MM_KI"} + { "type": "ScalarReactionDependentParameter", "varName": "Kh20", "confName": "ASM3_KH_20"}, + { "type": "ScalarReactionDependentParameter", "varName": "T", "confName": "ASM3_T"}, + { "type": "ScalarReactionDependentParameter", "varName": "iO2", "confName": "ASM3_I_O2"}, + { "type": "ScalarReactionDependentParameter", "varName": "V", "confName": "ASM3_V"}, + { "type": "ScalarReactionDependentParameter", "varName": "k_sto20", "confName": "ASM3_K_STO_20"}, + { "type": "ScalarReactionDependentParameter", "varName": "KX", "confName": "ASM3_KX"}, + { "type": "ScalarReactionDependentParameter", "varName": "KHO2", "confName": "ASM3_KHO2"}, + { "type": "ScalarReactionDependentParameter", "varName": "KHSS", "confName": "ASM3_KHSS"}, + { "type": "ScalarReactionDependentParameter", "varName": "KHNO3", "confName": "ASM3_KHNO3"}, + { "type": "ScalarReactionDependentParameter", "varName": "etaHNO3", "confName": "ASM3_ETA_HNO3"}, + { "type": "ScalarReactionDependentParameter", "varName": "KHNH4", "confName": "ASM3_KHNH4"}, + { "type": "ScalarReactionDependentParameter", "varName": "KHALK", "confName": "ASM3_KHALK"}, + { "type": "ScalarReactionDependentParameter", "varName": "KHSTO", "confName": "ASM3_KHSTO"}, + { "type": "ScalarReactionDependentParameter", "varName": "muH20", "confName": "ASM3_MU_H_20"}, + { "type": "ScalarReactionDependentParameter", "varName": "etaHend", "confName": "ASM3_ETA_H_END"}, + { "type": "ScalarReactionDependentParameter", "varName": "bH20", "confName": "ASM3_B_H_20"}, + { "type": "ScalarReactionDependentParameter", "varName": "muAUT20", "confName": "ASM3_MU_AUT_20"}, + { "type": "ScalarReactionDependentParameter", "varName": "KNO2", "confName": "ASM3_KNO2"}, + { "type": "ScalarReactionDependentParameter", "varName": "KNNH4", "confName": "ASM3_KNNH4"}, + { "type": "ScalarReactionDependentParameter", "varName": "KNALK", "confName": "ASM3_KNALK"}, + { "type": "ScalarReactionDependentParameter", "varName": "bAUT20", "confName": "ASM3_B_AUT_20"}, + { "type": "ScalarReactionDependentParameter", "varName": "etaNend", "confName": "ASM3_ETA_N_END"}, ] } */ From 564fc8c13c195ee903af6bde81a4dddf24c0f0fe Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 14 May 2025 16:06:46 +0200 Subject: [PATCH 15/48] add ASM3 to factory --- src/libcadet/ReactionModelFactory.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libcadet/ReactionModelFactory.cpp b/src/libcadet/ReactionModelFactory.cpp index 63b041043..3976b4cb8 100644 --- a/src/libcadet/ReactionModelFactory.cpp +++ b/src/libcadet/ReactionModelFactory.cpp @@ -22,6 +22,7 @@ namespace cadet void registerMassActionLawReaction(std::unordered_map>& reactions); void registerDummyReaction(std::unordered_map>& reactions); void registerMichaelisMentenReaction(std::unordered_map>& reactions); + void registerActivatedSludgeModelThreeReaction(std::unordered_map>& reactions); void registerCrystallizationReaction(std::unordered_map>& reactions); } From b3660e725354f4062738ced4ce29102ffd692114 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 16:09:33 +0200 Subject: [PATCH 16/48] ASM3: fix codegen json --- src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 654add3b5..d3dd74449 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -53,7 +53,7 @@ { "type": "ScalarReactionDependentParameter", "varName": "KNNH4", "confName": "ASM3_KNNH4"}, { "type": "ScalarReactionDependentParameter", "varName": "KNALK", "confName": "ASM3_KNALK"}, { "type": "ScalarReactionDependentParameter", "varName": "bAUT20", "confName": "ASM3_B_AUT_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "etaNend", "confName": "ASM3_ETA_N_END"}, + { "type": "ScalarReactionDependentParameter", "varName": "etaNend", "confName": "ASM3_ETA_N_END"} ] } */ From f7046133fecf8618a242d9e9942d544a54fe98f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 16:18:32 +0200 Subject: [PATCH 17/48] ASM3: use scalar external params --- .../reaction/ActivatedSludgeModelThree.cpp | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index d3dd74449..1cee1ad49 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -32,28 +32,28 @@ "externalName": "ExtActivatedSludgeModelThreeParamHandler", "parameters": [ - { "type": "ScalarReactionDependentParameter", "varName": "Kh20", "confName": "ASM3_KH_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "T", "confName": "ASM3_T"}, - { "type": "ScalarReactionDependentParameter", "varName": "iO2", "confName": "ASM3_I_O2"}, - { "type": "ScalarReactionDependentParameter", "varName": "V", "confName": "ASM3_V"}, - { "type": "ScalarReactionDependentParameter", "varName": "k_sto20", "confName": "ASM3_K_STO_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "KX", "confName": "ASM3_KX"}, - { "type": "ScalarReactionDependentParameter", "varName": "KHO2", "confName": "ASM3_KHO2"}, - { "type": "ScalarReactionDependentParameter", "varName": "KHSS", "confName": "ASM3_KHSS"}, - { "type": "ScalarReactionDependentParameter", "varName": "KHNO3", "confName": "ASM3_KHNO3"}, - { "type": "ScalarReactionDependentParameter", "varName": "etaHNO3", "confName": "ASM3_ETA_HNO3"}, - { "type": "ScalarReactionDependentParameter", "varName": "KHNH4", "confName": "ASM3_KHNH4"}, - { "type": "ScalarReactionDependentParameter", "varName": "KHALK", "confName": "ASM3_KHALK"}, - { "type": "ScalarReactionDependentParameter", "varName": "KHSTO", "confName": "ASM3_KHSTO"}, - { "type": "ScalarReactionDependentParameter", "varName": "muH20", "confName": "ASM3_MU_H_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "etaHend", "confName": "ASM3_ETA_H_END"}, - { "type": "ScalarReactionDependentParameter", "varName": "bH20", "confName": "ASM3_B_H_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "muAUT20", "confName": "ASM3_MU_AUT_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "KNO2", "confName": "ASM3_KNO2"}, - { "type": "ScalarReactionDependentParameter", "varName": "KNNH4", "confName": "ASM3_KNNH4"}, - { "type": "ScalarReactionDependentParameter", "varName": "KNALK", "confName": "ASM3_KNALK"}, - { "type": "ScalarReactionDependentParameter", "varName": "bAUT20", "confName": "ASM3_B_AUT_20"}, - { "type": "ScalarReactionDependentParameter", "varName": "etaNend", "confName": "ASM3_ETA_N_END"} + { "type": "ScalarParameter", "varName": "Kh20", "confName": "ASM3_KH_20"}, + { "type": "ScalarParameter", "varName": "T", "confName": "ASM3_T"}, + { "type": "ScalarParameter", "varName": "iO2", "confName": "ASM3_I_O2"}, + { "type": "ScalarParameter", "varName": "V", "confName": "ASM3_V"}, + { "type": "ScalarParameter", "varName": "k_sto20", "confName": "ASM3_K_STO_20"}, + { "type": "ScalarParameter", "varName": "KX", "confName": "ASM3_KX"}, + { "type": "ScalarParameter", "varName": "KHO2", "confName": "ASM3_KHO2"}, + { "type": "ScalarParameter", "varName": "KHSS", "confName": "ASM3_KHSS"}, + { "type": "ScalarParameter", "varName": "KHNO3", "confName": "ASM3_KHNO3"}, + { "type": "ScalarParameter", "varName": "etaHNO3", "confName": "ASM3_ETA_HNO3"}, + { "type": "ScalarParameter", "varName": "KHNH4", "confName": "ASM3_KHNH4"}, + { "type": "ScalarParameter", "varName": "KHALK", "confName": "ASM3_KHALK"}, + { "type": "ScalarParameter", "varName": "KHSTO", "confName": "ASM3_KHSTO"}, + { "type": "ScalarParameter", "varName": "muH20", "confName": "ASM3_MU_H_20"}, + { "type": "ScalarParameter", "varName": "etaHend", "confName": "ASM3_ETA_H_END"}, + { "type": "ScalarParameter", "varName": "bH20", "confName": "ASM3_B_H_20"}, + { "type": "ScalarParameter", "varName": "muAUT20", "confName": "ASM3_MU_AUT_20"}, + { "type": "ScalarParameter", "varName": "KNO2", "confName": "ASM3_KNO2"}, + { "type": "ScalarParameter", "varName": "KNNH4", "confName": "ASM3_KNNH4"}, + { "type": "ScalarParameter", "varName": "KNALK", "confName": "ASM3_KNALK"}, + { "type": "ScalarParameter", "varName": "bAUT20", "confName": "ASM3_B_AUT_20"}, + { "type": "ScalarParameter", "varName": "etaNend", "confName": "ASM3_ETA_N_END"} ] } */ From c36c198b95c04136192a5f1c014f7fabb91a2931 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 14 May 2025 16:24:15 +0200 Subject: [PATCH 18/48] fix parameter --- .../reaction/ActivatedSludgeModelThree.cpp | 23 +++++++++---------- 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index d3dd74449..46f139232 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -134,7 +134,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { public: - ActivatedSludgeModelThreeBase() : _idxSubstrate(0) { } + ActivatedSludgeModelThreeBase() CADET_NOEXCEPT { } virtual ~ActivatedSludgeModelThreeBase() CADET_NOEXCEPT { } static const char* identifier() { return ParamHandler_t::identifier(); } @@ -161,7 +161,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const unsigned int nReactions = numElements / nComp; _stoichiometry.resize(nComp, nReactions); - _idxSubstrate = std::vector(nReactions, -1); } return true; @@ -469,7 +468,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double T = static_cast(p->T); double iO2 = static_cast(p->iO2); double V = static_cast(p->V); - double k_sto20 = static_cast(p->k_sto20); + double k_sto20 = static_cast(p->k_sto20); double KX = static_cast(p->KX); double KHO2 = static_cast(p->KHO2); double KHSS = static_cast(p->KHSS); @@ -482,21 +481,21 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double etaHend = static_cast(p->etaHend); double bH20 = static_cast(p->bH20); double muAUT20 = static_cast(p->muAUT20); - double KNO2 = static_castp->KNO2); + double KNO2 = static_cast(p->KNO2); double KNNH4 = static_cast(p->KNNH4); double KNALK = static_cast(p->KNALK); double bAUT20 = static_cast(p->bAUT20); double etaNend = static_cast(p->etaNend); // derived parameters - double ft04 = exp(-0.04 * (20.0 - static_cast(T))); - double ft07 = exp(-0.06952 * (20 - static_cast(T))); - double ft105 = exp(-0.105 * (20 - static_cast(T))); - double k_sto = static_cast(k_sto20) * ft07; - double muH = static_cast(muH20) * ft07; - double bH = static_cast(bH20) * ft07; - double muAUT = static_cast(muAUT20) * ft105; - double bAUT = static_cast(bAUT20) * ft105; + double ft04 = exp(-0.04 * (20.0 - static_cast(p->T))); + double ft07 = exp(-0.06952 * (20 - static_cast(p->T))); + double ft105 = exp(-0.105 * (20 - static_cast(p->T))); + double k_sto = static_cast(p->k_sto20) * ft07; + double muH = static_cast(p->muH20) * ft07; + double bH = static_cast(p->bH20) * ft07; + double muAUT = static_cast(p->muAUT20) * ft105; + double bAUT = static_cast(p->bAUT20) * ft105; double SO = y[0]; double SS = y[1]; From 49dd73cd9b6f9b557eb2c9ad342a7ac1177b0397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 16:40:11 +0200 Subject: [PATCH 19/48] ASM3: fix jacobian computation --- .../reaction/ActivatedSludgeModelThree.cpp | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 37146963d..49bc49300 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -462,7 +462,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase void jacobianLiquidImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, double const* y, double factor, const RowIterator& jac, LinearBufferAllocator workSpace) const { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); - RowIterator curJac = jac; double Kh20 = static_cast(p->Kh20); double T = static_cast(p->T); @@ -533,17 +532,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase //double dp1_XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / (XH_S * XH_S) * XH; d[0][idxXH] = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); - /* - curJac = jac; - size_t curIdxXS = idxXS; - for (size_t i = 0; i < _stoichiometry.rows(); i++) { - curJac[idxXS] += _stoichiometry.native(i, 0) * dp1_XS; - curJac[idxXH] += _stoichiometry.native(i, 0) * dp1_XH; - curJac++; - curIdxXS--; - } - */ - // reaction2: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; d[1][idxSO] = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; d[1][idxSS] = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; @@ -606,12 +594,13 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[11][idxSNO] = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; d[11][idxXA] = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - for (size_t rIdx = 0; rIdx < 13; rIdx++) { - for (size_t compIdx = 0; compIdx < 13; compIdx++) { - // TODO consider configurable component indices - for (size_t i = 0; i < _stoichiometry.rows(); i++) { - curJac = &jac[i]; - curJac[compIdx] += _stoichiometry.native(i, rIdx) * d[rIdx][compIdx]; + RowIterator curJac = jac; + // TODO consider configurable component indices + for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { + for (size_t rIdx = 0; rIdx < 13; rIdx++) { + const double colFactor = static_cast(_stoichiometry.native(row, rIdx)); + for (size_t compIdx = 0; compIdx < 13; compIdx++) { + curJac[compIdx] += colFactor * d[rIdx][compIdx]; } } } From e78df3451a305cd5b4976d458bba90d53f6df8e2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Wed, 14 May 2025 17:13:02 +0200 Subject: [PATCH 20/48] overload ScalarParameter methods for reaction model compatibility --- src/libcadet/model/Parameters.hpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/libcadet/model/Parameters.hpp b/src/libcadet/model/Parameters.hpp index 99bbad3bf..e934612ed 100644 --- a/src/libcadet/model/Parameters.hpp +++ b/src/libcadet/model/Parameters.hpp @@ -171,6 +171,7 @@ class ScalarParameter * @param [in] nBoundStates Array with number of bound states for each component */ inline void reserve(unsigned int numElem, unsigned int numSlices, unsigned int nComp, unsigned int const* nBoundStates) { } + inline void reserve(unsigned int numSlices, unsigned int nComp, unsigned int nBoundStates) { } inline const active& get() const CADET_NOEXCEPT { return *_p; } inline active& get() CADET_NOEXCEPT { return *_p; } @@ -694,6 +695,8 @@ class ExternalScalarParameter */ inline void reserve(unsigned int numElem, unsigned int numSlices, unsigned int nComp, unsigned int const* nBoundStates) { } + inline void reserve(unsigned int nReactions, unsigned int nComp, unsigned int nBoundStates) { } + /** * @brief Calculates a parameter in order to take the external profile into account * @param [out] result Stores the result of the paramter @@ -767,6 +770,7 @@ class ExternalScalarParameter * @return Amount of additional memory in bytes */ inline std::size_t additionalDynamicMemory(unsigned int nComp, unsigned int totalNumBoundStates, unsigned int const* nBoundStates) const CADET_NOEXCEPT { return 0; } + inline std::size_t additionalDynamicMemory(unsigned int nComp, unsigned int totalNumBoundStates, unsigned int nBoundStates) const CADET_NOEXCEPT { return 0; } /** * @brief Prepares the cache for the updated values From 507847c84e0455e374a5a6d651c029f9640769c3 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Thu, 15 May 2025 10:17:57 +0200 Subject: [PATCH 21/48] parameter declariation and jacobian fix --- .../reaction/ActivatedSludgeModelThree.cpp | 290 ++++++++---------- 1 file changed, 123 insertions(+), 167 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 49bc49300..799add818 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -32,11 +32,11 @@ "externalName": "ExtActivatedSludgeModelThreeParamHandler", "parameters": [ - { "type": "ScalarParameter", "varName": "Kh20", "confName": "ASM3_KH_20"}, + { "type": "ScalarParameter", "varName": "Kh20", "confName": "ASM3_KH20"}, { "type": "ScalarParameter", "varName": "T", "confName": "ASM3_T"}, - { "type": "ScalarParameter", "varName": "iO2", "confName": "ASM3_I_O2"}, + { "type": "ScalarParameter", "varName": "iO2", "confName": "ASM3_IO2"}, { "type": "ScalarParameter", "varName": "V", "confName": "ASM3_V"}, - { "type": "ScalarParameter", "varName": "k_sto20", "confName": "ASM3_K_STO_20"}, + { "type": "ScalarParameter", "varName": "k_sto20", "confName": "ASM3_KSTO20"}, { "type": "ScalarParameter", "varName": "KX", "confName": "ASM3_KX"}, { "type": "ScalarParameter", "varName": "KHO2", "confName": "ASM3_KHO2"}, { "type": "ScalarParameter", "varName": "KHSS", "confName": "ASM3_KHSS"}, @@ -45,21 +45,23 @@ { "type": "ScalarParameter", "varName": "KHNH4", "confName": "ASM3_KHNH4"}, { "type": "ScalarParameter", "varName": "KHALK", "confName": "ASM3_KHALK"}, { "type": "ScalarParameter", "varName": "KHSTO", "confName": "ASM3_KHSTO"}, - { "type": "ScalarParameter", "varName": "muH20", "confName": "ASM3_MU_H_20"}, - { "type": "ScalarParameter", "varName": "etaHend", "confName": "ASM3_ETA_H_END"}, - { "type": "ScalarParameter", "varName": "bH20", "confName": "ASM3_B_H_20"}, - { "type": "ScalarParameter", "varName": "muAUT20", "confName": "ASM3_MU_AUT_20"}, + { "type": "ScalarParameter", "varName": "muH20", "confName": "ASM3_MU_H20"}, + { "type": "ScalarParameter", "varName": "etaHend", "confName": "ASM3_ETAH_END"}, + { "type": "ScalarParameter", "varName": "bH20", "confName": "ASM3_BH20"}, + { "type": "ScalarParameter", "varName": "muAUT20", "confName": "ASM3_MU_AUT20"}, { "type": "ScalarParameter", "varName": "KNO2", "confName": "ASM3_KNO2"}, { "type": "ScalarParameter", "varName": "KNNH4", "confName": "ASM3_KNNH4"}, { "type": "ScalarParameter", "varName": "KNALK", "confName": "ASM3_KNALK"}, - { "type": "ScalarParameter", "varName": "bAUT20", "confName": "ASM3_B_AUT_20"}, - { "type": "ScalarParameter", "varName": "etaNend", "confName": "ASM3_ETA_N_END"} + { "type": "ScalarParameter", "varName": "bAUT20", "confName": "ASM3_BAUT20"}, + { "type": "ScalarParameter", "varName": "etaNend", "confName": "ASM3_ETAN_END"} ] } */ /* Parameter description ------------------------ + + */ @@ -151,18 +153,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase virtual bool configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) { DynamicReactionModelBase::configureModelDiscretization(paramProvider, nComp, nBound, boundOffset); - - if (paramProvider.exists("MM_STOICHIOMETRY_BULK")) - { - const std::size_t numElements = paramProvider.numElements("MM_STOICHIOMETRY_BULK"); - if (numElements % nComp != 0) - throw InvalidParameterException("Size of field MM_STOICHIOMETRY_BULK must be a positive multiple of NCOMP (" + std::to_string(nComp) + ")"); - - const unsigned int nReactions = numElements / nComp; - - _stoichiometry.resize(nComp, nReactions); - } - + _stoichiometry.resize(nComp, 13); return true; } @@ -186,28 +177,32 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx) { + _paramHandler.configure(paramProvider, _stoichiometry.columns(), _nComp, _nBoundStates); + _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); + + _stoichiometry.resize(_nComp, 13); _stoichiometry.setAll(0); - // parameter set ASM3hC - double iNSI = paramProvider.getDouble("ASM_iNSI"); - double iNSS = paramProvider.getDouble("ASM_iNSS"); - double iNXI = paramProvider.getDouble("ASM_iNXI"); - double iNXS = paramProvider.getDouble("ASM_iNXS"); - double iNBM = paramProvider.getDouble("ASM_iNBM"); - - double fSI = paramProvider.getDouble("ASM_fSI"); - double fXI = paramProvider.getDouble("ASM_fXI"); - - double YH_aer = paramProvider.getDouble("ASM_YH_aer"); - double YH_anox = paramProvider.getDouble("ASM_YH_anox"); - double YSTO_aer = paramProvider.getDouble("ASM_YSTO_aer"); - double YSTO_anox = paramProvider.getDouble("ASM_YSTO_anox"); - double YA = paramProvider.getDouble("ASM_YA"); - - double fiSS_BM_prod = paramProvider.getDouble("ASM_fiSS_BM_prod"); - double iVSS_BM = paramProvider.getDouble("ASM_iVSS_BM"); - double iTSS_VSS_BM = paramProvider.getDouble("ASM_iTSS_VSS_BM"); + // parameter set ASM3h + double iNSI = paramProvider.getDouble("ASM3_INSI"); + double iNSS = paramProvider.getDouble("ASM3_INSS"); + double iNXI = paramProvider.getDouble("ASM3_INXI"); + double iNXS = paramProvider.getDouble("ASM3_INXS"); + double iNBM = paramProvider.getDouble("ASM3_INBM"); + + double fSI = paramProvider.getDouble("ASM3_FSI"); + double fXI = paramProvider.getDouble("ASM3_FXI"); + + double YH_aer = paramProvider.getDouble("ASM3_YH_AER"); + double YH_anox = paramProvider.getDouble("ASM3_YH_ANOX"); + double YSTO_aer = paramProvider.getDouble("ASM3_YSTO_AER"); + double YSTO_anox = paramProvider.getDouble("ASM3_YSTO_ANOX"); + double YA = paramProvider.getDouble("ASM3_YA"); + + double fiSS_BM_prod = paramProvider.getDouble("ASM3_FISS_BM_PROD"); + double iVSS_BM = paramProvider.getDouble("ASM3_IVSS_BM"); + double iTSS_VSS_BM = paramProvider.getDouble("ASM3_ITSS_VSS_BM"); // internal variables double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); @@ -330,8 +325,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(12, 10) = fXMI_BM; _stoichiometry.native(12, 11) = fXMI_BM; - //_paramHandler.configure(paramProvider, _stoichiometry.columns(), _nComp, _nBoundStates); - _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); + + //registerCompRowMatrix(_parameters, unitOpIdx, parTypeIdx, "MM_STOICHIOMETRY_BULK", _stoichiometryBulk); todo @@ -349,28 +344,28 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase typedef typename DoubleActivePromoter::type flux_t; BufferedArray fluxes = workSpace.array(_stoichiometry.columns()); - flux_t Kh20 = static_cast::type>(p->Kh20); - flux_t T = static_cast::type>(p->T); - flux_t iO2 = static_cast::type>(p->iO2); - flux_t V = static_cast::type>(p->V); - flux_t k_sto20 = static_cast::type>(p->k_sto20); - flux_t KX = static_cast::type>(p->KX); - flux_t KHO2 = static_cast::type>(p->KHO2); - flux_t KHSS = static_cast::type>(p->KHSS); - flux_t KHNO3 = static_cast::type>(p->KHNO3); - flux_t etaHNO3 = static_cast::type>(p->etaHNO3); - flux_t KHNH4 = static_cast::type>(p->KHNH4); - flux_t KHALK = static_cast::type>(p->KHALK); - flux_t KHSTO = static_cast::type>(p->KHSTO); - flux_t muH20 = static_cast::type>(p->muH20); - flux_t etaHend = static_cast::type>(p->etaHend); - flux_t bH20 = static_cast::type>(p->bH20); - flux_t muAUT20 = static_cast::type>(p->muAUT20); - flux_t KNO2 = static_cast::type>(p->KNO2); - flux_t KNNH4 = static_cast::type>(p->KNNH4); - flux_t KNALK = static_cast::type>(p->KNALK); - flux_t bAUT20 = static_cast::type>(p->bAUT20); - flux_t etaNend = static_cast::type>(p->etaNend); + flux_t Kh20 = static_cast::type>(p->Kh20); + flux_t T = static_cast::type>(p->T); + flux_t iO2 = static_cast::type>(p->iO2); + flux_t V = static_cast::type>(p->V); + flux_t k_sto20 = static_cast::type>(p->k_sto20); + flux_t KX = static_cast::type>(p->KX); + flux_t KHO2 = static_cast::type>(p->KHO2); + flux_t KHSS = static_cast::type>(p->KHSS); + flux_t KHNO3 = static_cast::type>(p->KHNO3); + flux_t etaHNO3 = static_cast::type>(p->etaHNO3); + flux_t KHNH4 = static_cast::type>(p->KHNH4); + flux_t KHALK = static_cast::type>(p->KHALK); + flux_t KHSTO = static_cast::type>(p->KHSTO); + flux_t muH20 = static_cast::type>(p->muH20); + flux_t etaHend = static_cast::type>(p->etaHend); + flux_t bH20 = static_cast::type>(p->bH20); + flux_t muAUT20 = static_cast::type>(p->muAUT20); + flux_t KNO2 = static_cast::type>(p->KNO2); + flux_t KNNH4 = static_cast::type>(p->KNNH4); + flux_t KNALK = static_cast::type>(p->KNALK); + flux_t bAUT20 = static_cast::type>(p->bAUT20); + flux_t etaNend = static_cast::type>(p->etaNend); // derived parameters double ft04 = exp(-0.04 * (20.0 - static_cast(T))); @@ -463,28 +458,28 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); - double Kh20 = static_cast(p->Kh20); - double T = static_cast(p->T); - double iO2 = static_cast(p->iO2); - double V = static_cast(p->V); - double k_sto20 = static_cast(p->k_sto20); - double KX = static_cast(p->KX); - double KHO2 = static_cast(p->KHO2); - double KHSS = static_cast(p->KHSS); - double KHNO3 = static_cast(p->KHNO3); - double etaHNO3 = static_cast(p->etaHNO3); - double KHNH4 = static_cast(p->KHNH4); - double KHALK = static_cast(p->KHALK); - double KHSTO = static_cast(p->KHSTO); - double muH20 = static_cast(p->muH20); - double etaHend = static_cast(p->etaHend); - double bH20 = static_cast(p->bH20); - double muAUT20 = static_cast(p->muAUT20); - double KNO2 = static_cast(p->KNO2); - double KNNH4 = static_cast(p->KNNH4); - double KNALK = static_cast(p->KNALK); - double bAUT20 = static_cast(p->bAUT20); - double etaNend = static_cast(p->etaNend); + const double Kh20 = static_cast(p->Kh20); + const double T = static_cast(p->T); + const double iO2 = static_cast(p->iO2); + const double V = static_cast(p->V); + const double k_sto20 = static_cast(p->k_sto20); + const double KX = static_cast(p->KX); + const double KHO2 = static_cast(p->KHO2); + const double KHSS = static_cast(p->KHSS); + const double KHNO3 = static_cast(p->KHNO3); + const double etaHNO3 = static_cast(p->etaHNO3); + const double KHNH4 = static_cast(p->KHNH4); + const double KHALK = static_cast(p->KHALK); + const double KHSTO = static_cast(p->KHSTO); + const double muH20 = static_cast(p->muH20); + const double etaHend = static_cast(p->etaHend); + const double bH20 = static_cast(p->bH20); + const double muAUT20 = static_cast(p->muAUT20); + const double KNO2 = static_cast(p->KNO2); + const double KNNH4 = static_cast(p->KNNH4); + const double KNALK = static_cast(p->KNALK); + const double bAUT20 = static_cast(p->bAUT20); + const double etaNend = static_cast(p->etaNend); // derived parameters double ft04 = exp(-0.04 * (20.0 - static_cast(p->T))); @@ -512,87 +507,48 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double XMI = y[12]; // unused size_t idxSO = 0; - size_t idxSS = 1; - size_t idxSNH = 2; - size_t idxSNO = 3; - size_t idxSN2 = 4; - size_t idxSALK = 5; - size_t idxSI = 6; - size_t idxXI = 7; - size_t idxXS = 8; - size_t idxXH = 9; - size_t idxXSTO = 10; - size_t idxXA = 11; - size_t idxXMI = 12; - - double d[13][13] = {}; - //memset(d, 0, 13 * 13 * sizeof(double)); - // p1: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - d[0][idxXS] = Kh20 * ft04 * XH / (XS/XH_S + KX) - Kh20 * ft04 * XS/XH_S / ((XS/XH_S + KX) * (XS/XH_S + KX)) * XH; - //double dp1_XH_S = -Kh20 * ft04 * XS / (XS/XH_S + KX) / (XH_S * XH_S) * XH; - d[0][idxXH] = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX); - - // reaction2: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - d[1][idxSO] = k_sto * ft07 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[1][idxSS] = k_sto * ft07 * SO / (SO + KHO2) * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; - d[1][idxSNO] = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[1][idxXH] = k_sto * ft07 * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); - - //reaction3: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - d[2][idxSO] = k_sto * ft07 * etaHNO3 * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - d[2][idxSS] = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / ((SS + KHSS) * (SS + KHSS)) * SNO / (SNO + KHNO3) * XH; - d[2][idxSNO] = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[2][idxXH] = k_sto * ft07 * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); - - //reaction4: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; - d[3][idxSO] = muH * ft07 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[3][idxSNH] = muH * ft07 * SO / (SO + KHO2) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; - d[3][idxSALK] = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; - d[3][idxXSTO] = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; - //double dp4_XH_S = -muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) / (XSTO/XH_S * XSTO/XH_S) * XH; - d[3][idxXH] = muH * ft07 * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); - - //reaction5: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxSO] = muH * ft07 * etaHNO3 * KHO2 / ((KHO2 + SO) * (KHO2 + SO)) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxSNH] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / ((KHNH4 + SNH) * (KHNH4 + SNH)) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxSALK] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / ((KHALK + SALK) * (KHALK + SALK)) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxXSTO] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * SNO / (KHNO3 + SNO) * XH; - //double dp5_XH_S = -muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) / (XSTO/XH_S * XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxSNO] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; - d[4][idxXH] = muH * ft07 * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO); - - - //reaction6: bH * SO / (SO + KHO2) * XH; - d[5][idxSO] = bH * ft07 * XH / ((SO + KHO2) * (SO + KHO2)); - d[5][idxXH] = bH * ft07 * SO / (SO + KHO2); - - //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; - d[6][idxSO] = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XH; - d[6][idxSNO] = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[6][idxXH] = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; - - //reaction8: bH * SO / (SO + KHO2) * XSTO; - d[7][idxSO] = bH * ft07 * XSTO / ((SO + KHO2) * (SO + KHO2)); - d[7][idxXSTO] = bH * ft07 * SO / (SO + KHO2); - - //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; - d[8][idxSO] = bH * ft07 * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XSTO; - d[8][idxSNO] = bH * ft07 * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; - - //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; - d[9][idxSO] = muAUT * ft105 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / ((SO + KNO2) * (SO + KNO2)); - d[9][idxSNH] = muAUT * ft105 * SO / (SO + KNO2) * SALK / (SALK + KNALK) / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; - d[9][idxSALK] = muAUT * ft105 * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / ((SALK + KNALK) * (SALK + KNALK)) * XA; - - //reaction11: bAUT * SO / (SO + KHO2) * XA; - d[10][idxSO] = bAUT * ft105 * XA / ((SO + KHO2) * (SO + KHO2)); - d[10][idxXA] = bAUT * ft105 * SO / (SO + KHO2); - - - //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - d[11][idxSO] = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; - d[11][idxSNO] = bAUT * ft105 * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; - d[11][idxXA] = bAUT * ft105 * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + size_t idxSS = 1; + size_t idxSNH = 2; + size_t idxSNO = 3; + size_t idxSN2 = 4; + size_t idxSALK = 5; + size_t idxSI = 6; + size_t idxXI = 7; + size_t idxXS = 8; + size_t idxXH = 9; + size_t idxXSTO = 10; + size_t idxXA = 11; + size_t idxXMI = 12; + + double d[13][13] = {}; + + // p1: Hydrolysis: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; + d[0][idxXS] = Kh20 * ft04 * XH_S / ((XS + XH_S * KX) * (XS + XH_S * KX)) * XH * XH_S; + d[0][idxXH] = Kh20 * ft04 * XS / (XS + XH_S * KX); + + // p2: Aerobic storage of SS: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; + d[1][idxSO] = k_sto * KHO2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[1][idxSS] = k_sto * SO / (SO + KHO2) * KHSS * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; + d[1][idxSNO] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[1][idxXH] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + + // p3: Anoxic storage of SS: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + d[2][idxSO] = -k_sto * etaHNO3 * KHO2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[2][idxSS] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * KHSS * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; + d[2][idxSNO] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[2][idxXH] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); + + // p4: Aerobic growth: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; + d[3][idxSO] = muH * KHO2 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[3][idxSNH] = muH * SO / (SO + KHO2) * KHNH4 * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; + d[3][idxSALK] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * KHALK * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; + d[3][idxXSTO] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * KHSTO / XH_S / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; + d[3][idxXH] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); + + // p5: Anoxic growth: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + d[4][idxSO] = -muH * etaHNO3 * KHO2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH / ((KHO2 + SO) * (KHO2 + SO)); + d[4][idxSNH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * KHNH4 * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH / ((KHNH4 + SNH) * (KHNH4 + SNH)); + d[4][idxSALK] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * KHALK * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH / ((KHALK + SALK) * (KHALK + SALK)); RowIterator curJac = jac; // TODO consider configurable component indices From 0750d08005724e0d9a900855953ac2470b29f121 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Thu, 15 May 2025 11:15:03 +0200 Subject: [PATCH 22/48] fic internal variable determination numbered value int-> double --- .../reaction/ActivatedSludgeModelThree.cpp | 40 +++++++++---------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 799add818..54c134b40 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -216,30 +216,30 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double c10n = -1 / YA - iNBM; double c11n = -fXI * iNXI + iNBM; double c12n = c11n; - double c3no = (YSTO_anox - 1) / (40 / 14); - double c5no = (1 - 1/YH_anox) / (40 / 14); - double c7no = (fXI - 1) / (40 / 14); - double c9no = -14 / 40; + double c3no = (YSTO_anox - 1.0) / (40.0 / 14.0); + double c5no = (1.0 - 1 / YH_anox) / (40.0 / 14.0); + double c7no = (fXI - 1) / (40.0 / 14.0); + double c9no = -14.0 / 40.0; double c10no = 1 / YA; double c12no = c7no; - double c1a = c1n / 14; - double c2a = c2n / 14; - double c3a = (c3n - c3no) / 14; - double c4a = c4n / 14; - double c5a = (c5n - c5no) / 14; - double c6a = c6n / 14; - double c7a = (c7n - c7no) / 14; - double c9a = 1 / 40; - double c10a = (c10n - c10no) / 14; - double c11a = c11n / 14; - double c12a = (c12n - c12no) / 14; + double c1a = c1n / 14.0; + double c2a = c2n / 14.0; + double c3a = (c3n - c3no) / 14.0; + double c4a = c4n / 14.0; + double c5a = (c5n - c5no) / 14.0; + double c6a = c6n / 14.0; + double c7a = (c7n - c7no) / 14.0; + double c9a = 1 / 40.0; + double c10a = (c10n - c10no) / 14.0; + double c11a = c11n / 14.0; + double c12a = (c12n - c12no) / 14.0; // SO _stoichiometry.native(0, 1) = YSTO_aer - 1; _stoichiometry.native(0, 3) = 1 - 1 / YH_aer; _stoichiometry.native(0, 5) = -1 * (1 - fXI); _stoichiometry.native(0, 7) = -1; - _stoichiometry.native(0, 9) = -64/14 * 1/YA + 1; + _stoichiometry.native(0, 9) = -64.0/14.0 * 1/YA + 1; _stoichiometry.native(0, 10) = -1 * (1 - fXI); _stoichiometry.native(0, 12) = 1; @@ -369,8 +369,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // derived parameters double ft04 = exp(-0.04 * (20.0 - static_cast(T))); - double ft07 = exp(-0.06952 * (20 - static_cast(T))); - double ft105 = exp(-0.105 * (20 - static_cast(T))); + double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); + double ft105 = exp(-0.105 * (20.0 - static_cast(T))); double k_sto = static_cast(k_sto20) * ft07; double muH = static_cast(muH20) * ft07; double bH = static_cast(bH20) * ft07; @@ -483,8 +483,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // derived parameters double ft04 = exp(-0.04 * (20.0 - static_cast(p->T))); - double ft07 = exp(-0.06952 * (20 - static_cast(p->T))); - double ft105 = exp(-0.105 * (20 - static_cast(p->T))); + double ft07 = exp(-0.06952 * (20.0 - static_cast(p->T))); + double ft105 = exp(-0.105 * (20.0 - static_cast(p->T))); double k_sto = static_cast(p->k_sto20) * ft07; double muH = static_cast(p->muH20) * ft07; double bH = static_cast(p->bH20) * ft07; From 785dd6c1ca1bb63999be95b13daf89e17b5e5105 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Sat, 17 May 2025 12:14:11 +0200 Subject: [PATCH 23/48] ASM3: start documenting parameters --- .../reaction/activated_sludge_model.rst | 184 ++++++++++++++++++ doc/interface/reaction/index.rst | 1 + .../reaction/activated_sludge_model.rst | 11 ++ doc/modelling/reaction/index.rst | 4 + 4 files changed, 200 insertions(+) create mode 100644 doc/interface/reaction/activated_sludge_model.rst create mode 100644 doc/modelling/reaction/activated_sludge_model.rst diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst new file mode 100644 index 000000000..2e87c2e1a --- /dev/null +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -0,0 +1,184 @@ +.. _activated_sludge_model_config: + +Activated Sludge Model (ASM3h) +~~~~~~~~~~~~~~~~~~~~~~~~~ + +**Group /input/model/unit_XXX/reaction - REACTION_MODEL = ACTIVATED_SLUDGE_MODEL3** + +For information on model equations, refer to :ref:`activated_sludge_model`. + +``ASM3_KH20`` + + Hydrolysis rate constant :math:`k_H` at 20 °C. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_T`` + + Temperature :math:`T`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_IO2`` + + Aeration rate :math:`iO_2`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\ge 0` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_V`` + + Aeration volume :math:`V`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\gt 0` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KSTO20`` + + Saturation constant :math:`K_{STO}` at 20 °C. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\gt 0` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KX`` + + Hydrolysis saturation constant :math:`KX`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KHO2`` + + :math:`SO_2` saturation constant :math:`KHO_2`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KHSS`` + + Substrate :math:`SS` saturation constant :math:`KHSS`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KHNO3`` + + :math:`SNO_X` saturation constant :math:`KHNO_3`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_ETA_HNO3`` + + Anoxic reduction factor :math:`\eta_{HNO_3}`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KHNH4`` + + Ammonium saturation constant :math:`KHNH_4`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KHALK`` + + :math:`XH` alkalinity saturation constant :math:`KH_{ALK}`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KHSTO`` + + :math:`X_{STO}` saturation constant :math:`KH_{STO}`. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_MU_H20`` + + Heterotrophic max. growth rate :math:`\mu_{H}` at 20 °C. + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_ETAH_END`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_BH20`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_MU_AUT20`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KNO2`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KNNH4`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_KNALK`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_BAUT20`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== + +``ASM3_ETAN_END`` + + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ======================================================== diff --git a/doc/interface/reaction/index.rst b/doc/interface/reaction/index.rst index a1b6e14a7..e8e955750 100644 --- a/doc/interface/reaction/index.rst +++ b/doc/interface/reaction/index.rst @@ -8,6 +8,7 @@ Reaction models mass_action_law michaelis_menten_kinetics + activated_sludge_model Externally dependent reaction models ------------------------------------ diff --git a/doc/modelling/reaction/activated_sludge_model.rst b/doc/modelling/reaction/activated_sludge_model.rst new file mode 100644 index 000000000..09b380544 --- /dev/null +++ b/doc/modelling/reaction/activated_sludge_model.rst @@ -0,0 +1,11 @@ +.. _activated_sludge_model: + +Activated Sludge Model (ASM3h) +------------------------- + +Activated Sludge Models are a group of reaction models published by the International Water Association (IWA) +and mostly used for modelling biological processes in wastewater treatment plants. + +The ASM3h model implemented in CADET is a variant of the ASM3 model and documented in the SIMBA# software suite. + +For more information on model parameters required to define in CADET file format, see :ref:`activated_sludge_model_config`. diff --git a/doc/modelling/reaction/index.rst b/doc/modelling/reaction/index.rst index 02330bd4a..b9e3d6517 100644 --- a/doc/modelling/reaction/index.rst +++ b/doc/modelling/reaction/index.rst @@ -16,6 +16,10 @@ Historically, a chromatography system is modeled as a reaction system without co - :ref:`thomas_model` - :ref:`rate_constant_distribution_theory` +More specialized models are also implemented: + + - :ref:`activated_sludge_model` + .. _dependence-on-external-function_react: Dependence on external function From ee896db4e752bf28f7826c2ca7587e84090c5a4f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Mon, 19 May 2025 15:51:45 +0200 Subject: [PATCH 24/48] ASM3: document remaining params from SIMBA# --- .../reaction/activated_sludge_model.rst | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst index 2e87c2e1a..3a3633866 100644 --- a/doc/interface/reaction/activated_sludge_model.rst +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -25,7 +25,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_IO2`` - Aeration rate :math:`iO_2`. + Aeration xygen input :math:`iO_2`. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\ge 0` **Length:** 1 @@ -41,7 +41,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_KSTO20`` - Saturation constant :math:`K_{STO}` at 20 °C. + Maximum storage rate :math:`K_{STO}` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\gt 0` **Length:** 1 @@ -49,7 +49,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_KX`` - Hydrolysis saturation constant :math:`KX`. + Saturation/inhibition coefficient for particulate COD :math:`KX`. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -57,7 +57,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_KHO2`` - :math:`SO_2` saturation constant :math:`KHO_2`. + Saturation/inhibition coefficient for oxygen, heterotrophic growth :math:`KHO_2`. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -121,7 +121,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_ETAH_END`` - + Reduction factor :math:`\eta_{H_end}` for :math:`b_H` under anoxic conditions. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -129,7 +129,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_BH20`` - + Rate constant for lysis and decay :math:`b_H` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -137,7 +137,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_MU_AUT20`` - + Maximum growth rate of :math:`XAUT`, :math:`\mu_{AUT}` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -145,7 +145,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_KNO2`` - + Saturation coefficient :math:`K_{NO_2}` for oxygen, autotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -153,7 +153,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_KNNH4`` - + Saturation coefficient :math:`K_{NNH_4}` for ammonium (substrate), autotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -161,7 +161,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_KNALK`` - + Saturation coefficient :math:`K_{NALK}` for alkalinity, autotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -169,7 +169,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_BAUT20`` - + Decay rate :math:`b_{AUT}` of :math:`XAUT` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -177,7 +177,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_ETAN_END`` - + Reduction factor :math:`\eta_{N_end}` for :math:`b_{AUT}` under anoxic conditions. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 From ce294c2f8d5ec88e12a0d0e99f6464c77350ec1a Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Mon, 19 May 2025 15:54:17 +0200 Subject: [PATCH 25/48] fix lost of jacobi equaitons --- .../reaction/ActivatedSludgeModelThree.cpp | 212 ++++++++++++------ 1 file changed, 142 insertions(+), 70 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 54c134b40..54e5fb152 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -239,7 +239,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(0, 3) = 1 - 1 / YH_aer; _stoichiometry.native(0, 5) = -1 * (1 - fXI); _stoichiometry.native(0, 7) = -1; - _stoichiometry.native(0, 9) = -64.0/14.0 * 1/YA + 1; + _stoichiometry.native(0, 9) = -(64.0/14.0) * 1/YA + 1; _stoichiometry.native(0, 10) = -1 * (1 - fXI); _stoichiometry.native(0, 12) = 1; @@ -387,26 +387,32 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // StateType XI = y[7]; // unused StateType XS = y[8]; StateType XH = y[9]; - StateType XH_S = XH; // ASM3hC: XH_S = max(XH, 0.1) StateType XSTO = y[10]; StateType XA = y[11]; // StateType XMI = y[12]; // unused // p1: Hydrolysis of organic structures - fluxes[0] = Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; + fluxes[0] = Kh20 * ft04 * (XS/XH) / ((XS/XH) + KX) * XH; + if (XH < 0.1) + fluxes[0] = Kh20 * ft04 * (XS/0.1) / ((XS/0.1) + KX) * XH; + // p2: Aerobic storage of SS - fluxes[1] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + fluxes[1] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; // p3: Anoxic storage of SS fluxes[2] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; // p4: Aerobic growth of heterotrophic biomass (XH) - fluxes[3] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; + fluxes[3] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * ((XSTO/XH)) / (((XSTO/XH)) + KHSTO) * XH; + if (XH < 0.1) + fluxes[3] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * XH; // p5: Anoxic growth of heterotrophic biomass (XH, denitrification) - fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; + fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / XH) * 1 / (KHSTO + (XSTO / XH)) * SNO / (KHNO3 + SNO) * XH; + if (XH < 0.1) + fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) * 1 / (KHSTO + (XSTO/0.1)) * SNO / (KHNO3 + SNO) * XH; // r6: Aerobic endogenous respiration of heterotroph microorganisms (XH) fluxes[5] = bH * SO / (SO + KHO2) * XH; @@ -458,38 +464,39 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); - const double Kh20 = static_cast(p->Kh20); - const double T = static_cast(p->T); - const double iO2 = static_cast(p->iO2); - const double V = static_cast(p->V); - const double k_sto20 = static_cast(p->k_sto20); - const double KX = static_cast(p->KX); - const double KHO2 = static_cast(p->KHO2); - const double KHSS = static_cast(p->KHSS); - const double KHNO3 = static_cast(p->KHNO3); - const double etaHNO3 = static_cast(p->etaHNO3); - const double KHNH4 = static_cast(p->KHNH4); - const double KHALK = static_cast(p->KHALK); - const double KHSTO = static_cast(p->KHSTO); - const double muH20 = static_cast(p->muH20); - const double etaHend = static_cast(p->etaHend); - const double bH20 = static_cast(p->bH20); - const double muAUT20 = static_cast(p->muAUT20); - const double KNO2 = static_cast(p->KNO2); - const double KNNH4 = static_cast(p->KNNH4); - const double KNALK = static_cast(p->KNALK); - const double bAUT20 = static_cast(p->bAUT20); - const double etaNend = static_cast(p->etaNend); + + const double Kh20 = static_cast(p->Kh20); + const double T = static_cast(p->T); + const double iO2 = static_cast(p->iO2); + const double V = static_cast(p->V); + const double k_sto20 = static_cast(p->k_sto20); + const double KX = static_cast(p->KX); + const double KHO2 = static_cast(p->KHO2); + const double KHSS = static_cast(p->KHSS); + const double KHNO3 = static_cast(p->KHNO3); + const double etaHNO3 = static_cast(p->etaHNO3); + const double KHNH4 = static_cast(p->KHNH4); + const double KHALK = static_cast(p->KHALK); + const double KHSTO = static_cast(p->KHSTO); + const double muH20 = static_cast(p->muH20); + const double etaHend = static_cast(p->etaHend); + const double bH20 = static_cast(p->bH20); + const double muAUT20 = static_cast(p->muAUT20); + const double KNO2 = static_cast(p->KNO2); + const double KNNH4 = static_cast(p->KNNH4); + const double KNALK = static_cast(p->KNALK); + const double bAUT20 = static_cast(p->bAUT20); + const double etaNend = static_cast(p->etaNend); // derived parameters - double ft04 = exp(-0.04 * (20.0 - static_cast(p->T))); - double ft07 = exp(-0.06952 * (20.0 - static_cast(p->T))); - double ft105 = exp(-0.105 * (20.0 - static_cast(p->T))); - double k_sto = static_cast(p->k_sto20) * ft07; - double muH = static_cast(p->muH20) * ft07; - double bH = static_cast(p->bH20) * ft07; - double muAUT = static_cast(p->muAUT20) * ft105; - double bAUT = static_cast(p->bAUT20) * ft105; + const double ft04 = exp(-0.04 * (20.0 - static_cast(p->T))); + const double ft07 = exp(-0.06952 * (20.0 - static_cast(p->T))); + const double ft105 = exp(-0.105 * (20.0 - static_cast(p->T))); + const double k_sto = static_cast(p->k_sto20) * ft07; + const double muH = static_cast(p->muH20) * ft07; + const double bH = static_cast(p->bH20) * ft07; + const double muAUT = static_cast(p->muAUT20) * ft105; + const double bAUT = static_cast(p->bAUT20) * ft105; double SO = y[0]; double SS = y[1]; @@ -501,35 +508,41 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double XI = y[7]; // unused double XS = y[8]; double XH = y[9]; - double XH_S = XH; // ASM3hC: XH_S = max(XH, 0.1) double XSTO = y[10]; double XA = y[11]; double XMI = y[12]; // unused - - size_t idxSO = 0; - size_t idxSS = 1; - size_t idxSNH = 2; - size_t idxSNO = 3; - size_t idxSN2 = 4; - size_t idxSALK = 5; - size_t idxSI = 6; - size_t idxXI = 7; - size_t idxXS = 8; - size_t idxXH = 9; - size_t idxXSTO = 10; - size_t idxXA = 11; - size_t idxXMI = 12; - - double d[13][13] = {}; + //XH_S = max(XH, 0.1) + + const size_t idxSO = 0; + const size_t idxSS = 1; + const size_t idxSNH = 2; + const size_t idxSNO = 3; + const size_t idxSN2 = 4; + const size_t idxSALK = 5; + const size_t idxSI = 6; + const size_t idxXI = 7; + const size_t idxXS = 8; + const size_t idxXH = 9; + const size_t idxXSTO = 10; + const size_t idxXA = 11; + const size_t idxXMI = 12; + + const double d[13][13] = {}; // p1: Hydrolysis: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - d[0][idxXS] = Kh20 * ft04 * XH_S / ((XS + XH_S * KX) * (XS + XH_S * KX)) * XH * XH_S; - d[0][idxXH] = Kh20 * ft04 * XS / (XS + XH_S * KX); + // Jacobian: + d[0][idxXS] = Kh20 * ft04 * XH / ((XS + XH * KX) * (XS + XH * KX)) * XH; + d[0][idxXH] = Kh20 * ft04 * (XS/XH) / ((XS/XH) + KX); + if (XH < 0.1) + { + d[0][idxXH] = Kh20 * ft04 * (XS / 0.1) / ((XS / 0.1) + KX); + d[0][idxXS] = Kh20 * ft04 * 0.1 / ((XS + 0.1 * KX) * (XS + 0.1 * KX)) * XH; + } + // p2: Aerobic storage of SS: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; d[1][idxSO] = k_sto * KHO2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; d[1][idxSS] = k_sto * SO / (SO + KHO2) * KHSS * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; - d[1][idxSNO] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; d[1][idxXH] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); // p3: Anoxic storage of SS: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; @@ -538,25 +551,84 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[2][idxSNO] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; d[2][idxXH] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); - // p4: Aerobic growth: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) * XH; - d[3][idxSO] = muH * KHO2 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[3][idxSNH] = muH * SO / (SO + KHO2) * KHNH4 * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; - d[3][idxSALK] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * KHALK * XSTO/XH_S / (XSTO/XH_S + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; - d[3][idxXSTO] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * KHSTO / XH_S / ((XSTO/XH_S + KHSTO) * (XSTO/XH_S + KHSTO)) * XH; - d[3][idxXH] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * XSTO/XH_S / (XSTO/XH_S + KHSTO); + // p4: Aerobic growth: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO/XH_S) / ((XSTO/XH_S) + KHSTO) * XH; + d[3][idxSO] = muH * KHO2 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO/XH) / (((XSTO/XH)) + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[3][idxSNH] = muH * SO / (SO + KHO2) * KHNH4 * SALK / (SALK + KHALK) * (XSTO/XH) / ((XSTO/XH) + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; + d[3][idxSALK] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * KHALK * (XSTO/XH) / ((XSTO/XH) + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; + d[3][idxXSTO] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (KHSTO / XH) / (((XSTO/XH) + KHSTO) * ((XSTO/XH) + KHSTO)) * XH; + d[3][idxXH] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * ((XSTO/XH) / ((XSTO/XH) + KHSTO) - XSTO*KHSTO / (XH*XH * ((XSTO/XH) + KHSTO) * ((XSTO/XH) + KHSTO))); + + if (XH < 0.1) + { + d[3][idxSO] = muH * KHO2 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; + d[3][idxSNH] = muH * SO / (SO + KHO2) * KHNH4 * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; + d[3][idxSALK] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * KHALK * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; + d[3][idxXSTO] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (KHSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * (XSTO / 0.1 + KHSTO)) * XH; + d[3][idxXH] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO); + } - // p5: Anoxic growth: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxSO] = -muH * etaHNO3 * KHO2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH / ((KHO2 + SO) * (KHO2 + SO)); - d[4][idxSNH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * KHNH4 * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH / ((KHNH4 + SNH) * (KHNH4 + SNH)); - d[4][idxSALK] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * KHALK * XSTO / XH_S * 1 / (KHSTO + XSTO/XH_S) * SNO / (KHNO3 + SNO) * XH / ((KHALK + SALK) * (KHALK + SALK)); + // p5: Anoxic growth: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + (XSTO/XH)_S) * SNO / (KHNO3 + SNO) * XH; + d[4][idxSO] = -muH * etaHNO3 * KHO2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / XH) * 1 / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH / ((KHO2 + SO) * (KHO2 + SO)); + d[4][idxSNH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * KHNH4 * SALK / (KHALK + SALK) * (XSTO / XH) * 1 / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH / ((KHNH4 + SNH) * (KHNH4 + SNH)); + d[4][idxSALK] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * KHALK * (XSTO / XH) * 1 / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH / ((KHALK + SALK) * (KHALK + SALK)); + d[4][idxSNO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / XH) / (KHSTO + (XSTO/XH)) * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; + d[4][idxXSTO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * 1 / XH / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH; + d[4][idxXH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * SNO / (KHNO3 + SNO) * ((XSTO/XH) / (KHSTO + (XSTO/XH)) - XSTO*KHSTO / (XH*XH * (KHSTO + (XSTO/XH)) * (KHSTO + (XSTO/XH)))); + + if (XH < 0.1) + { + d[4][idxSO] = -muH * etaHNO3 * KHO2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) * 1 / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH / ((KHO2 + SO) * (KHO2 + SO)); + d[4][idxSNH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * KHNH4 * SALK / (KHALK + SALK) * (XSTO / 0.1) * 1 / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH / ((KHNH4 + SNH) * (KHNH4 + SNH)); + d[4][idxSALK] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * KHALK * (XSTO / 0.1) * 1 / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH / ((KHALK + SALK) * (KHALK + SALK)); + d[4][idxSNO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; + d[4][idxXSTO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (1 / 0.1) / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH; + d[4][idxXH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO); + } + + //reaction6: bH * SO / (SO + KHO2) * XH; + d[5][idxSO] = bH * KHO2 * XH / ((SO + KHO2) * (SO + KHO2)); + d[5][idxXH] = bH * SO / (SO + KHO2); + + //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + d[6][idxSO] = bH * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XH; + d[6][idxSNO] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[6][idxXH] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + + //reaction8: bH * SO / (SO + KHO2) * XSTO; + d[7][idxSO] = bH * KHO2 * XSTO / ((SO + KHO2) * (SO + KHO2)); + d[7][idxXSTO] = bH * SO / (SO + KHO2); + + //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; + d[8][idxSO] = bH * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XSTO; + d[8][idxSNO] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; + d[8][idxXSTO] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3); + + //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; + d[9][idxSO] = muAUT * KNO2 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / ((SO + KNO2) * (SO + KNO2)); + d[9][idxSALK] = muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / ((SALK + KNALK) * (SALK + KNALK)) * XA; + d[9][idxXA] = muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK); + d[9][idxSNH] = muAUT * SO / (SO + KNO2) * SALK / (SALK + KNALK) / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; + + //reaction11: bAUT * SO / (SO + KHO2) * XA; + d[10][idxSO] = bAUT * XA / ((SO + KHO2) * (SO + KHO2)); + d[10][idxXA] = bAUT * SO / (SO + KHO2); + + //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + d[11][idxSO] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; + d[11][idxSNO] = bAUT * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; + d[11][idxXA] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2); + + RowIterator curJac = jac; // TODO consider configurable component indices - for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { - for (size_t rIdx = 0; rIdx < 13; rIdx++) { + for (size_t rIdx = 0; rIdx < 13; rIdx++) { + RowIterator curJac = jac; + for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { + rIdx = 10; const double colFactor = static_cast(_stoichiometry.native(row, rIdx)); - for (size_t compIdx = 0; compIdx < 13; compIdx++) { - curJac[compIdx] += colFactor * d[rIdx][compIdx]; + for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) { + curJac[compIdx - static_cast(row)] += colFactor * d[rIdx][compIdx]; } } } From 3a34839417969433295a88caa0a00c9dd7ba25d7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Mon, 19 May 2025 15:55:33 +0200 Subject: [PATCH 26/48] ASM3: fix doc typos --- doc/interface/reaction/activated_sludge_model.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst index 3a3633866..f6078fb2e 100644 --- a/doc/interface/reaction/activated_sludge_model.rst +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -25,7 +25,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_IO2`` - Aeration xygen input :math:`iO_2`. + Aeration oxygen input :math:`iO_2`. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\ge 0` **Length:** 1 @@ -121,7 +121,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_ETAH_END`` - Reduction factor :math:`\eta_{H_end}` for :math:`b_H` under anoxic conditions. + Reduction factor :math:`\eta_{H_{end}}` for :math:`b_H` under anoxic conditions. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 @@ -177,7 +177,7 @@ For information on model equations, refer to :ref:`activated_sludge_model`. ``ASM3_ETAN_END`` - Reduction factor :math:`\eta_{N_end}` for :math:`b_{AUT}` under anoxic conditions. + Reduction factor :math:`\eta_{N_{end}}` for :math:`b_{AUT}` under anoxic conditions. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 From 1773de22c9da0c69a20e4d57af5b6ccb11c09839 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Mon, 19 May 2025 16:44:39 +0200 Subject: [PATCH 27/48] ASM3: use ScalarParameter fix from #412 --- src/libcadet/model/Parameters.hpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/libcadet/model/Parameters.hpp b/src/libcadet/model/Parameters.hpp index 4e4994a5b..1d72da30f 100644 --- a/src/libcadet/model/Parameters.hpp +++ b/src/libcadet/model/Parameters.hpp @@ -171,7 +171,6 @@ class ScalarParameter * @param [in] nBoundStates Array with number of bound states for each component */ inline void reserve(unsigned int numElem, unsigned int numSlices, unsigned int nComp, unsigned int const* nBoundStates) { } - inline void reserve(unsigned int numSlices, unsigned int nComp, unsigned int nBoundStates) { } /** * @brief Reserves space in the storage of the parameter @@ -703,8 +702,6 @@ class ExternalScalarParameter */ inline void reserve(unsigned int numElem, unsigned int numSlices, unsigned int nComp, unsigned int const* nBoundStates) { } - inline void reserve(unsigned int nReactions, unsigned int nComp, unsigned int nBoundStates) { } - /** * @brief Reserves space in the storage of the parameters * @param [in] nReactions Number of reactions @@ -786,7 +783,6 @@ class ExternalScalarParameter * @return Amount of additional memory in bytes */ inline std::size_t additionalDynamicMemory(unsigned int nComp, unsigned int totalNumBoundStates, unsigned int const* nBoundStates) const CADET_NOEXCEPT { return 0; } - inline std::size_t additionalDynamicMemory(unsigned int nComp, unsigned int totalNumBoundStates, unsigned int nBoundStates) const CADET_NOEXCEPT { return 0; } /** * @brief Returns the amount of additional memory (usually dynamically allocated by containers) for storing the final parameters From 69a968f9cb6c33e345ef53200a86ee43bea9c53e Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Tue, 20 May 2025 14:28:23 +0200 Subject: [PATCH 28/48] add jacobian test and fix jacobian --- .../reaction/ActivatedSludgeModelThree.cpp | 518 +++++++++++------- test/ReactionModelTests.cpp | 37 +- test/ReactionModels.cpp | 53 +- 3 files changed, 379 insertions(+), 229 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 54e5fb152..0a66847fe 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -185,54 +185,54 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.setAll(0); // parameter set ASM3h - double iNSI = paramProvider.getDouble("ASM3_INSI"); - double iNSS = paramProvider.getDouble("ASM3_INSS"); - double iNXI = paramProvider.getDouble("ASM3_INXI"); - double iNXS = paramProvider.getDouble("ASM3_INXS"); - double iNBM = paramProvider.getDouble("ASM3_INBM"); + const double iNSI = paramProvider.getDouble("ASM3_INSI"); + const double iNSS = paramProvider.getDouble("ASM3_INSS"); + const double iNXI = paramProvider.getDouble("ASM3_INXI"); + const double iNXS = paramProvider.getDouble("ASM3_INXS"); + const double iNBM = paramProvider.getDouble("ASM3_INBM"); - double fSI = paramProvider.getDouble("ASM3_FSI"); - double fXI = paramProvider.getDouble("ASM3_FXI"); + const double fSI = paramProvider.getDouble("ASM3_FSI"); + const double fXI = paramProvider.getDouble("ASM3_FXI"); - double YH_aer = paramProvider.getDouble("ASM3_YH_AER"); - double YH_anox = paramProvider.getDouble("ASM3_YH_ANOX"); - double YSTO_aer = paramProvider.getDouble("ASM3_YSTO_AER"); - double YSTO_anox = paramProvider.getDouble("ASM3_YSTO_ANOX"); - double YA = paramProvider.getDouble("ASM3_YA"); + const double YH_aer = paramProvider.getDouble("ASM3_YH_AER"); + const double YH_anox = paramProvider.getDouble("ASM3_YH_ANOX"); + const double YSTO_aer = paramProvider.getDouble("ASM3_YSTO_AER"); + const double YSTO_anox = paramProvider.getDouble("ASM3_YSTO_ANOX"); + const double YA = paramProvider.getDouble("ASM3_YA"); - double fiSS_BM_prod = paramProvider.getDouble("ASM3_FISS_BM_PROD"); - double iVSS_BM = paramProvider.getDouble("ASM3_IVSS_BM"); - double iTSS_VSS_BM = paramProvider.getDouble("ASM3_ITSS_VSS_BM"); + const double fiSS_BM_prod = paramProvider.getDouble("ASM3_FISS_BM_PROD"); + const double iVSS_BM = paramProvider.getDouble("ASM3_IVSS_BM"); + const double iTSS_VSS_BM = paramProvider.getDouble("ASM3_ITSS_VSS_BM"); // internal variables - double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); - double c1n = iNXS - iNSI * fSI - (1 - fSI) * iNSS; - double c2n = iNSS; - double c3n = iNSS; - double c4n = -iNBM; - double c5n = c4n; - double c6n = -fXI * iNXI + iNBM; - double c7n = c6n; - double c10n = -1 / YA - iNBM; - double c11n = -fXI * iNXI + iNBM; - double c12n = c11n; - double c3no = (YSTO_anox - 1.0) / (40.0 / 14.0); - double c5no = (1.0 - 1 / YH_anox) / (40.0 / 14.0); - double c7no = (fXI - 1) / (40.0 / 14.0); - double c9no = -14.0 / 40.0; - double c10no = 1 / YA; - double c12no = c7no; - double c1a = c1n / 14.0; - double c2a = c2n / 14.0; - double c3a = (c3n - c3no) / 14.0; - double c4a = c4n / 14.0; - double c5a = (c5n - c5no) / 14.0; - double c6a = c6n / 14.0; - double c7a = (c7n - c7no) / 14.0; - double c9a = 1 / 40.0; - double c10a = (c10n - c10no) / 14.0; - double c11a = c11n / 14.0; - double c12a = (c12n - c12no) / 14.0; + const double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); + const double c1n = iNXS - iNSI * fSI - (1 - fSI) * iNSS; + const double c2n = iNSS; + const double c3n = iNSS; + const double c4n = -iNBM; + const double c5n = c4n; + const double c6n = -fXI * iNXI + iNBM; + const double c7n = c6n; + const double c10n = -1 / YA - iNBM; + const double c11n = -fXI * iNXI + iNBM; + const double c12n = c11n; + const double c3no = (YSTO_anox - 1.0) / (40.0 / 14.0); + const double c5no = (1.0 - 1 / YH_anox) / (40.0 / 14.0); + const double c7no = (fXI - 1) / (40.0 / 14.0); + const double c9no = -14.0 / 40.0; + const double c10no = 1 / YA; + const double c12no = c7no; + const double c1a = c1n / 14.0; + const double c2a = c2n / 14.0; + const double c3a = (c3n - c3no) / 14.0; + const double c4a = c4n / 14.0; + const double c5a = (c5n - c5no) / 14.0; + const double c6a = c6n / 14.0; + const double c7a = (c7n - c7no) / 14.0; + const double c9a = 1 / 40.0; + const double c10a = (c10n - c10no) / 14.0; + const double c11a = c11n / 14.0; + const double c12a = (c12n - c12no) / 14.0; // SO _stoichiometry.native(0, 1) = YSTO_aer - 1; @@ -344,38 +344,38 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase typedef typename DoubleActivePromoter::type flux_t; BufferedArray fluxes = workSpace.array(_stoichiometry.columns()); - flux_t Kh20 = static_cast::type>(p->Kh20); - flux_t T = static_cast::type>(p->T); - flux_t iO2 = static_cast::type>(p->iO2); - flux_t V = static_cast::type>(p->V); - flux_t k_sto20 = static_cast::type>(p->k_sto20); - flux_t KX = static_cast::type>(p->KX); - flux_t KHO2 = static_cast::type>(p->KHO2); - flux_t KHSS = static_cast::type>(p->KHSS); - flux_t KHNO3 = static_cast::type>(p->KHNO3); - flux_t etaHNO3 = static_cast::type>(p->etaHNO3); - flux_t KHNH4 = static_cast::type>(p->KHNH4); - flux_t KHALK = static_cast::type>(p->KHALK); - flux_t KHSTO = static_cast::type>(p->KHSTO); - flux_t muH20 = static_cast::type>(p->muH20); - flux_t etaHend = static_cast::type>(p->etaHend); - flux_t bH20 = static_cast::type>(p->bH20); - flux_t muAUT20 = static_cast::type>(p->muAUT20); - flux_t KNO2 = static_cast::type>(p->KNO2); - flux_t KNNH4 = static_cast::type>(p->KNNH4); - flux_t KNALK = static_cast::type>(p->KNALK); - flux_t bAUT20 = static_cast::type>(p->bAUT20); - flux_t etaNend = static_cast::type>(p->etaNend); + const flux_t Kh20 = static_cast::type>(p->Kh20); + const flux_t T = static_cast::type>(p->T); + const flux_t iO2 = static_cast::type>(p->iO2); + const flux_t V = static_cast::type>(p->V); + const flux_t k_sto20 = static_cast::type>(p->k_sto20); + const flux_t KX = static_cast::type>(p->KX); + const flux_t KHO2 = static_cast::type>(p->KHO2); + const flux_t KHSS = static_cast::type>(p->KHSS); + const flux_t KHNO3 = static_cast::type>(p->KHNO3); + const flux_t etaHNO3 = static_cast::type>(p->etaHNO3); + const flux_t KHNH4 = static_cast::type>(p->KHNH4); + const flux_t KHALK = static_cast::type>(p->KHALK); + const flux_t KHSTO = static_cast::type>(p->KHSTO); + const flux_t muH20 = static_cast::type>(p->muH20); + const flux_t etaHend = static_cast::type>(p->etaHend); + const flux_t bH20 = static_cast::type>(p->bH20); + const flux_t muAUT20 = static_cast::type>(p->muAUT20); + const flux_t KNO2 = static_cast::type>(p->KNO2); + const flux_t KNNH4 = static_cast::type>(p->KNNH4); + const flux_t KNALK = static_cast::type>(p->KNALK); + const flux_t bAUT20 = static_cast::type>(p->bAUT20); + const flux_t etaNend = static_cast::type>(p->etaNend); // derived parameters - double ft04 = exp(-0.04 * (20.0 - static_cast(T))); - double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); - double ft105 = exp(-0.105 * (20.0 - static_cast(T))); - double k_sto = static_cast(k_sto20) * ft07; - double muH = static_cast(muH20) * ft07; - double bH = static_cast(bH20) * ft07; - double muAUT = static_cast(muAUT20) * ft105; - double bAUT = static_cast(bAUT20) * ft105; + const double ft04 = exp(-0.04 * (20.0 - static_cast(T))); + const double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); + const double ft105 = exp(-0.105 * (20.0 - static_cast(T))); + const double k_sto = static_cast(k_sto20) * ft07; + const double muH = static_cast(muH20) * ft07; + const double bH = static_cast(bH20) * ft07; + const double muAUT = static_cast(muAUT20) * ft105; + const double bAUT = static_cast(bAUT20) * ft105; StateType SO = y[0]; StateType SS = y[1]; @@ -399,7 +399,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // p2: Aerobic storage of SS - fluxes[1] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; + fluxes[1] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; // p3: Anoxic storage of SS fluxes[2] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; @@ -464,39 +464,39 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); + const double Kh20 = static_cast(p->Kh20); + const double T = static_cast(p->T); + const double iO2 = static_cast(p->iO2); + const double V = static_cast(p->V); + const double k_sto20 = static_cast(p->k_sto20); + const double KX = static_cast(p->KX); + const double KHO2 = static_cast(p->KHO2); + const double KHSS = static_cast(p->KHSS); + const double KHNO3 = static_cast(p->KHNO3); + const double etaHNO3 = static_cast(p->etaHNO3); + const double KHNH4 = static_cast(p->KHNH4); + const double KHALK = static_cast(p->KHALK); + const double KHSTO = static_cast(p->KHSTO); + const double muH20 = static_cast(p->muH20); + const double etaHend = static_cast(p->etaHend); + const double bH20 = static_cast(p->bH20); + const double muAUT20 = static_cast(p->muAUT20); + const double KNO2 = static_cast(p->KNO2); + const double KNNH4 = static_cast(p->KNNH4); + const double KNALK = static_cast(p->KNALK); + const double bAUT20 = static_cast(p->bAUT20); + const double etaNend = static_cast(p->etaNend); - const double Kh20 = static_cast(p->Kh20); - const double T = static_cast(p->T); - const double iO2 = static_cast(p->iO2); - const double V = static_cast(p->V); - const double k_sto20 = static_cast(p->k_sto20); - const double KX = static_cast(p->KX); - const double KHO2 = static_cast(p->KHO2); - const double KHSS = static_cast(p->KHSS); - const double KHNO3 = static_cast(p->KHNO3); - const double etaHNO3 = static_cast(p->etaHNO3); - const double KHNH4 = static_cast(p->KHNH4); - const double KHALK = static_cast(p->KHALK); - const double KHSTO = static_cast(p->KHSTO); - const double muH20 = static_cast(p->muH20); - const double etaHend = static_cast(p->etaHend); - const double bH20 = static_cast(p->bH20); - const double muAUT20 = static_cast(p->muAUT20); - const double KNO2 = static_cast(p->KNO2); - const double KNNH4 = static_cast(p->KNNH4); - const double KNALK = static_cast(p->KNALK); - const double bAUT20 = static_cast(p->bAUT20); - const double etaNend = static_cast(p->etaNend); - // derived parameters - const double ft04 = exp(-0.04 * (20.0 - static_cast(p->T))); - const double ft07 = exp(-0.06952 * (20.0 - static_cast(p->T))); - const double ft105 = exp(-0.105 * (20.0 - static_cast(p->T))); - const double k_sto = static_cast(p->k_sto20) * ft07; - const double muH = static_cast(p->muH20) * ft07; - const double bH = static_cast(p->bH20) * ft07; - const double muAUT = static_cast(p->muAUT20) * ft105; - const double bAUT = static_cast(p->bAUT20) * ft105; + // derived parameters + const double ft04 = exp(-0.04 * (20.0 - static_cast(T))); + const double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); + const double ft105 = exp(-0.105 * (20.0 - static_cast(T))); + const double k_sto = static_cast(k_sto20) * ft07; + const double muH = static_cast(muH20) * ft07; + const double bH = static_cast(bH20) * ft07; + const double muAUT = static_cast(muAUT20) * ft105; + const double bAUT = static_cast(bAUT20) * ft105; double SO = y[0]; double SS = y[1]; @@ -514,120 +514,268 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase //XH_S = max(XH, 0.1) const size_t idxSO = 0; - const size_t idxSS = 1; - const size_t idxSNH = 2; - const size_t idxSNO = 3; - const size_t idxSN2 = 4; - const size_t idxSALK = 5; - const size_t idxSI = 6; - const size_t idxXI = 7; - const size_t idxXS = 8; - const size_t idxXH = 9; - const size_t idxXSTO = 10; - const size_t idxXA = 11; - const size_t idxXMI = 12; - - const double d[13][13] = {}; + const size_t idxSS = 1; + const size_t idxSNH = 2; + const size_t idxSNO = 3; + const size_t idxSN2 = 4; + const size_t idxSALK = 5; + const size_t idxSI = 6; + const size_t idxXI = 7; + const size_t idxXS = 8; + const size_t idxXH = 9; + const size_t idxXSTO = 10; + const size_t idxXA = 11; + const size_t idxXMI = 12; + + double d[13][13] = {}; - // p1: Hydrolysis: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; - // Jacobian: - d[0][idxXS] = Kh20 * ft04 * XH / ((XS + XH * KX) * (XS + XH * KX)) * XH; - d[0][idxXH] = Kh20 * ft04 * (XS/XH) / ((XS/XH) + KX); - if (XH < 0.1) + // p1: Hydrolysis: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; +// Jacobian: + d[0][idxXS] = Kh20 * ft04 + * XH / ((XS + XH * KX) + * (XS + XH * KX)) * XH; + d[0][idxXH] = Kh20 * ft04 + * (XS * XS) / ((XS + KX * XH) + * (XS + KX * XH)); + if (XH < 0.1) { - d[0][idxXH] = Kh20 * ft04 * (XS / 0.1) / ((XS / 0.1) + KX); - d[0][idxXS] = Kh20 * ft04 * 0.1 / ((XS + 0.1 * KX) * (XS + 0.1 * KX)) * XH; + d[0][idxXS] = Kh20 * ft04 + * 0.1 / ((XS + 0.1 * KX) * (XS + 0.1 * KX)) * XH; + d[0][idxXH] = 0.0; } + // p2: Aerobic storage of SS: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; + d[1][idxSO] = k_sto + * SS / (SS + KHSS) + * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XH; + d[1][idxSS] = k_sto + * SO / (SO + KHO2) + * KHSS / ((SS + KHSS) * (SS + KHSS)) * XH; + d[1][idxXH] = k_sto + * SO / (SO + KHO2) + * SS / (SS + KHSS); + + // p3: Anoxic storage of SS: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + d[2][idxSO] = k_sto * etaHNO3 + * -KHO2 / ((SO + KHO2) * (SO + KHO2)) + * SS / (SS + KHSS) + * SNO / (SNO + KHNO3) * XH; + d[2][idxSS] = k_sto * etaHNO3 + * KHO2 / (SO + KHO2) + * KHSS / ((SS + KHSS) * (SS + KHSS)) + * SNO / (SNO + KHNO3) * XH; + d[2][idxSNO] = k_sto * etaHNO3 + * KHO2 / (SO + KHO2) + * SS / (SS + KHSS) + * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[2][idxXH] = k_sto * etaHNO3 + * KHO2 / (SO + KHO2) + * SS / (SS + KHSS) + * SNO / (SNO + KHNO3); + + // p4: Aerobic growth: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO/XH_S) / ((XSTO/XH_S) + KHSTO) * XH; + d[3][idxSO] = muH + * KHO2 / ((SO + KHO2) * (SO + KHO2)) + * SNH / (SNH + KHNH4) + * SALK / (SALK + KHALK) + * (XSTO / XH) / ((XSTO / XH) + KHSTO) * XH; + d[3][idxSNH] = muH + * SO / (SO + KHO2) + * KHNH4 / ((SNH + KHNH4) * (SNH + KHNH4)) + * SALK / (SALK + KHALK) + * (XSTO / XH) / ((XSTO / XH) + KHSTO) * XH; + d[3][idxSALK] = muH + * SO / (SO + KHO2) + * SNH / (SNH + KHNH4) + * KHALK / ((SALK + KHALK) * (SALK + KHALK)) + * (XSTO / XH) / ((XSTO / XH) + KHSTO) * XH; + d[3][idxXSTO] = muH + * SO / (SO + KHO2) + * SNH / (SNH + KHNH4) + * SALK / (SALK + KHALK) + * (KHSTO * XH) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)) * XH; + d[3][idxXH] = muH + * SO / (SO + KHO2) + * SNH / (SNH + KHNH4) + * SALK / (SALK + KHALK) + * (XSTO * XSTO) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)); - // p2: Aerobic storage of SS: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; - d[1][idxSO] = k_sto * KHO2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[1][idxSS] = k_sto * SO / (SO + KHO2) * KHSS * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; - d[1][idxXH] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); - - // p3: Anoxic storage of SS: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - d[2][idxSO] = -k_sto * etaHNO3 * KHO2 * SS / (SS + KHSS) * SNO / (SNO + KHNO3) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[2][idxSS] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * KHSS * SNO / (SNO + KHNO3) / ((SS + KHSS) * (SS + KHSS)) * XH; - d[2][idxSNO] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[2][idxXH] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3); - - // p4: Aerobic growth: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO/XH_S) / ((XSTO/XH_S) + KHSTO) * XH; - d[3][idxSO] = muH * KHO2 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO/XH) / (((XSTO/XH)) + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[3][idxSNH] = muH * SO / (SO + KHO2) * KHNH4 * SALK / (SALK + KHALK) * (XSTO/XH) / ((XSTO/XH) + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; - d[3][idxSALK] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * KHALK * (XSTO/XH) / ((XSTO/XH) + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; - d[3][idxXSTO] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (KHSTO / XH) / (((XSTO/XH) + KHSTO) * ((XSTO/XH) + KHSTO)) * XH; - d[3][idxXH] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * ((XSTO/XH) / ((XSTO/XH) + KHSTO) - XSTO*KHSTO / (XH*XH * ((XSTO/XH) + KHSTO) * ((XSTO/XH) + KHSTO))); - if (XH < 0.1) { - d[3][idxSO] = muH * KHO2 * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) / ((SO + KHO2) * (SO + KHO2)) * XH; - d[3][idxSNH] = muH * SO / (SO + KHO2) * KHNH4 * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) / ((SNH + KHNH4) * (SNH + KHNH4)) * XH; - d[3][idxSALK] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * KHALK * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) / ((SALK + KHALK) * (SALK + KHALK)) * XH; - d[3][idxXSTO] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (KHSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * (XSTO / 0.1 + KHSTO)) * XH; - d[3][idxXH] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO); + d[3][idxSO] = muH + * -KHO2 / ((SO + KHO2) * (SO + KHO2)) + * SNH / (SNH + KHNH4) + * SALK / (SALK + KHALK) + * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * 0.1; + d[3][idxSNH] = muH + * SO / (SO + KHO2) + * KHNH4 / ((SNH + KHNH4) * (SNH + KHNH4)) + * SALK / (SALK + KHALK) + * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * 0.1; + d[3][idxSALK] = muH + * SO / (SO + KHO2) + * SNH / (SNH + KHNH4) + * KHALK / ((SALK + KHALK) * (SALK + KHALK)) + * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * 0.1; + d[3][idxXSTO] = muH + * SO / (SO + KHO2) + * SNH / (SNH + KHNH4) + * SALK / (SALK + KHALK) + * (KHSTO * 0.1) / ((XSTO + KHSTO * 0.1) * (XSTO + KHSTO * 0.1)) * 0.1; + d[3][idxXH] = 0.0; } - // p5: Anoxic growth: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * XSTO / XH_S * 1 / (KHSTO + (XSTO/XH)_S) * SNO / (KHNO3 + SNO) * XH; - d[4][idxSO] = -muH * etaHNO3 * KHO2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / XH) * 1 / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH / ((KHO2 + SO) * (KHO2 + SO)); - d[4][idxSNH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * KHNH4 * SALK / (KHALK + SALK) * (XSTO / XH) * 1 / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH / ((KHNH4 + SNH) * (KHNH4 + SNH)); - d[4][idxSALK] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * KHALK * (XSTO / XH) * 1 / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH / ((KHALK + SALK) * (KHALK + SALK)); - d[4][idxSNO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / XH) / (KHSTO + (XSTO/XH)) * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; - d[4][idxXSTO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * 1 / XH / (KHSTO + (XSTO/XH)) * SNO / (KHNO3 + SNO) * XH; - d[4][idxXH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * SNO / (KHNO3 + SNO) * ((XSTO/XH) / (KHSTO + (XSTO/XH)) - XSTO*KHSTO / (XH*XH * (KHSTO + (XSTO/XH)) * (KHSTO + (XSTO/XH)))); - + // p5: Anoxic growth: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * SNO / (KHNO3 + SNO)* (XSTO / XH_S) / (KHSTO + (XSTO/XH)_S) * XH; + d[4][idxSO] = muH * etaHNO3 + * -KHO2 / ((KHO2 + SO) * (KHO2 + SO)) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * (XSTO / XH) / (KHSTO + (XSTO / XH)) + * SNO / (KHNO3 + SNO) * XH; + d[4][idxSNH] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * KHNH4 / ((KHNH4 + SNH) * (KHNH4 + SNH)) + * SALK / (KHALK + SALK) + * (XSTO / XH) / (KHSTO + (XSTO / XH)) + * SNO / (KHNO3 + SNO) * XH; + d[4][idxSALK] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * KHALK / ((KHALK + SALK) * (KHALK + SALK)) + * (XSTO / XH) / (KHSTO + (XSTO / XH)) + * SNO / (KHNO3 + SNO) * XH; + d[4][idxSNO] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * (XSTO / XH) / (KHSTO + (XSTO / XH)) + * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; + d[4][idxXSTO] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * (1.0 / XH) + * SNO / (KHNO3 + SNO) + * (KHSTO * XH * XH) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)) * XH; + d[4][idxXH] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * SNO / (KHNO3 + SNO) + * (XSTO * XSTO) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)); + + if (XH < 0.1) { - d[4][idxSO] = -muH * etaHNO3 * KHO2 * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) * 1 / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH / ((KHO2 + SO) * (KHO2 + SO)); - d[4][idxSNH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * KHNH4 * SALK / (KHALK + SALK) * (XSTO / 0.1) * 1 / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH / ((KHNH4 + SNH) * (KHNH4 + SNH)); - d[4][idxSALK] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * KHALK * (XSTO / 0.1) * 1 / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH / ((KHALK + SALK) * (KHALK + SALK)); - d[4][idxSNO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; - d[4][idxXSTO] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (1 / 0.1) / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO) * XH; - d[4][idxXH] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) * SNO / (KHNO3 + SNO); + d[4][idxSO] = muH * etaHNO3 + * -KHO2 / ((KHO2 + SO) * (KHO2 + SO)) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) + * SNO / (KHNO3 + SNO) * 0.1; + d[4][idxSNH] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * KHNH4 / ((KHNH4 + SNH) * (KHNH4 + SNH)) + * SALK / (KHALK + SALK) + * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) + * SNO / (KHNO3 + SNO) * 0.1; + d[4][idxSALK] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * KHALK / ((KHALK + SALK) * (KHALK + SALK)) + * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) + * SNO / (KHNO3 + SNO) * 0.1; + d[4][idxSNO] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) + * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * 0.1; + d[4][idxXSTO] = muH * etaHNO3 + * KHO2 / (KHO2 + SO) + * SNH / (KHNH4 + SNH) + * SALK / (KHALK + SALK) + * (1.0 / 0.1) + * SNO / (KHNO3 + SNO) + * (KHSTO * 0.1 * 0.1) / ((XSTO + KHSTO * 0.1) * (XSTO + KHSTO * 0.1)) * 0.1; + d[4][idxXH] = 0.0; } - //reaction6: bH * SO / (SO + KHO2) * XH; - d[5][idxSO] = bH * KHO2 * XH / ((SO + KHO2) * (SO + KHO2)); + d[5][idxSO] = bH + * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XH; d[5][idxXH] = bH * SO / (SO + KHO2); //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; - d[6][idxSO] = bH * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XH; - d[6][idxSNO] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[6][idxXH] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + d[6][idxSO] = bH * etaHend + * -KHO2 / ((SO + KHO2) * (SO + KHO2)) + * SNO / (SNO + KHNO3) * XH; + d[6][idxSNO] = bH * etaHend + * KHO2 / (SO + KHO2) + * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; + d[6][idxXH] = bH * etaHend + * KHO2 / (SO + KHO2) + * SNO / (SNO + KHNO3); //reaction8: bH * SO / (SO + KHO2) * XSTO; - d[7][idxSO] = bH * KHO2 * XSTO / ((SO + KHO2) * (SO + KHO2)); - d[7][idxXSTO] = bH * SO / (SO + KHO2); + d[7][idxSO] = bH + * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XSTO; + d[7][idxXSTO] = bH * SO / (SO + KHO2); //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; - d[8][idxSO] = bH * etaHend * KHO2 / ((SO + KHO2) * (SO + KHO2)) * SNO / (SNO + KHNO3) * XSTO; - d[8][idxSNO] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; - d[8][idxXSTO] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3); + d[8][idxSO] = bH * etaHend + * -KHO2 / ((SO + KHO2) * (SO + KHO2)) + * SNO / (SNO + KHNO3) * XSTO; + d[8][idxSNO] = bH * etaHend + * KHO2 / (SO + KHO2) + * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; + d[8][idxXSTO] = bH * etaHend + * KHO2 / (SO + KHO2) + * SNO / (SNO + KHNO3); //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; - d[9][idxSO] = muAUT * KNO2 * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA / ((SO + KNO2) * (SO + KNO2)); - d[9][idxSALK] = muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) / ((SALK + KNALK) * (SALK + KNALK)) * XA; - d[9][idxXA] = muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK); - d[9][idxSNH] = muAUT * SO / (SO + KNO2) * SALK / (SALK + KNALK) / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; - + d[9][idxSO] = muAUT + * KNO2 / ((SO + KNO2) * (SO + KNO2)) + * SNH / (SNH + KNNH4) + * SALK / (SALK + KNALK) * XA; + d[9][idxSALK] = muAUT + * SO / (SO + KNO2) + * SNH / (SNH + KNNH4) + * KNALK / ((SALK + KNALK) * (SALK + KNALK)) * XA; + d[9][idxSNH] = muAUT + * SO / (SO + KNO2) + * SALK / (SALK + KNALK) + * KNNH4 / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; + d[9][idxXA] = muAUT + * SO / (SO + KNO2) + * SNH / (SNH + KNNH4) + * SALK / (SALK + KNALK); + //reaction11: bAUT * SO / (SO + KHO2) * XA; - d[10][idxSO] = bAUT * XA / ((SO + KHO2) * (SO + KHO2)); - d[10][idxXA] = bAUT * SO / (SO + KHO2); + d[10][idxSO] = bAUT + * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; + d[10][idxXA] = bAUT + * SO / (SO + KHO2); //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - d[11][idxSO] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; - d[11][idxSNO] = bAUT * etaNend * KHO2 / (SO + KHO2) * SNO / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; - d[11][idxXA] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2); + d[11][idxSO] = bAUT * etaNend + * SNO / (SNO + KHNO3) + * -KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; + d[11][idxSNO] = bAUT * etaNend + * KHO2 / (SO + KHO2) + * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; + d[11][idxXA] = bAUT * etaNend + * SNO / (SNO + KHNO3) + * KHO2 / (SO + KHO2); RowIterator curJac = jac; - // TODO consider configurable component indices for (size_t rIdx = 0; rIdx < 13; rIdx++) { RowIterator curJac = jac; for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { - rIdx = 10; const double colFactor = static_cast(_stoichiometry.native(row, rIdx)); for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) { + curJac[compIdx - static_cast(row)] += colFactor * d[rIdx][compIdx]; } } diff --git a/test/ReactionModelTests.cpp b/test/ReactionModelTests.cpp index bb3ae78f5..191f92799 100644 --- a/test/ReactionModelTests.cpp +++ b/test/ReactionModelTests.cpp @@ -392,7 +392,6 @@ namespace reaction ad::prepareAdVectorSeedsForDenseMatrix(adY, 0, numDofs); ad::copyToAd(yState.data(), adY, numDofs); ad::resetAd(adRes, numDofs); - crm.model().residualCombinedAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, adY, adY + crm.nComp(), adRes, adRes + crm.nComp(), 1.0, crm.buffer()); // Extract Jacobian cadet::linalg::DenseMatrix jacAD; @@ -402,42 +401,8 @@ namespace reaction // Calculate analytic Jacobian cadet::linalg::DenseMatrix jacAna; jacAna.resize(numDofs, numDofs); - crm.model().analyticJacobianCombinedAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, yState.data(), yState.data() + crm.nComp(), 1.0, jacAna.row(0), jacAna.row(crm.nComp()), crm.buffer()); - cadet::test::checkJacobianPatternFD( - [&](double const* lDir, double* res) -> void - { - std::fill_n(res, numDofs, 0.0); - crm.model().residualCombinedAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, lDir, lDir + crm.nComp(), res, res + crm.nComp(), 1.0, crm.buffer()); - }, - [&](double const* lDir, double* res) -> void - { - jacAna.multiplyVector(lDir, res); - }, - yState.data(), dir.data(), colA.data(), colB.data(), numDofs, numDofs); - - cadet::test::checkJacobianPatternFD( - [&](double const* lDir, double* res) -> void - { - std::fill_n(res, numDofs, 0.0); - crm.model().residualCombinedAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, lDir, lDir + crm.nComp(), res, res + crm.nComp(), 1.0, crm.buffer()); - }, - [&](double const* lDir, double* res) -> void - { - jacAD.multiplyVector(lDir, res); - }, - yState.data(), dir.data(), colA.data(), colB.data(), numDofs, numDofs); - - // Check Jacobians against each other - for (unsigned int row = 0; row < numDofs; ++row) - { - for (unsigned int col = 0; col < numDofs; ++col) - { - CAPTURE(row); - CAPTURE(col); - CHECK(jacAna.native(row, col) == makeApprox(jacAD.native(row, col), absTol, relTol)); - } - } + // Only liquid phase diff --git a/test/ReactionModels.cpp b/test/ReactionModels.cpp index 1b26e5d0a..49c76ebba 100644 --- a/test/ReactionModels.cpp +++ b/test/ReactionModels.cpp @@ -131,7 +131,7 @@ TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD without inhibition", TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD with inhibition", "[MichaelisMenten],[ReactionModel],[Jacobian],[AD]") { - const unsigned int nBound[] = {1, 2, 1}; + const unsigned int nBound[] = {1.0, 2.0, 1.0}; const double point[] = {1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8}; cadet::test::reaction::testDynamicJacobianAD("MICHAELIS_MENTEN", 3, nBound, R"json({ @@ -145,17 +145,54 @@ TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD with inhibition", "[M point, 1e-15, 1e-15 ); } -TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD]") +TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD],[testHere]") { - const unsigned int nBound[] = {0}; + const unsigned int nBound[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0 }; unsigned int ncomp = 13; - const double point[] = { 1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8 }; - cadet::test::reaction::testDynamicJacobianAD("ACTIVATES_SLUDGE_MODEL3", ncomp, nBound, + const double point[] = { 1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8, 1.5, 1.0, 4.2, 1.4, 0.3, 1.4}; + cadet::test::reaction::testDynamicJacobianAD("ACTIVATED_SLUDGE_MODEL3", ncomp, nBound, R"json({ - "ACSM3_p1": [1.0, 2.0, 0.4], - "ACSM3_p2": [-1.0, 1.0, -1.0, -1.0, -1.0, -1.0, 3.0, 2.0, -1.0] + "ASM3_FISS_BM_PROD": 1.0, + "ASM3_FSI": 0.0, + "ASM3_YH_AER": 0.8, + "ASM3_YH_ANOX": 0.65, + "ASM3_YSTO_AER": 0.8375, + "ASM3_YSTO_ANOX": 0.7, + "ASM3_FXI": 0.2, + "ASM3_YA": 0.24, + "ASM3_KH20": 9.0, + "ASM3_KX": 1.0, + "ASM3_KSTO20": 12.0, + "ASM3_MU_H20": 3.0, + "ASM3_BH20": 0.33, + "ASM3_ETA_HNO3": 0.5, + "ASM3_KHO2": 0.2, + "ASM3_KHSS": 10.0, + "ASM3_KHNO3": 0.5, + "ASM3_KHNH4": 0.01, + "ASM3_KHALK": 0.1, + "ASM3_KHSTO": 0.1, + "ASM3_MU_AUT20": 1.12, + "ASM3_BAUT20": 0.18, + "ASM3_ETAH_END": 0.5, + "ASM3_ETAN_END": 0.5, + "ASM3_KNO2": 0.5, + "ASM3_KNNH4": 0.7, + "ASM3_KNALK": 0.5, + "ASM3_T": 12.0, + "ASM3_V": 1000.0, + "ASM3_IO2": 0.0, + "ASM3_INSI": 0.01, + "ASM3_INSS": 0.03, + "ASM3_INXI": 0.04, + "ASM3_INXS": 0.03, + "ASM3_INBM": 0.07, + "ASM3_IVSS_XI": 0.751879699, + "ASM3_IVSS_XS": 0.555555556, + "ASM3_IVSS_STO": 0.6, + "ASM3_IVSS_BM": 0.704225352, + "ASM3_ITSS_VSS_BM": 1.086956522 })json", point, 1e-15, 1e-15 ); } - From 2ce03deca62ee5bc7386008d11fdf6b49e521c42 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Tue, 20 May 2025 14:52:02 +0200 Subject: [PATCH 29/48] add AD test only for reactions in liquit phase --- test/ReactionModelTests.cpp | 155 +++++++++++++++++++++++++++++++----- test/ReactionModelTests.hpp | 14 +++- test/ReactionModels.cpp | 6 +- 3 files changed, 152 insertions(+), 23 deletions(-) diff --git a/test/ReactionModelTests.cpp b/test/ReactionModelTests.cpp index 191f92799..be9edea65 100644 --- a/test/ReactionModelTests.cpp +++ b/test/ReactionModelTests.cpp @@ -392,6 +392,7 @@ namespace reaction ad::prepareAdVectorSeedsForDenseMatrix(adY, 0, numDofs); ad::copyToAd(yState.data(), adY, numDofs); ad::resetAd(adRes, numDofs); + crm.model().residualCombinedAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, adY, adY + crm.nComp(), adRes, adRes + crm.nComp(), 1.0, crm.buffer()); // Extract Jacobian cadet::linalg::DenseMatrix jacAD; @@ -401,14 +402,48 @@ namespace reaction // Calculate analytic Jacobian cadet::linalg::DenseMatrix jacAna; jacAna.resize(numDofs, numDofs); + crm.model().analyticJacobianCombinedAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, yState.data(), yState.data() + crm.nComp(), 1.0, jacAna.row(0), jacAna.row(crm.nComp()), crm.buffer()); - + cadet::test::checkJacobianPatternFD( + [&](double const* lDir, double* res) -> void + { + std::fill_n(res, numDofs, 0.0); + crm.model().residualCombinedAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, lDir, lDir + crm.nComp(), res, res + crm.nComp(), 1.0, crm.buffer()); + }, + [&](double const* lDir, double* res) -> void + { + jacAna.multiplyVector(lDir, res); + }, + yState.data(), dir.data(), colA.data(), colB.data(), numDofs, numDofs); + + cadet::test::checkJacobianPatternFD( + [&](double const* lDir, double* res) -> void + { + std::fill_n(res, numDofs, 0.0); + crm.model().residualCombinedAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, lDir, lDir + crm.nComp(), res, res + crm.nComp(), 1.0, crm.buffer()); + }, + [&](double const* lDir, double* res) -> void + { + jacAD.multiplyVector(lDir, res); + }, + yState.data(), dir.data(), colA.data(), colB.data(), numDofs, numDofs); + + // Check Jacobians against each other + for (unsigned int row = 0; row < numDofs; ++row) + { + for (unsigned int col = 0; col < numDofs; ++col) + { + CAPTURE(row); + CAPTURE(col); + CHECK(jacAna.native(row, col) == makeApprox(jacAD.native(row, col), absTol, relTol)); + } + } // Only liquid phase // Evaluate with AD ad::resetAd(adRes, numDofs); - crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, adY, adRes, 1.0, crm.buffer()); + crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, adY, adRes, 1.0, crm.buffer()); // Extract Jacobian jacAD.setAll(0.0); @@ -416,33 +451,115 @@ namespace reaction // Calculate analytic Jacobian jacAna.setAll(0.0); - crm.model().analyticJacobianLiquidAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, yState.data(), 1.0, jacAna.row(0), crm.buffer()); + crm.model().analyticJacobianLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, yState.data(), 1.0, jacAna.row(0), crm.buffer()); delete[] adY; delete[] adRes; cadet::test::checkJacobianPatternFD( [&](double const* lDir, double* res) -> void - { - std::fill_n(res, nComp, 0.0); - crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, lDir, res, 1.0, crm.buffer()); - }, - [&](double const* lDir, double* res) -> void - { - jacAna.submatrixMultiplyVector(lDir, 0, 0, nComp, nComp, res); - }, + { + std::fill_n(res, nComp, 0.0); + crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, lDir, res, 1.0, crm.buffer()); + }, + [&](double const* lDir, double* res) -> void + { + jacAna.submatrixMultiplyVector(lDir, 0, 0, nComp, nComp, res); + }, yState.data(), dir.data(), colA.data(), colB.data(), nComp, nComp); cadet::test::checkJacobianPatternFD( [&](double const* lDir, double* res) -> void - { - std::fill_n(res, nComp, 0.0); - crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{0.0, 0.0, 0.0}, lDir, res, 1.0, crm.buffer()); - }, - [&](double const* lDir, double* res) -> void - { - jacAD.submatrixMultiplyVector(lDir, 0, 0, nComp, nComp, res); - }, + { + std::fill_n(res, nComp, 0.0); + crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, lDir, res, 1.0, crm.buffer()); + }, + [&](double const* lDir, double* res) -> void + { + jacAD.submatrixMultiplyVector(lDir, 0, 0, nComp, nComp, res); + }, + yState.data(), dir.data(), colA.data(), colB.data(), nComp, nComp); + + // Check Jacobians against each other + for (unsigned int row = 0; row < nComp; ++row) + { + for (unsigned int col = 0; col < nComp; ++col) + { + CAPTURE(row); + CAPTURE(col); + CHECK(jacAna.native(row, col) == makeApprox(jacAD.native(row, col), absTol, relTol)); + } + } + } + + void testLiquidReactionJacobianAD(const char* modelName, unsigned int nComp, unsigned int const* nBound, const char* config, double const* point, double absTol, double relTol) + { + ConfiguredDynamicReactionModel crm = ConfiguredDynamicReactionModel::create(modelName, nComp, nBound, config); + + const unsigned int numDofs = crm.nComp() + crm.numBoundStates(); + std::vector yState(numDofs, 0.0); + std::copy_n(point, numDofs, yState.data()); + + std::vector dir(numDofs, 0.0); + std::vector colA(numDofs, 0.0); + std::vector colB(numDofs, 0.0); + + // Enable AD + cadet::ad::setDirections(cadet::ad::getMaxDirections()); + cadet::active* adRes = new cadet::active[numDofs]; + cadet::active* adY = new cadet::active[numDofs]; + + // Evaluate with AD + ad::prepareAdVectorSeedsForDenseMatrix(adY, 0, numDofs); + ad::copyToAd(yState.data(), adY, numDofs); + ad::resetAd(adRes, numDofs); + + // Extract Jacobian + cadet::linalg::DenseMatrix jacAD; + jacAD.resize(numDofs, numDofs); + ad::extractDenseJacobianFromAd(adRes, 0, jacAD); + + // Calculate analytic Jacobian + cadet::linalg::DenseMatrix jacAna; + jacAna.resize(numDofs, numDofs); + + // Evaluate with AD + ad::resetAd(adRes, numDofs); + crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, adY, adRes, 1.0, crm.buffer()); + + // Extract Jacobian + jacAD.setAll(0.0); + ad::extractDenseJacobianFromAd(adRes, 0, jacAD); + + // Calculate analytic Jacobian + jacAna.setAll(0.0); + crm.model().analyticJacobianLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, yState.data(), 1.0, jacAna.row(0), crm.buffer()); + + delete[] adY; + delete[] adRes; + + cadet::test::checkJacobianPatternFD( + [&](double const* lDir, double* res) -> void + { + std::fill_n(res, nComp, 0.0); + crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, lDir, res, 1.0, crm.buffer()); + }, + [&](double const* lDir, double* res) -> void + { + jacAna.submatrixMultiplyVector(lDir, 0, 0, nComp, nComp, res); + }, + yState.data(), dir.data(), colA.data(), colB.data(), nComp, nComp); + + cadet::test::checkJacobianPatternFD( + [&](double const* lDir, double* res) -> void + { + std::fill_n(res, nComp, 0.0); + crm.model().residualLiquidAdd(1.0, 0u, ColumnPosition{ 0.0, 0.0, 0.0 }, lDir, res, 1.0, crm.buffer()); + }, + [&](double const* lDir, double* res) -> void + { + jacAD.submatrixMultiplyVector(lDir, 0, 0, nComp, nComp, res); + }, yState.data(), dir.data(), colA.data(), colB.data(), nComp, nComp); // Check Jacobians against each other diff --git a/test/ReactionModelTests.hpp b/test/ReactionModelTests.hpp index aa0060144..0b4fed543 100644 --- a/test/ReactionModelTests.hpp +++ b/test/ReactionModelTests.hpp @@ -123,6 +123,18 @@ namespace reaction */ void testMichaelisMentenToSMAInhibitionMicroKinetic(const std::string configFilePathMM, const std::string configFilePathSMA, const double absTol, const double relTol); + /** + * @brief Checks the analytic Jacobians of the dynamic reaction model against AD + * @param [in] modelName Name of the reaction model + * @param [in] nComp Number of components + * @param [in] nBound Array with number of bound states for each component + * @param [in] config JSON string with reaction model parameters + * @param [in] point Liquid phase and solid phase values to check Jacobian at + * @param [in] absTol Absolute error tolerance + * @param [in] relTol Relative error tolerance + */ + void testDynamicJacobianAD(const char* modelName, unsigned int nComp, unsigned int const* nBound, const char* config, double const* point, double absTol = 0.0, double relTol = std::numeric_limits::epsilon() * 100.0); + /** * @brief Checks the analytic Jacobians of the dynamic reaction model against AD * @param [in] modelName Name of the reaction model @@ -133,7 +145,7 @@ namespace reaction * @param [in] absTol Absolute error tolerance * @param [in] relTol Relative error tolerance */ - void testDynamicJacobianAD(const char* modelName, unsigned int nComp, unsigned int const* nBound, const char* config, double const* point, double absTol = 0.0, double relTol = std::numeric_limits::epsilon() * 100.0); + void testLiquidReactionJacobianAD(const char* modelName, unsigned int nComp, unsigned int const* nBound, const char* config, double const* point, double absTol = 0.0, double relTol = std::numeric_limits::epsilon() * 100.0); /** * @brief Extends a model with dynamic reactions in each phase and particle type diff --git a/test/ReactionModels.cpp b/test/ReactionModels.cpp index 49c76ebba..bff1593b6 100644 --- a/test/ReactionModels.cpp +++ b/test/ReactionModels.cpp @@ -145,12 +145,12 @@ TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD with inhibition", "[M point, 1e-15, 1e-15 ); } -TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD],[testHere]") +TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD]") { - const unsigned int nBound[] = { 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,0.0, 0.0, 0.0 }; + const unsigned int nBound[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0 }; unsigned int ncomp = 13; const double point[] = { 1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8, 1.5, 1.0, 4.2, 1.4, 0.3, 1.4}; - cadet::test::reaction::testDynamicJacobianAD("ACTIVATED_SLUDGE_MODEL3", ncomp, nBound, + cadet::test::reaction::testLiquidReactionJacobianAD("ACTIVATED_SLUDGE_MODEL3", ncomp, nBound, R"json({ "ASM3_FISS_BM_PROD": 1.0, "ASM3_FSI": 0.0, From 5e1c12470063a9c2d356a38eb42039b39247d7ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Tue, 20 May 2025 20:36:43 +0200 Subject: [PATCH 30/48] ASM3: group parameter documentation --- .../reaction/activated_sludge_model.rst | 96 +++++++++++-------- 1 file changed, 54 insertions(+), 42 deletions(-) diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst index f6078fb2e..71c9c98b2 100644 --- a/doc/interface/reaction/activated_sludge_model.rst +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -7,20 +7,23 @@ Activated Sludge Model (ASM3h) For information on model equations, refer to :ref:`activated_sludge_model`. -``ASM3_KH20`` +Environment/process parameters +---- - Hydrolysis rate constant :math:`k_H` at 20 °C. +``ASM3_T`` + + Temperature :math:`T`. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_T`` +``ASM3_V`` + + Reactor volume :math:`V`. Set this to the column volume since the model can't compute it itself. - Temperature :math:`T`. - ================ ============================= ======================================================== - **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + **Type:** double **Range:** :math:`\gt 0` **Length:** 1 ================ ============================= ======================================================== ``ASM3_IO2`` @@ -31,12 +34,15 @@ For information on model equations, refer to :ref:`activated_sludge_model`. **Type:** double **Range:** :math:`\ge 0` **Length:** 1 ================ ============================= ======================================================== -``ASM3_V`` +Maximum rates +---- - Aeration volume :math:`V`. +``ASM3_KH20`` + Hydrolysis rate constant :math:`k_H` at 20 °C. + ================ ============================= ======================================================== - **Type:** double **Range:** :math:`\gt 0` **Length:** 1 + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== ``ASM3_KSTO20`` @@ -47,137 +53,143 @@ For information on model equations, refer to :ref:`activated_sludge_model`. **Type:** double **Range:** :math:`\gt 0` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KX`` +``ASM3_MU_H20`` - Saturation/inhibition coefficient for particulate COD :math:`KX`. + Heterotrophic max. growth rate :math:`\mu_{H}` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KHO2`` +``ASM3_BH20`` - Saturation/inhibition coefficient for oxygen, heterotrophic growth :math:`KHO_2`. + Rate constant for lysis and decay :math:`b_H` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KHSS`` +``ASM3_MU_AUT20`` - Substrate :math:`SS` saturation constant :math:`KHSS`. + Autotrophic max. growth rate :math:`\mu_{AUT}` at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KHNO3`` +``ASM3_BAUT20`` - :math:`SNO_X` saturation constant :math:`KHNO_3`. + Rate constant :math:`b_{AUT}` for decay of autotrophs at 20 °C. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== +Anoxic reduction factors +---- + ``ASM3_ETA_HNO3`` - Anoxic reduction factor :math:`\eta_{HNO_3}`. + Anoxic reduction factor :math:`\eta_{HNO_3}` – heterotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KHNH4`` +``ASM3_ETAH_END`` - Ammonium saturation constant :math:`KHNH_4`. + Anoxic reduction factor :math:`\eta_{H_{end}}` – endogenous respiration XH. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KHALK`` +``ASM3_ETAN_END`` - :math:`XH` alkalinity saturation constant :math:`KH_{ALK}`. + Anoxic reduction factor :math:`\eta_{N_{end}}` – endogenous respiration XA. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KHSTO`` +Saturation/inhibition coefficients +---- - :math:`X_{STO}` saturation constant :math:`KH_{STO}`. +``ASM3_KX`` + + Saturation/inhibition coefficient :math:`KX` for particulate COD. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_MU_H20`` +``ASM3_KHO2`` - Heterotrophic max. growth rate :math:`\mu_{H}` at 20 °C. + Saturation/inhibition coefficient :math:`KHO_2` for oxygen, heterotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_ETAH_END`` +``ASM3_KHSS`` - Reduction factor :math:`\eta_{H_{end}}` for :math:`b_H` under anoxic conditions. + Saturation/inhibition coefficient :math:`KHSS` for readily biodegradable substrates. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_BH20`` +``ASM3_KHNO3`` - Rate constant for lysis and decay :math:`b_H` at 20 °C. + Saturation/inhibition coefficient :math:`KHNO_3` for nitrate. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_MU_AUT20`` +``ASM3_KHNH4`` - Maximum growth rate of :math:`XAUT`, :math:`\mu_{AUT}` at 20 °C. + Saturation/inhibition coefficient :math:`KHNH_4` for ammonium (nutrient). ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KNO2`` +``ASM3_KHALK`` - Saturation coefficient :math:`K_{NO_2}` for oxygen, autotrophic growth. + Saturation coefficient :math:`KH_{ALK}` for alkalinity (HCO3-). ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KNNH4`` +``ASM3_KHSTO`` - Saturation coefficient :math:`K_{NNH_4}` for ammonium (substrate), autotrophic growth. + Saturation coefficient :math:`KH_{STO}` for storage products. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_KNALK`` +``ASM3_KNO2`` - Saturation coefficient :math:`K_{NALK}` for alkalinity, autotrophic growth. + Saturation coefficient :math:`K_{NO_2}` for oxygen, autotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_BAUT20`` +``ASM3_KNNH4`` - Decay rate :math:`b_{AUT}` of :math:`XAUT` at 20 °C. + Saturation coefficient :math:`K_{NNH_4}` for ammonium (substrate), autotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -``ASM3_ETAN_END`` +``ASM3_KNALK`` - Reduction factor :math:`\eta_{N_{end}}` for :math:`b_{AUT}` under anoxic conditions. + Saturation coefficient :math:`K_{NALK}` for alkalinity (HCO3-), autotrophic growth. ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 From 8473f7793e97b64adab234ae312f60aa5c53b0f7 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Fri, 6 Jun 2025 08:33:17 +0200 Subject: [PATCH 31/48] change parameter name to lowercase --- .../reaction/ActivatedSludgeModelThree.cpp | 544 +++++++++--------- 1 file changed, 272 insertions(+), 272 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 0a66847fe..46e65bbac 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -32,28 +32,28 @@ "externalName": "ExtActivatedSludgeModelThreeParamHandler", "parameters": [ - { "type": "ScalarParameter", "varName": "Kh20", "confName": "ASM3_KH20"}, + { "type": "ScalarParameter", "varName": "kh20", "confName": "ASM3_KH20"}, { "type": "ScalarParameter", "varName": "T", "confName": "ASM3_T"}, - { "type": "ScalarParameter", "varName": "iO2", "confName": "ASM3_IO2"}, + { "type": "ScalarParameter", "varName": "io2", "confName": "ASM3_IO2"}, { "type": "ScalarParameter", "varName": "V", "confName": "ASM3_V"}, { "type": "ScalarParameter", "varName": "k_sto20", "confName": "ASM3_KSTO20"}, - { "type": "ScalarParameter", "varName": "KX", "confName": "ASM3_KX"}, - { "type": "ScalarParameter", "varName": "KHO2", "confName": "ASM3_KHO2"}, - { "type": "ScalarParameter", "varName": "KHSS", "confName": "ASM3_KHSS"}, - { "type": "ScalarParameter", "varName": "KHNO3", "confName": "ASM3_KHNO3"}, - { "type": "ScalarParameter", "varName": "etaHNO3", "confName": "ASM3_ETA_HNO3"}, - { "type": "ScalarParameter", "varName": "KHNH4", "confName": "ASM3_KHNH4"}, - { "type": "ScalarParameter", "varName": "KHALK", "confName": "ASM3_KHALK"}, - { "type": "ScalarParameter", "varName": "KHSTO", "confName": "ASM3_KHSTO"}, - { "type": "ScalarParameter", "varName": "muH20", "confName": "ASM3_MU_H20"}, - { "type": "ScalarParameter", "varName": "etaHend", "confName": "ASM3_ETAH_END"}, - { "type": "ScalarParameter", "varName": "bH20", "confName": "ASM3_BH20"}, + { "type": "ScalarParameter", "varName": "kx", "confName": "ASM3_KX"}, + { "type": "ScalarParameter", "varName": "kho2", "confName": "ASM3_KHO2"}, + { "type": "ScalarParameter", "varName": "khss", "confName": "ASM3_KHSS"}, + { "type": "ScalarParameter", "varName": "khn03", "confName": "ASM3_KHNO3"}, + { "type": "ScalarParameter", "varName": "etahno3", "confName": "ASM3_ETA_HNO3"}, + { "type": "ScalarParameter", "varName": "khnh4", "confName": "ASM3_KHNH4"}, + { "type": "ScalarParameter", "varName": "khalk", "confName": "ASM3_KHALK"}, + { "type": "ScalarParameter", "varName": "khsto", "confName": "ASM3_KHSTO"}, + { "type": "ScalarParameter", "varName": "muh2o", "confName": "ASM3_MU_H20"}, + { "type": "ScalarParameter", "varName": "etahend", "confName": "ASM3_ETAH_END"}, + { "type": "ScalarParameter", "varName": "bh20", "confName": "ASM3_BH20"}, { "type": "ScalarParameter", "varName": "muAUT20", "confName": "ASM3_MU_AUT20"}, - { "type": "ScalarParameter", "varName": "KNO2", "confName": "ASM3_KNO2"}, - { "type": "ScalarParameter", "varName": "KNNH4", "confName": "ASM3_KNNH4"}, - { "type": "ScalarParameter", "varName": "KNALK", "confName": "ASM3_KNALK"}, - { "type": "ScalarParameter", "varName": "bAUT20", "confName": "ASM3_BAUT20"}, - { "type": "ScalarParameter", "varName": "etaNend", "confName": "ASM3_ETAN_END"} + { "type": "ScalarParameter", "varName": "kno2", "confName": "ASM3_KNO2"}, + { "type": "ScalarParameter", "varName": "knnh4", "confName": "ASM3_KNNH4"}, + { "type": "ScalarParameter", "varName": "knalk", "confName": "ASM3_KNALK"}, + { "type": "ScalarParameter", "varName": "baut20", "confName": "ASM3_BAUT20"}, + { "type": "ScalarParameter", "varName": "etanend", "confName": "ASM3_ETAN_END"} ] } */ @@ -344,38 +344,38 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase typedef typename DoubleActivePromoter::type flux_t; BufferedArray fluxes = workSpace.array(_stoichiometry.columns()); - const flux_t Kh20 = static_cast::type>(p->Kh20); + const flux_t kh20 = static_cast::type>(p->kh20); const flux_t T = static_cast::type>(p->T); - const flux_t iO2 = static_cast::type>(p->iO2); + const flux_t io2 = static_cast::type>(p->io2); const flux_t V = static_cast::type>(p->V); const flux_t k_sto20 = static_cast::type>(p->k_sto20); - const flux_t KX = static_cast::type>(p->KX); - const flux_t KHO2 = static_cast::type>(p->KHO2); - const flux_t KHSS = static_cast::type>(p->KHSS); - const flux_t KHNO3 = static_cast::type>(p->KHNO3); - const flux_t etaHNO3 = static_cast::type>(p->etaHNO3); - const flux_t KHNH4 = static_cast::type>(p->KHNH4); - const flux_t KHALK = static_cast::type>(p->KHALK); - const flux_t KHSTO = static_cast::type>(p->KHSTO); - const flux_t muH20 = static_cast::type>(p->muH20); - const flux_t etaHend = static_cast::type>(p->etaHend); - const flux_t bH20 = static_cast::type>(p->bH20); + const flux_t kx = static_cast::type>(p->kx); + const flux_t kho2 = static_cast::type>(p->kho2); + const flux_t khss = static_cast::type>(p->khss); + const flux_t khn03 = static_cast::type>(p->khn03); + const flux_t etahno3 = static_cast::type>(p->etahno3); + const flux_t khnh4 = static_cast::type>(p->khnh4); + const flux_t khalk = static_cast::type>(p->khalk); + const flux_t khsto = static_cast::type>(p->khsto); + const flux_t muh2o = static_cast::type>(p->muh2o); + const flux_t etahend = static_cast::type>(p->etahend); + const flux_t bh20 = static_cast::type>(p->bh20); const flux_t muAUT20 = static_cast::type>(p->muAUT20); - const flux_t KNO2 = static_cast::type>(p->KNO2); - const flux_t KNNH4 = static_cast::type>(p->KNNH4); - const flux_t KNALK = static_cast::type>(p->KNALK); - const flux_t bAUT20 = static_cast::type>(p->bAUT20); - const flux_t etaNend = static_cast::type>(p->etaNend); + const flux_t kno2 = static_cast::type>(p->kno2); + const flux_t knnh4 = static_cast::type>(p->knnh4); + const flux_t knalk = static_cast::type>(p->knalk); + const flux_t baut20 = static_cast::type>(p->baut20); + const flux_t etanend = static_cast::type>(p->etanend); // derived parameters const double ft04 = exp(-0.04 * (20.0 - static_cast(T))); const double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); const double ft105 = exp(-0.105 * (20.0 - static_cast(T))); const double k_sto = static_cast(k_sto20) * ft07; - const double muH = static_cast(muH20) * ft07; - const double bH = static_cast(bH20) * ft07; + const double muH = static_cast(muh2o) * ft07; + const double bH = static_cast(bh20) * ft07; const double muAUT = static_cast(muAUT20) * ft105; - const double bAUT = static_cast(bAUT20) * ft105; + const double bAUT = static_cast(baut20) * ft105; StateType SO = y[0]; StateType SS = y[1]; @@ -393,51 +393,51 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // p1: Hydrolysis of organic structures - fluxes[0] = Kh20 * ft04 * (XS/XH) / ((XS/XH) + KX) * XH; + fluxes[0] = kh20 * ft04 * (XS/XH) / ((XS/XH) + kx) * XH; if (XH < 0.1) - fluxes[0] = Kh20 * ft04 * (XS/0.1) / ((XS/0.1) + KX) * XH; + fluxes[0] = kh20 * ft04 * (XS/0.1) / ((XS/0.1) + kx) * XH; // p2: Aerobic storage of SS - fluxes[1] = k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; + fluxes[1] = k_sto * SO / (SO + kho2) * SS / (SS + khss) * XH; // p3: Anoxic storage of SS - fluxes[2] = k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; + fluxes[2] = k_sto * etahno3 * kho2 / (SO + kho2) * SS / (SS + khss) * SNO / (SNO + khn03) * XH; // p4: Aerobic growth of heterotrophic biomass (XH) - fluxes[3] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * ((XSTO/XH)) / (((XSTO/XH)) + KHSTO) * XH; + fluxes[3] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * ((XSTO/XH)) / (((XSTO/XH)) + khsto) * XH; if (XH < 0.1) - fluxes[3] = muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * XH; + fluxes[3] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * XH; // p5: Anoxic growth of heterotrophic biomass (XH, denitrification) - fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / XH) * 1 / (KHSTO + (XSTO / XH)) * SNO / (KHNO3 + SNO) * XH; + fluxes[4] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (XSTO / XH) * 1 / (khsto + (XSTO / XH)) * SNO / (khn03 + SNO) * XH; if (XH < 0.1) - fluxes[4] = muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * (XSTO / 0.1) * 1 / (KHSTO + (XSTO/0.1)) * SNO / (KHNO3 + SNO) * XH; + fluxes[4] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (XSTO / 0.1) * 1 / (khsto + (XSTO/0.1)) * SNO / (khn03 + SNO) * XH; // r6: Aerobic endogenous respiration of heterotroph microorganisms (XH) - fluxes[5] = bH * SO / (SO + KHO2) * XH; + fluxes[5] = bH * SO / (SO + kho2) * XH; // r7: Anoxic endogenous respiration of heterotroph microorganisms (XH) - fluxes[6] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; + fluxes[6] = bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03) * XH; // r8: Aerobic respiration of internal cell storage products - fluxes[7] = bH * SO / (SO + KHO2) * XSTO; + fluxes[7] = bH * SO / (SO + kho2) * XSTO; // r9: Anoxic respiration of internal cell storage products - fluxes[8] = bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; + fluxes[8] = bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03) * XSTO; // r10: Aerobic growth of autotrophic biomass (XAUT, nitrification) - fluxes[9] = muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; + fluxes[9] = muAUT * SO / (SO + kno2) * SNH / (SNH + knnh4) * SALK / (SALK + knalk) * XA; // r11: Aerobic endogenous respiration of autotrophic biomass (XAUT) - fluxes[10] = bAUT * SO / (SO + KHO2) * XA; + fluxes[10] = bAUT * SO / (SO + kho2) * XA; // r12: Anoxic endogenous respiration of autotrophic biomass (XAUT) - fluxes[11] = bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; + fluxes[11] = bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2) * XA; // r13: Aeration // TODO: is V in litres? - fluxes[12] = iO2 / V; + fluxes[12] = io2 / V; // Add reaction terms to residual _stoichiometry.multiplyVector(static_cast(fluxes), factor, res); @@ -464,28 +464,28 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); - const double Kh20 = static_cast(p->Kh20); + const double kh20 = static_cast(p->kh20); const double T = static_cast(p->T); - const double iO2 = static_cast(p->iO2); + const double io2 = static_cast(p->io2); const double V = static_cast(p->V); const double k_sto20 = static_cast(p->k_sto20); - const double KX = static_cast(p->KX); - const double KHO2 = static_cast(p->KHO2); - const double KHSS = static_cast(p->KHSS); - const double KHNO3 = static_cast(p->KHNO3); - const double etaHNO3 = static_cast(p->etaHNO3); - const double KHNH4 = static_cast(p->KHNH4); - const double KHALK = static_cast(p->KHALK); - const double KHSTO = static_cast(p->KHSTO); - const double muH20 = static_cast(p->muH20); - const double etaHend = static_cast(p->etaHend); - const double bH20 = static_cast(p->bH20); + const double kx = static_cast(p->kx); + const double kho2 = static_cast(p->kho2); + const double khss = static_cast(p->khss); + const double khn03 = static_cast(p->khn03); + const double etahno3 = static_cast(p->etahno3); + const double khnh4 = static_cast(p->khnh4); + const double khalk = static_cast(p->khalk); + const double khsto = static_cast(p->khsto); + const double muh2o = static_cast(p->muh2o); + const double etahend = static_cast(p->etahend); + const double bh20 = static_cast(p->bh20); const double muAUT20 = static_cast(p->muAUT20); - const double KNO2 = static_cast(p->KNO2); - const double KNNH4 = static_cast(p->KNNH4); - const double KNALK = static_cast(p->KNALK); - const double bAUT20 = static_cast(p->bAUT20); - const double etaNend = static_cast(p->etaNend); + const double kno2 = static_cast(p->kno2); + const double knnh4 = static_cast(p->knnh4); + const double knalk = static_cast(p->knalk); + const double baut20 = static_cast(p->baut20); + const double etanend = static_cast(p->etanend); // derived parameters // derived parameters @@ -493,10 +493,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); const double ft105 = exp(-0.105 * (20.0 - static_cast(T))); const double k_sto = static_cast(k_sto20) * ft07; - const double muH = static_cast(muH20) * ft07; - const double bH = static_cast(bH20) * ft07; + const double muH = static_cast(muh2o) * ft07; + const double bH = static_cast(bh20) * ft07; const double muAUT = static_cast(muAUT20) * ft105; - const double bAUT = static_cast(bAUT20) * ft105; + const double bAUT = static_cast(baut20) * ft105; double SO = y[0]; double SS = y[1]; @@ -529,244 +529,244 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double d[13][13] = {}; - // p1: Hydrolysis: Kh20 * ft04 * XS/XH_S / (XS/XH_S + KX) * XH; + // p1: Hydrolysis: kh20 * ft04 * XS/XH_S / (XS/XH_S + kx) * XH; // Jacobian: - d[0][idxXS] = Kh20 * ft04 - * XH / ((XS + XH * KX) - * (XS + XH * KX)) * XH; - d[0][idxXH] = Kh20 * ft04 - * (XS * XS) / ((XS + KX * XH) - * (XS + KX * XH)); + d[0][idxXS] = kh20 * ft04 + * XH / ((XS + XH * kx) + * (XS + XH * kx)) * XH; + d[0][idxXH] = kh20 * ft04 + * (XS * XS) / ((XS + kx * XH) + * (XS + kx * XH)); if (XH < 0.1) { - d[0][idxXS] = Kh20 * ft04 - * 0.1 / ((XS + 0.1 * KX) * (XS + 0.1 * KX)) * XH; + d[0][idxXS] = kh20 * ft04 + * 0.1 / ((XS + 0.1 * kx) * (XS + 0.1 * kx)) * XH; d[0][idxXH] = 0.0; } - // p2: Aerobic storage of SS: k_sto * SO / (SO + KHO2) * SS / (SS + KHSS) * XH; + // p2: Aerobic storage of SS: k_sto * SO / (SO + kho2) * SS / (SS + khss) * XH; d[1][idxSO] = k_sto - * SS / (SS + KHSS) - * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XH; + * SS / (SS + khss) + * kho2 / ((SO + kho2) * (SO + kho2)) * XH; d[1][idxSS] = k_sto - * SO / (SO + KHO2) - * KHSS / ((SS + KHSS) * (SS + KHSS)) * XH; + * SO / (SO + kho2) + * khss / ((SS + khss) * (SS + khss)) * XH; d[1][idxXH] = k_sto - * SO / (SO + KHO2) - * SS / (SS + KHSS); - - // p3: Anoxic storage of SS: k_sto * etaHNO3 * KHO2 / (SO + KHO2) * SS / (SS + KHSS) * SNO / (SNO + KHNO3) * XH; - d[2][idxSO] = k_sto * etaHNO3 - * -KHO2 / ((SO + KHO2) * (SO + KHO2)) - * SS / (SS + KHSS) - * SNO / (SNO + KHNO3) * XH; - d[2][idxSS] = k_sto * etaHNO3 - * KHO2 / (SO + KHO2) - * KHSS / ((SS + KHSS) * (SS + KHSS)) - * SNO / (SNO + KHNO3) * XH; - d[2][idxSNO] = k_sto * etaHNO3 - * KHO2 / (SO + KHO2) - * SS / (SS + KHSS) - * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[2][idxXH] = k_sto * etaHNO3 - * KHO2 / (SO + KHO2) - * SS / (SS + KHSS) - * SNO / (SNO + KHNO3); - - // p4: Aerobic growth: muH * SO / (SO + KHO2) * SNH / (SNH + KHNH4) * SALK / (SALK + KHALK) * (XSTO/XH_S) / ((XSTO/XH_S) + KHSTO) * XH; + * SO / (SO + kho2) + * SS / (SS + khss); + + // p3: Anoxic storage of SS: k_sto * etahno3 * kho2 / (SO + kho2) * SS / (SS + khss) * SNO / (SNO + khn03) * XH; + d[2][idxSO] = k_sto * etahno3 + * -kho2 / ((SO + kho2) * (SO + kho2)) + * SS / (SS + khss) + * SNO / (SNO + khn03) * XH; + d[2][idxSS] = k_sto * etahno3 + * kho2 / (SO + kho2) + * khss / ((SS + khss) * (SS + khss)) + * SNO / (SNO + khn03) * XH; + d[2][idxSNO] = k_sto * etahno3 + * kho2 / (SO + kho2) + * SS / (SS + khss) + * khn03 / ((SNO + khn03) * (SNO + khn03)) * XH; + d[2][idxXH] = k_sto * etahno3 + * kho2 / (SO + kho2) + * SS / (SS + khss) + * SNO / (SNO + khn03); + + // p4: Aerobic growth: muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (XSTO/XH_S) / ((XSTO/XH_S) + khsto) * XH; d[3][idxSO] = muH - * KHO2 / ((SO + KHO2) * (SO + KHO2)) - * SNH / (SNH + KHNH4) - * SALK / (SALK + KHALK) - * (XSTO / XH) / ((XSTO / XH) + KHSTO) * XH; + * kho2 / ((SO + kho2) * (SO + kho2)) + * SNH / (SNH + khnh4) + * SALK / (SALK + khalk) + * (XSTO / XH) / ((XSTO / XH) + khsto) * XH; d[3][idxSNH] = muH - * SO / (SO + KHO2) - * KHNH4 / ((SNH + KHNH4) * (SNH + KHNH4)) - * SALK / (SALK + KHALK) - * (XSTO / XH) / ((XSTO / XH) + KHSTO) * XH; + * SO / (SO + kho2) + * khnh4 / ((SNH + khnh4) * (SNH + khnh4)) + * SALK / (SALK + khalk) + * (XSTO / XH) / ((XSTO / XH) + khsto) * XH; d[3][idxSALK] = muH - * SO / (SO + KHO2) - * SNH / (SNH + KHNH4) - * KHALK / ((SALK + KHALK) * (SALK + KHALK)) - * (XSTO / XH) / ((XSTO / XH) + KHSTO) * XH; + * SO / (SO + kho2) + * SNH / (SNH + khnh4) + * khalk / ((SALK + khalk) * (SALK + khalk)) + * (XSTO / XH) / ((XSTO / XH) + khsto) * XH; d[3][idxXSTO] = muH - * SO / (SO + KHO2) - * SNH / (SNH + KHNH4) - * SALK / (SALK + KHALK) - * (KHSTO * XH) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)) * XH; + * SO / (SO + kho2) + * SNH / (SNH + khnh4) + * SALK / (SALK + khalk) + * (khsto * XH) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)) * XH; d[3][idxXH] = muH - * SO / (SO + KHO2) - * SNH / (SNH + KHNH4) - * SALK / (SALK + KHALK) - * (XSTO * XSTO) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)); + * SO / (SO + kho2) + * SNH / (SNH + khnh4) + * SALK / (SALK + khalk) + * (XSTO * XSTO) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)); if (XH < 0.1) { d[3][idxSO] = muH - * -KHO2 / ((SO + KHO2) * (SO + KHO2)) - * SNH / (SNH + KHNH4) - * SALK / (SALK + KHALK) - * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * 0.1; + * -kho2 / ((SO + kho2) * (SO + kho2)) + * SNH / (SNH + khnh4) + * SALK / (SALK + khalk) + * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; d[3][idxSNH] = muH - * SO / (SO + KHO2) - * KHNH4 / ((SNH + KHNH4) * (SNH + KHNH4)) - * SALK / (SALK + KHALK) - * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * 0.1; + * SO / (SO + kho2) + * khnh4 / ((SNH + khnh4) * (SNH + khnh4)) + * SALK / (SALK + khalk) + * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; d[3][idxSALK] = muH - * SO / (SO + KHO2) - * SNH / (SNH + KHNH4) - * KHALK / ((SALK + KHALK) * (SALK + KHALK)) - * (XSTO / 0.1) / ((XSTO / 0.1) + KHSTO) * 0.1; + * SO / (SO + kho2) + * SNH / (SNH + khnh4) + * khalk / ((SALK + khalk) * (SALK + khalk)) + * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; d[3][idxXSTO] = muH - * SO / (SO + KHO2) - * SNH / (SNH + KHNH4) - * SALK / (SALK + KHALK) - * (KHSTO * 0.1) / ((XSTO + KHSTO * 0.1) * (XSTO + KHSTO * 0.1)) * 0.1; + * SO / (SO + kho2) + * SNH / (SNH + khnh4) + * SALK / (SALK + khalk) + * (khsto * 0.1) / ((XSTO + khsto * 0.1) * (XSTO + khsto * 0.1)) * 0.1; d[3][idxXH] = 0.0; } - // p5: Anoxic growth: muH * etaHNO3 * KHO2 / (KHO2 + SO) * SNH / (KHNH4 + SNH) * SALK / (KHALK + SALK) * SNO / (KHNO3 + SNO)* (XSTO / XH_S) / (KHSTO + (XSTO/XH)_S) * XH; - d[4][idxSO] = muH * etaHNO3 - * -KHO2 / ((KHO2 + SO) * (KHO2 + SO)) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) - * (XSTO / XH) / (KHSTO + (XSTO / XH)) - * SNO / (KHNO3 + SNO) * XH; - d[4][idxSNH] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * KHNH4 / ((KHNH4 + SNH) * (KHNH4 + SNH)) - * SALK / (KHALK + SALK) - * (XSTO / XH) / (KHSTO + (XSTO / XH)) - * SNO / (KHNO3 + SNO) * XH; - d[4][idxSALK] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * KHALK / ((KHALK + SALK) * (KHALK + SALK)) - * (XSTO / XH) / (KHSTO + (XSTO / XH)) - * SNO / (KHNO3 + SNO) * XH; - d[4][idxSNO] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) - * (XSTO / XH) / (KHSTO + (XSTO / XH)) - * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * XH; - d[4][idxXSTO] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) + // p5: Anoxic growth: muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * SNO / (khn03 + SNO)* (XSTO / XH_S) / (khsto + (XSTO/XH)_S) * XH; + d[4][idxSO] = muH * etahno3 + * -kho2 / ((kho2 + SO) * (kho2 + SO)) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) + * (XSTO / XH) / (khsto + (XSTO / XH)) + * SNO / (khn03 + SNO) * XH; + d[4][idxSNH] = muH * etahno3 + * kho2 / (kho2 + SO) + * khnh4 / ((khnh4 + SNH) * (khnh4 + SNH)) + * SALK / (khalk + SALK) + * (XSTO / XH) / (khsto + (XSTO / XH)) + * SNO / (khn03 + SNO) * XH; + d[4][idxSALK] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * khalk / ((khalk + SALK) * (khalk + SALK)) + * (XSTO / XH) / (khsto + (XSTO / XH)) + * SNO / (khn03 + SNO) * XH; + d[4][idxSNO] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) + * (XSTO / XH) / (khsto + (XSTO / XH)) + * khn03 / ((khn03 + SNO) * (khn03 + SNO)) * XH; + d[4][idxXSTO] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) * (1.0 / XH) - * SNO / (KHNO3 + SNO) - * (KHSTO * XH * XH) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)) * XH; - d[4][idxXH] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) - * SNO / (KHNO3 + SNO) - * (XSTO * XSTO) / ((XSTO + KHSTO * XH) * (XSTO + KHSTO * XH)); + * SNO / (khn03 + SNO) + * (khsto * XH * XH) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)) * XH; + d[4][idxXH] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) + * SNO / (khn03 + SNO) + * (XSTO * XSTO) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)); if (XH < 0.1) { - d[4][idxSO] = muH * etaHNO3 - * -KHO2 / ((KHO2 + SO) * (KHO2 + SO)) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) - * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) - * SNO / (KHNO3 + SNO) * 0.1; - d[4][idxSNH] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * KHNH4 / ((KHNH4 + SNH) * (KHNH4 + SNH)) - * SALK / (KHALK + SALK) - * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) - * SNO / (KHNO3 + SNO) * 0.1; - d[4][idxSALK] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * KHALK / ((KHALK + SALK) * (KHALK + SALK)) - * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) - * SNO / (KHNO3 + SNO) * 0.1; - d[4][idxSNO] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) - * (XSTO / 0.1) / (KHSTO + (XSTO / 0.1)) - * KHNO3 / ((KHNO3 + SNO) * (KHNO3 + SNO)) * 0.1; - d[4][idxXSTO] = muH * etaHNO3 - * KHO2 / (KHO2 + SO) - * SNH / (KHNH4 + SNH) - * SALK / (KHALK + SALK) + d[4][idxSO] = muH * etahno3 + * -kho2 / ((kho2 + SO) * (kho2 + SO)) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) + * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) + * SNO / (khn03 + SNO) * 0.1; + d[4][idxSNH] = muH * etahno3 + * kho2 / (kho2 + SO) + * khnh4 / ((khnh4 + SNH) * (khnh4 + SNH)) + * SALK / (khalk + SALK) + * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) + * SNO / (khn03 + SNO) * 0.1; + d[4][idxSALK] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * khalk / ((khalk + SALK) * (khalk + SALK)) + * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) + * SNO / (khn03 + SNO) * 0.1; + d[4][idxSNO] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) + * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) + * khn03 / ((khn03 + SNO) * (khn03 + SNO)) * 0.1; + d[4][idxXSTO] = muH * etahno3 + * kho2 / (kho2 + SO) + * SNH / (khnh4 + SNH) + * SALK / (khalk + SALK) * (1.0 / 0.1) - * SNO / (KHNO3 + SNO) - * (KHSTO * 0.1 * 0.1) / ((XSTO + KHSTO * 0.1) * (XSTO + KHSTO * 0.1)) * 0.1; + * SNO / (khn03 + SNO) + * (khsto * 0.1 * 0.1) / ((XSTO + khsto * 0.1) * (XSTO + khsto * 0.1)) * 0.1; d[4][idxXH] = 0.0; } - //reaction6: bH * SO / (SO + KHO2) * XH; + //reaction6: bH * SO / (SO + kho2) * XH; d[5][idxSO] = bH - * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XH; - d[5][idxXH] = bH * SO / (SO + KHO2); - - //reaction7: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XH; - d[6][idxSO] = bH * etaHend - * -KHO2 / ((SO + KHO2) * (SO + KHO2)) - * SNO / (SNO + KHNO3) * XH; - d[6][idxSNO] = bH * etaHend - * KHO2 / (SO + KHO2) - * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XH; - d[6][idxXH] = bH * etaHend - * KHO2 / (SO + KHO2) - * SNO / (SNO + KHNO3); - - //reaction8: bH * SO / (SO + KHO2) * XSTO; + * kho2 / ((SO + kho2) * (SO + kho2)) * XH; + d[5][idxXH] = bH * SO / (SO + kho2); + + //reaction7: bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03) * XH; + d[6][idxSO] = bH * etahend + * -kho2 / ((SO + kho2) * (SO + kho2)) + * SNO / (SNO + khn03) * XH; + d[6][idxSNO] = bH * etahend + * kho2 / (SO + kho2) + * khn03 / ((SNO + khn03) * (SNO + khn03)) * XH; + d[6][idxXH] = bH * etahend + * kho2 / (SO + kho2) + * SNO / (SNO + khn03); + + //reaction8: bH * SO / (SO + kho2) * XSTO; d[7][idxSO] = bH - * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XSTO; - d[7][idxXSTO] = bH * SO / (SO + KHO2); - - //reaction9: bH * etaHend * KHO2 / (SO + KHO2) * SNO / (SNO + KHNO3) * XSTO; - d[8][idxSO] = bH * etaHend - * -KHO2 / ((SO + KHO2) * (SO + KHO2)) - * SNO / (SNO + KHNO3) * XSTO; - d[8][idxSNO] = bH * etaHend - * KHO2 / (SO + KHO2) - * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XSTO; - d[8][idxXSTO] = bH * etaHend - * KHO2 / (SO + KHO2) - * SNO / (SNO + KHNO3); - - //reaction10: muAUT * SO / (SO + KNO2) * SNH / (SNH + KNNH4) * SALK / (SALK + KNALK) * XA; + * kho2 / ((SO + kho2) * (SO + kho2)) * XSTO; + d[7][idxXSTO] = bH * SO / (SO + kho2); + + //reaction9: bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03) * XSTO; + d[8][idxSO] = bH * etahend + * -kho2 / ((SO + kho2) * (SO + kho2)) + * SNO / (SNO + khn03) * XSTO; + d[8][idxSNO] = bH * etahend + * kho2 / (SO + kho2) + * khn03 / ((SNO + khn03) * (SNO + khn03)) * XSTO; + d[8][idxXSTO] = bH * etahend + * kho2 / (SO + kho2) + * SNO / (SNO + khn03); + + //reaction10: muAUT * SO / (SO + kno2) * SNH / (SNH + knnh4) * SALK / (SALK + knalk) * XA; d[9][idxSO] = muAUT - * KNO2 / ((SO + KNO2) * (SO + KNO2)) - * SNH / (SNH + KNNH4) - * SALK / (SALK + KNALK) * XA; + * kno2 / ((SO + kno2) * (SO + kno2)) + * SNH / (SNH + knnh4) + * SALK / (SALK + knalk) * XA; d[9][idxSALK] = muAUT - * SO / (SO + KNO2) - * SNH / (SNH + KNNH4) - * KNALK / ((SALK + KNALK) * (SALK + KNALK)) * XA; + * SO / (SO + kno2) + * SNH / (SNH + knnh4) + * knalk / ((SALK + knalk) * (SALK + knalk)) * XA; d[9][idxSNH] = muAUT - * SO / (SO + KNO2) - * SALK / (SALK + KNALK) - * KNNH4 / ((SNH + KNNH4) * (SNH + KNNH4)) * XA; + * SO / (SO + kno2) + * SALK / (SALK + knalk) + * knnh4 / ((SNH + knnh4) * (SNH + knnh4)) * XA; d[9][idxXA] = muAUT - * SO / (SO + KNO2) - * SNH / (SNH + KNNH4) - * SALK / (SALK + KNALK); + * SO / (SO + kno2) + * SNH / (SNH + knnh4) + * SALK / (SALK + knalk); - //reaction11: bAUT * SO / (SO + KHO2) * XA; + //reaction11: bAUT * SO / (SO + kho2) * XA; d[10][idxSO] = bAUT - * KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; + * kho2 / ((SO + kho2) * (SO + kho2)) * XA; d[10][idxXA] = bAUT - * SO / (SO + KHO2); - - //reaction12: bAUT * etaNend * SNO / (SNO + KHNO3) * KHO2 / (SO + KHO2) * XA; - d[11][idxSO] = bAUT * etaNend - * SNO / (SNO + KHNO3) - * -KHO2 / ((SO + KHO2) * (SO + KHO2)) * XA; - d[11][idxSNO] = bAUT * etaNend - * KHO2 / (SO + KHO2) - * KHNO3 / ((SNO + KHNO3) * (SNO + KHNO3)) * XA; - d[11][idxXA] = bAUT * etaNend - * SNO / (SNO + KHNO3) - * KHO2 / (SO + KHO2); + * SO / (SO + kho2); + + //reaction12: bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2) * XA; + d[11][idxSO] = bAUT * etanend + * SNO / (SNO + khn03) + * -kho2 / ((SO + kho2) * (SO + kho2)) * XA; + d[11][idxSNO] = bAUT * etanend + * kho2 / (SO + kho2) + * khn03 / ((SNO + khn03) * (SNO + khn03)) * XA; + d[11][idxXA] = bAUT * etanend + * SNO / (SNO + khn03) + * kho2 / (SO + kho2); RowIterator curJac = jac; From 94a0a999e1656784e973f3ae359cea8e504b5910 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Fri, 6 Jun 2025 08:43:13 +0200 Subject: [PATCH 32/48] add example set up for cadet_python to doc --- .../reaction/activated_sludge_model.rst | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst index 71c9c98b2..1e65b3704 100644 --- a/doc/interface/reaction/activated_sludge_model.rst +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -194,3 +194,58 @@ Saturation/inhibition coefficients ================ ============================= ======================================================== **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== + +Example +------- + +.. python:: + + # Example of setting up an ASM3 reaction model in a unit operation with bulk reaction + + # Setup ASM3 reaction for unit 000 with example values + model.root.input.model.unit_000.reaction_model = 'ACTIVATED_SLUDGE_MODEL3' + model.root.input.model.unit_000.reaction_bulk.asm3_insi = 0.01 + model.root.input.model.unit_000.reaction_bulk.asm3_inss = 0.03 + model.root.input.model.unit_000.reaction_bulk.asm3_inxi = 0.04 + model.root.input.model.unit_000.reaction_bulk.asm3_inxs = 0.03 + model.root.input.model.unit_000.reaction_bulk.asm3_inbm = 0.07 + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_xi = 0.751879699 # not used + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_xs = 0.555555556 # not used + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_sto = 0.6 # not used + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_bm = 0.704225352 + model.root.input.model.unit_000.reaction_bulk.asm3_itss_vss_bm = 1.086956522 + + + model.root.input.model.unit_000.reaction_bulk.asm3_fiss_bm_prod = 1 + model.root.input.model.unit_000.reaction_bulk.asm3_fsi = 0 + model.root.input.model.unit_000.reaction_bulk.asm3_yh_aer = 0.8 + model.root.input.model.unit_000.reaction_bulk.asm3_yh_anox = 0.65 + + model.root.input.model.unit_000.reaction_bulk.asm3_ysto_aer = 0.8375 + model.root.input.model.unit_000.reaction_bulk.asm3_ysto_anox = 0.7 + model.root.input.model.unit_000.reaction_bulk.asm3_fxi = 0.2 + model.root.input.model.unit_000.reaction_bulk.asm3_ya = 0.24 + model.root.input.model.unit_000.reaction_bulk.asm3_kh20 = 9 + model.root.input.model.unit_000.reaction_bulk.asm3_kx = 1 + model.root.input.model.unit_000.reaction_bulk.asm3_ksto20 = 12 + model.root.input.model.unit_000.reaction_bulk.asm3_mu_h20 = 3 + model.root.input.model.unit_000.reaction_bulk.asm3_bh20 = 0.33 + model.root.input.model.unit_000.reaction_bulk.asm3_eta_hno3 = 0.5 + model.root.input.model.unit_000.reaction_bulk.asm3_khO2 = 0.2 + model.root.input.model.unit_000.reaction_bulk.asm3_khss = 10 + model.root.input.model.unit_000.reaction_bulk.asm3_khno3 = 0.5 + model.root.input.model.unit_000.reaction_bulk.asm3_khnh4 = 0.01 + model.root.input.model.unit_000.reaction_bulk.asm3_khalk = 0.1 + model.root.input.model.unit_000.reaction_bulk.asm3_khsto = 0.1 + model.root.input.model.unit_000.reaction_bulk.asm3_mu_aut20 = 1.12 + model.root.input.model.unit_000.reaction_bulk.asm3_baut20 = 0.18 + model.root.input.model.unit_000.reaction_bulk.asm3_etah_end = 0.5 + model.root.input.model.unit_000.reaction_bulk.asm3_etan_end = 0.5 + model.root.input.model.unit_000.reaction_bulk.asm3_kno2 = 0.5 + model.root.input.model.unit_000.reaction_bulk.asm3_knnh4 = 0.7 + model.root.input.model.unit_000.reaction_bulk.asm3_knalk = 0.5 + model.root.input.model.unit_000.reaction_bulk.asm3_t = 12 + + + model.root.input.model.unit_000.reaction_bulk.asm3_v = 1000.0 + model.root.input.model.unit_000.reaction_bulk.asm3_io2 = 0.0 From 1d92cd13a9bf17783bcade1acc4d22761986abe0 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Fri, 6 Jun 2025 08:43:54 +0200 Subject: [PATCH 33/48] fixup! add example set up for cadet_python to doc --- doc/interface/reaction/activated_sludge_model.rst | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst index 1e65b3704..8093f2e9b 100644 --- a/doc/interface/reaction/activated_sludge_model.rst +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -209,9 +209,9 @@ Example model.root.input.model.unit_000.reaction_bulk.asm3_inxi = 0.04 model.root.input.model.unit_000.reaction_bulk.asm3_inxs = 0.03 model.root.input.model.unit_000.reaction_bulk.asm3_inbm = 0.07 - model.root.input.model.unit_000.reaction_bulk.asm3_ivss_xi = 0.751879699 # not used - model.root.input.model.unit_000.reaction_bulk.asm3_ivss_xs = 0.555555556 # not used - model.root.input.model.unit_000.reaction_bulk.asm3_ivss_sto = 0.6 # not used + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_xi = 0.751879699 + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_xs = 0.555555556 + model.root.input.model.unit_000.reaction_bulk.asm3_ivss_sto = 0.6 model.root.input.model.unit_000.reaction_bulk.asm3_ivss_bm = 0.704225352 model.root.input.model.unit_000.reaction_bulk.asm3_itss_vss_bm = 1.086956522 From ad9355f6436c5279ae0bf7f7981c9d09a251e47a Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Sat, 7 Jun 2025 10:55:18 +0200 Subject: [PATCH 34/48] start error messaging --- .../reaction/ActivatedSludgeModelThree.cpp | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 46e65bbac..19581f50b 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -195,10 +195,16 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double fXI = paramProvider.getDouble("ASM3_FXI"); const double YH_aer = paramProvider.getDouble("ASM3_YH_AER"); + if (YH_aer < 1e-16) + throw InvalidParameterException("ASM3 configuration: YH_aer must be bigger than zero"); const double YH_anox = paramProvider.getDouble("ASM3_YH_ANOX"); const double YSTO_aer = paramProvider.getDouble("ASM3_YSTO_AER"); + if (YSTO_aer < 1e-16) + throw InvalidParameterException("ASM3 configuration: YSTO_aer must be bigger than zero"); const double YSTO_anox = paramProvider.getDouble("ASM3_YSTO_ANOX"); const double YA = paramProvider.getDouble("ASM3_YA"); + if (YA < 1e-16) + throw InvalidParameterException("ASM3 configuration: YA must be bigger than zero"); const double fiSS_BM_prod = paramProvider.getDouble("ASM3_FISS_BM_PROD"); const double iVSS_BM = paramProvider.getDouble("ASM3_IVSS_BM"); @@ -464,6 +470,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase { typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); + //parameters const double kh20 = static_cast(p->kh20); const double T = static_cast(p->T); const double io2 = static_cast(p->io2); @@ -487,7 +494,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double baut20 = static_cast(p->baut20); const double etanend = static_cast(p->etanend); - // derived parameters // derived parameters const double ft04 = exp(-0.04 * (20.0 - static_cast(T))); const double ft07 = exp(-0.06952 * (20.0 - static_cast(T))); @@ -530,7 +536,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double d[13][13] = {}; // p1: Hydrolysis: kh20 * ft04 * XS/XH_S / (XS/XH_S + kx) * XH; -// Jacobian: d[0][idxXS] = kh20 * ft04 * XH / ((XS + XH * kx) * (XS + XH * kx)) * XH; @@ -770,11 +775,14 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase RowIterator curJac = jac; - for (size_t rIdx = 0; rIdx < 13; rIdx++) { + for (size_t rIdx = 0; rIdx < 13; rIdx++) + { RowIterator curJac = jac; - for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { + for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) + { const double colFactor = static_cast(_stoichiometry.native(row, rIdx)); - for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) { + for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) + { curJac[compIdx - static_cast(row)] += colFactor * d[rIdx][compIdx]; } From 8ab57cfe515f7b87b284ffdbb3a16428bf147de6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Sat, 21 Jun 2025 15:38:23 +0200 Subject: [PATCH 35/48] configurable component index --- .../reaction/ActivatedSludgeModelThree.cpp | 75 +++++++++++++++---- 1 file changed, 62 insertions(+), 13 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 19581f50b..4c3f81278 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -166,6 +166,20 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase ParamHandler_t _paramHandler; //!< Handles parameters and their dependence on external functions linalg::ActiveDenseMatrix _stoichiometry; + + unsigned int _idxSO; //!< SO component index, default 0 + unsigned int _idxSS; //!< SS component index, default 1 + unsigned int _idxSNH; //!< SNH component index, default 2 + unsigned int _idxSNO; //!< SNO component index, default 3 + unsigned int _idxSN2; //!< SN2 component index, default 4 + unsigned int _idxSALK; //!< SALK component index, default 5 + unsigned int _idxSI; //!< SI component index, default 6 + unsigned int _idxXI; //!< XI component index, default 7 + unsigned int _idxXS; //!< XS component index, default 8 + unsigned int _idxXH; //!< XH component index, default 9 + unsigned int _idxXSTO; //!< XSTO component index, default 10 + unsigned int _idxXA; //!< XA component index, default 11 + unsigned int _idxXMI; //!< XMI component index, default 12 @@ -184,6 +198,41 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.resize(_nComp, 13); _stoichiometry.setAll(0); + const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); + if (compIdx.size() == 0) { + LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; + _idxSO = 0; + _idxSS = 1; + _idxSNH = 2; + _idxSNO = 3; + _idxSN2 = 4; + _idxSALK = 5; + _idxSI = 6; + _idxXI = 7; + _idxXS = 8; + _idxXH = 9; + _idxXSTO = 10; + _idxXA = 11; + _idxXMI = 12; + } else if (compIdx.size() != 13) { + throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); + } else { + LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; + _idxSO = compIdx[0]; + _idxSS = compIdx[1]; + _idxSNH = compIdx[2]; + _idxSNO = compIdx[3]; + _idxSN2 = compIdx[4]; + _idxSALK = compIdx[5]; + _idxSI = compIdx[6]; + _idxXI = compIdx[7]; + _idxXS = compIdx[8]; + _idxXH = compIdx[9]; + _idxXSTO = compIdx[10]; + _idxXA = compIdx[11]; + _idxXMI = compIdx[12]; + } + // parameter set ASM3h const double iNSI = paramProvider.getDouble("ASM3_INSI"); const double iNSS = paramProvider.getDouble("ASM3_INSS"); @@ -383,19 +432,19 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double muAUT = static_cast(muAUT20) * ft105; const double bAUT = static_cast(baut20) * ft105; - StateType SO = y[0]; - StateType SS = y[1]; - StateType SNH = y[2]; - StateType SNO = y[3]; - StateType SN2 = y[4]; - StateType SALK = y[5]; - // StateType SI = y[6]; // unused - // StateType XI = y[7]; // unused - StateType XS = y[8]; - StateType XH = y[9]; - StateType XSTO = y[10]; - StateType XA = y[11]; - // StateType XMI = y[12]; // unused + StateType SO = y[_idxSO]; + StateType SS = y[_idxSS]; + StateType SNH = y[_idxSNH]; + StateType SNO = y[_idxSNO]; + StateType SN2 = y[_idxSN2]; + StateType SALK = y[_idxSALK]; + // StateType SI = y[_idxSI]; // unused + // StateType XI = y[_idxXI]; // unused + StateType XS = y[_idxXS]; + StateType XH = y[_idxXH]; + StateType XSTO = y[_idxXSTO]; + StateType XA = y[_idxXA]; + // StateType XMI = y[_idxXMI]; // unused // p1: Hydrolysis of organic structures From 3f406b19846b81e55a856e92e11c7a27c5e209ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Sun, 22 Jun 2025 14:04:20 +0200 Subject: [PATCH 36/48] check ASM3_COMP_IDX exists before reading --- .../reaction/ActivatedSludgeModelThree.cpp | 40 ++++++++++--------- 1 file changed, 21 insertions(+), 19 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 4c3f81278..2aa2f65a7 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -198,8 +198,27 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.resize(_nComp, 13); _stoichiometry.setAll(0); - const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); - if (compIdx.size() == 0) { + if (paramProvider.exists("ASM3_COMP_IDX")) { + const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); + if (compIdx.size() != 13) { + throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); + } else { + LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; + _idxSO = compIdx[0]; + _idxSS = compIdx[1]; + _idxSNH = compIdx[2]; + _idxSNO = compIdx[3]; + _idxSN2 = compIdx[4]; + _idxSALK = compIdx[5]; + _idxSI = compIdx[6]; + _idxXI = compIdx[7]; + _idxXS = compIdx[8]; + _idxXH = compIdx[9]; + _idxXSTO = compIdx[10]; + _idxXA = compIdx[11]; + _idxXMI = compIdx[12]; + } + } else { LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; _idxSO = 0; _idxSS = 1; @@ -214,23 +233,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _idxXSTO = 10; _idxXA = 11; _idxXMI = 12; - } else if (compIdx.size() != 13) { - throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); - } else { - LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; - _idxSO = compIdx[0]; - _idxSS = compIdx[1]; - _idxSNH = compIdx[2]; - _idxSNO = compIdx[3]; - _idxSN2 = compIdx[4]; - _idxSALK = compIdx[5]; - _idxSI = compIdx[6]; - _idxXI = compIdx[7]; - _idxXS = compIdx[8]; - _idxXH = compIdx[9]; - _idxXSTO = compIdx[10]; - _idxXA = compIdx[11]; - _idxXMI = compIdx[12]; } // parameter set ASM3h From a39e4cdd793fcbf2e7f4fb3941b3fdb6bff11435 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Sun, 22 Jun 2025 18:05:41 +0200 Subject: [PATCH 37/48] document ASM3_COMP_IDX, use bold sub headings --- .../reaction/activated_sludge_model.rst | 45 ++++++++++++++----- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/doc/interface/reaction/activated_sludge_model.rst b/doc/interface/reaction/activated_sludge_model.rst index 8093f2e9b..183e4ed47 100644 --- a/doc/interface/reaction/activated_sludge_model.rst +++ b/doc/interface/reaction/activated_sludge_model.rst @@ -7,8 +7,37 @@ Activated Sludge Model (ASM3h) For information on model equations, refer to :ref:`activated_sludge_model`. -Environment/process parameters ----- +**Component configuration** + +``ASM3_COMP_IDX`` + + Optional component indexes. Set this in case the relevant components start at a certain offset or are provided + in a different order than listed below: + + ============= ========= + **Component** **Index** + ============= ========= + SO 0 + SS 1 + SNH 2 + SNO 3 + SN2 4 + SALK 5 + SI 6 + XI 7 + XS 8 + XH 9 + XSTO 10 + XA 11 + XMI 12 + ============= ========= + + + ================ ============================= ======================================================== + **Type:** double **Range:** :math:`\mathbb{N}` **Length:** 13 + ================ ============================= ======================================================== + +**Environment/process parameters** ``ASM3_T`` @@ -34,8 +63,7 @@ Environment/process parameters **Type:** double **Range:** :math:`\ge 0` **Length:** 1 ================ ============================= ======================================================== -Maximum rates ----- +**Maximum rates** ``ASM3_KH20`` @@ -85,8 +113,7 @@ Maximum rates **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -Anoxic reduction factors ----- +**Anoxic reduction factors** ``ASM3_ETA_HNO3`` @@ -112,8 +139,7 @@ Anoxic reduction factors **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -Saturation/inhibition coefficients ----- +**Saturation/inhibition coefficients** ``ASM3_KX`` @@ -195,8 +221,7 @@ Saturation/inhibition coefficients **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 ================ ============================= ======================================================== -Example -------- +**Example** .. python:: From 775b437812855bc56a2e93845913c9782626db0a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Sun, 22 Jun 2025 22:47:18 +0200 Subject: [PATCH 38/48] update stoichimoetry using custom indexes --- .../reaction/ActivatedSludgeModelThree.cpp | 197 ++++++++---------- 1 file changed, 92 insertions(+), 105 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 2aa2f65a7..fb92ff237 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -167,19 +167,19 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase linalg::ActiveDenseMatrix _stoichiometry; - unsigned int _idxSO; //!< SO component index, default 0 - unsigned int _idxSS; //!< SS component index, default 1 - unsigned int _idxSNH; //!< SNH component index, default 2 - unsigned int _idxSNO; //!< SNO component index, default 3 - unsigned int _idxSN2; //!< SN2 component index, default 4 - unsigned int _idxSALK; //!< SALK component index, default 5 - unsigned int _idxSI; //!< SI component index, default 6 - unsigned int _idxXI; //!< XI component index, default 7 - unsigned int _idxXS; //!< XS component index, default 8 - unsigned int _idxXH; //!< XH component index, default 9 - unsigned int _idxXSTO; //!< XSTO component index, default 10 - unsigned int _idxXA; //!< XA component index, default 11 - unsigned int _idxXMI; //!< XMI component index, default 12 + unsigned int _idxSO = 0; //!< SO component index, default 0 + unsigned int _idxSS = 1; //!< SS component index, default 1 + unsigned int _idxSNH = 2; //!< SNH component index, default 2 + unsigned int _idxSNO = 3; //!< SNO component index, default 3 + unsigned int _idxSN2 = 4; //!< SN2 component index, default 4 + unsigned int _idxSALK = 5; //!< SALK component index, default 5 + unsigned int _idxSI = 6; //!< SI component index, default 6 + unsigned int _idxXI = 7; //!< XI component index, default 7 + unsigned int _idxXS = 8; //!< XS component index, default 8 + unsigned int _idxXH = 9; //!< XH component index, default 9 + unsigned int _idxXSTO = 10; //!< XSTO component index, default 10 + unsigned int _idxXA = 11; //!< XA component index, default 11 + unsigned int _idxXMI = 12; //!< XMI component index, default 12 @@ -220,19 +220,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase } } else { LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; - _idxSO = 0; - _idxSS = 1; - _idxSNH = 2; - _idxSNO = 3; - _idxSN2 = 4; - _idxSALK = 5; - _idxSI = 6; - _idxXI = 7; - _idxXS = 8; - _idxXH = 9; - _idxXSTO = 10; - _idxXA = 11; - _idxXMI = 12; } // parameter set ASM3h @@ -292,95 +279,95 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double c12a = (c12n - c12no) / 14.0; // SO - _stoichiometry.native(0, 1) = YSTO_aer - 1; - _stoichiometry.native(0, 3) = 1 - 1 / YH_aer; - _stoichiometry.native(0, 5) = -1 * (1 - fXI); - _stoichiometry.native(0, 7) = -1; - _stoichiometry.native(0, 9) = -(64.0/14.0) * 1/YA + 1; - _stoichiometry.native(0, 10) = -1 * (1 - fXI); - _stoichiometry.native(0, 12) = 1; + _stoichiometry.native(_idxSO, 1) = YSTO_aer - 1; + _stoichiometry.native(_idxSO, 3) = 1 - 1 / YH_aer; + _stoichiometry.native(_idxSO, 5) = -1 * (1 - fXI); + _stoichiometry.native(_idxSO, 7) = -1; + _stoichiometry.native(_idxSO, 9) = -(64.0/14.0) * 1/YA + 1; + _stoichiometry.native(_idxSO, 10) = -1 * (1 - fXI); + _stoichiometry.native(_idxSO, 12) = 1; // SS - _stoichiometry.native(1, 0) = 1 - fSI; - _stoichiometry.native(1, 1) = -1; - _stoichiometry.native(1, 2) = -1; + _stoichiometry.native(_idxSS, 0) = 1 - fSI; + _stoichiometry.native(_idxSS, 1) = -1; + _stoichiometry.native(_idxSS, 2) = -1; // SNH - _stoichiometry.native(2, 0) = c1n; - _stoichiometry.native(2, 1) = c2n; - _stoichiometry.native(2, 2) = c3n; - _stoichiometry.native(2, 3) = c4n; - _stoichiometry.native(2, 4) = c5n; - _stoichiometry.native(2, 5) = c6n; - _stoichiometry.native(2, 6) = c7n; - _stoichiometry.native(2, 9) = c10n; - _stoichiometry.native(2, 10) = c11n; - _stoichiometry.native(2, 11) = c12n; + _stoichiometry.native(_idxSNH, 0) = c1n; + _stoichiometry.native(_idxSNH, 1) = c2n; + _stoichiometry.native(_idxSNH, 2) = c3n; + _stoichiometry.native(_idxSNH, 3) = c4n; + _stoichiometry.native(_idxSNH, 4) = c5n; + _stoichiometry.native(_idxSNH, 5) = c6n; + _stoichiometry.native(_idxSNH, 6) = c7n; + _stoichiometry.native(_idxSNH, 9) = c10n; + _stoichiometry.native(_idxSNH, 10) = c11n; + _stoichiometry.native(_idxSNH, 11) = c12n; // SNO - _stoichiometry.native(3, 2) = c3no; - _stoichiometry.native(3, 4) = c5no; - _stoichiometry.native(3, 6) = c7no; - _stoichiometry.native(3, 8) = c9no; - _stoichiometry.native(3, 9) = c10no; - _stoichiometry.native(3, 11) = c12no; + _stoichiometry.native(_idxSNO, 2) = c3no; + _stoichiometry.native(_idxSNO, 4) = c5no; + _stoichiometry.native(_idxSNO, 6) = c7no; + _stoichiometry.native(_idxSNO, 8) = c9no; + _stoichiometry.native(_idxSNO, 9) = c10no; + _stoichiometry.native(_idxSNO, 11) = c12no; // SN2 - _stoichiometry.native(4, 2) = -c3no; - _stoichiometry.native(4, 4) = -c5no; - _stoichiometry.native(4, 6) = -c7no; - _stoichiometry.native(4, 8) = -c9no; - _stoichiometry.native(4, 11) = -c12no; + _stoichiometry.native(_idxSN2, 2) = -c3no; + _stoichiometry.native(_idxSN2, 4) = -c5no; + _stoichiometry.native(_idxSN2, 6) = -c7no; + _stoichiometry.native(_idxSN2, 8) = -c9no; + _stoichiometry.native(_idxSN2, 11) = -c12no; // SALK - _stoichiometry.native(5, 0) = c1a; - _stoichiometry.native(5, 1) = c2a; - _stoichiometry.native(5, 2) = c3a; - _stoichiometry.native(5, 3) = c4a; - _stoichiometry.native(5, 4) = c5a; - _stoichiometry.native(5, 5) = c6a; - _stoichiometry.native(5, 6) = c7a; - _stoichiometry.native(5, 8) = c9a; - _stoichiometry.native(5, 9) = c10a; - _stoichiometry.native(5, 10) = c11a; - _stoichiometry.native(5, 11) = c12a; + _stoichiometry.native(_idxSALK, 0) = c1a; + _stoichiometry.native(_idxSALK, 1) = c2a; + _stoichiometry.native(_idxSALK, 2) = c3a; + _stoichiometry.native(_idxSALK, 3) = c4a; + _stoichiometry.native(_idxSALK, 4) = c5a; + _stoichiometry.native(_idxSALK, 5) = c6a; + _stoichiometry.native(_idxSALK, 6) = c7a; + _stoichiometry.native(_idxSALK, 8) = c9a; + _stoichiometry.native(_idxSALK, 9) = c10a; + _stoichiometry.native(_idxSALK, 10) = c11a; + _stoichiometry.native(_idxSALK, 11) = c12a; // SI - _stoichiometry.native(6, 0) = fSI; + _stoichiometry.native(_idxSI, 0) = fSI; // XI - _stoichiometry.native(7, 5) = fXI; - _stoichiometry.native(7, 6) = fXI; - _stoichiometry.native(7, 10) = fXI; - _stoichiometry.native(7, 11) = fXI; + _stoichiometry.native(_idxXI, 5) = fXI; + _stoichiometry.native(_idxXI, 6) = fXI; + _stoichiometry.native(_idxXI, 10) = fXI; + _stoichiometry.native(_idxXI, 11) = fXI; // XS - _stoichiometry.native(8, 0) = -1; + _stoichiometry.native(_idxXS, 0) = -1; // XH - _stoichiometry.native(9, 3) = 1; - _stoichiometry.native(9, 4) = 1; - _stoichiometry.native(9, 5) = -1; - _stoichiometry.native(9, 6) = -1; + _stoichiometry.native(_idxXH, 3) = 1; + _stoichiometry.native(_idxXH, 4) = 1; + _stoichiometry.native(_idxXH, 5) = -1; + _stoichiometry.native(_idxXH, 6) = -1; // XSTO - _stoichiometry.native(10, 1) = YSTO_aer; - _stoichiometry.native(10, 2) = YSTO_anox; - _stoichiometry.native(10, 3) = -1 / YH_aer; - _stoichiometry.native(10, 4) = -1 / YH_anox; - _stoichiometry.native(10, 7) = -1; - _stoichiometry.native(10, 8) = -1; + _stoichiometry.native(_idxXSTO, 1) = YSTO_aer; + _stoichiometry.native(_idxXSTO, 2) = YSTO_anox; + _stoichiometry.native(_idxXSTO, 3) = -1 / YH_aer; + _stoichiometry.native(_idxXSTO, 4) = -1 / YH_anox; + _stoichiometry.native(_idxXSTO, 7) = -1; + _stoichiometry.native(_idxXSTO, 8) = -1; // XA - _stoichiometry.native(11, 9) = 1; - _stoichiometry.native(11, 10) = -1; - _stoichiometry.native(11, 11) = -1; + _stoichiometry.native(_idxXA, 9) = 1; + _stoichiometry.native(_idxXA, 10) = -1; + _stoichiometry.native(_idxXA, 11) = -1; // XMI - _stoichiometry.native(12, 5) = fXMI_BM; - _stoichiometry.native(12, 6) = fXMI_BM; - _stoichiometry.native(12, 10) = fXMI_BM; - _stoichiometry.native(12, 11) = fXMI_BM; + _stoichiometry.native(_idxXMI, 5) = fXMI_BM; + _stoichiometry.native(_idxXMI, 6) = fXMI_BM; + _stoichiometry.native(_idxXMI, 10) = fXMI_BM; + _stoichiometry.native(_idxXMI, 11) = fXMI_BM; @@ -571,20 +558,20 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase //XH_S = max(XH, 0.1) const size_t idxSO = 0; - const size_t idxSS = 1; - const size_t idxSNH = 2; - const size_t idxSNO = 3; - const size_t idxSN2 = 4; - const size_t idxSALK = 5; - const size_t idxSI = 6; - const size_t idxXI = 7; - const size_t idxXS = 8; - const size_t idxXH = 9; - const size_t idxXSTO = 10; - const size_t idxXA = 11; - const size_t idxXMI = 12; - - double d[13][13] = {}; + const size_t idxSS = 1; + const size_t idxSNH = 2; + const size_t idxSNO = 3; + const size_t idxSN2 = 4; + const size_t idxSALK = 5; + const size_t idxSI = 6; + const size_t idxXI = 7; + const size_t idxXS = 8; + const size_t idxXH = 9; + const size_t idxXSTO = 10; + const size_t idxXA = 11; + const size_t idxXMI = 12; + + double d[13][13] = {}; // p1: Hydrolysis: kh20 * ft04 * XS/XH_S / (XS/XH_S + kx) * XH; d[0][idxXS] = kh20 * ft04 From 05c40fddac08bea5b0747b68273670665177372d Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Mon, 30 Jun 2025 11:52:23 +0200 Subject: [PATCH 39/48] Seperation of SS and SI and flexible index for jacobinan --- .../reaction/ActivatedSludgeModelThree.cpp | 229 ++++++++++-------- 1 file changed, 129 insertions(+), 100 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index fb92ff237..1a4b1a109 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -168,18 +168,22 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase linalg::ActiveDenseMatrix _stoichiometry; unsigned int _idxSO = 0; //!< SO component index, default 0 - unsigned int _idxSS = 1; //!< SS component index, default 1 + unsigned int _idxSS_ad = 1; //!< SS / SS_ad component index, default 1 unsigned int _idxSNH = 2; //!< SNH component index, default 2 unsigned int _idxSNO = 3; //!< SNO component index, default 3 unsigned int _idxSN2 = 4; //!< SN2 component index, default 4 unsigned int _idxSALK = 5; //!< SALK component index, default 5 - unsigned int _idxSI = 6; //!< SI component index, default 6 + unsigned int _idxSI_bio = 6; //!< SI component index, default 6 unsigned int _idxXI = 7; //!< XI component index, default 7 unsigned int _idxXS = 8; //!< XS component index, default 8 unsigned int _idxXH = 9; //!< XH component index, default 9 unsigned int _idxXSTO = 10; //!< XSTO component index, default 10 unsigned int _idxXA = 11; //!< XA component index, default 11 unsigned int _idxXMI = 12; //!< XMI component index, default 12 + unsigned int _idxSS_nad = 13; //!< SS_nad component index, default 13 + unsigned int _idxSI_nbio = 14; //!< SI_nbio component index, default 14 + + bool _distribution = false; @@ -198,25 +202,34 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.resize(_nComp, 13); _stoichiometry.setAll(0); + if (paramProvider.exists("ASM3_DISTRIBUTION")) + _distribution = paramProvider.getBool("ASM3_DISTRIBUTION") + if (paramProvider.exists("ASM3_COMP_IDX")) { const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); if (compIdx.size() != 13) { throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); } else { LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; + // todo index fuer SS_nad und SI_nbio nach unten _idxSO = compIdx[0]; - _idxSS = compIdx[1]; + _idxSS_ad = compIdx[1]; _idxSNH = compIdx[2]; _idxSNO = compIdx[3]; _idxSN2 = compIdx[4]; _idxSALK = compIdx[5]; - _idxSI = compIdx[6]; + _idxSI_bio = compIdx[6]; _idxXI = compIdx[7]; _idxXS = compIdx[8]; _idxXH = compIdx[9]; _idxXSTO = compIdx[10]; _idxXA = compIdx[11]; _idxXMI = compIdx[12]; + if (_distribution) + { + _idxSS_nad = compIdx[13]; + _idxSI_nbio = compIdx[14]; + } } } else { LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; @@ -247,6 +260,14 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double fiSS_BM_prod = paramProvider.getDouble("ASM3_FISS_BM_PROD"); const double iVSS_BM = paramProvider.getDouble("ASM3_IVSS_BM"); const double iTSS_VSS_BM = paramProvider.getDouble("ASM3_ITSS_VSS_BM"); + + double r = 0.0; + if (paramProvider.exists("ASM3_R")) + r = paramProvider.getDouble("ASM3_R"); + + double s = 0.0; + if (!paramProvider.exists("ASM3_S")) + s = paramProvider.getDouble("ASM3_S"); // internal variables const double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); @@ -287,10 +308,15 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSO, 10) = -1 * (1 - fXI); _stoichiometry.native(_idxSO, 12) = 1; - // SS - _stoichiometry.native(_idxSS, 0) = 1 - fSI; - _stoichiometry.native(_idxSS, 1) = -1; - _stoichiometry.native(_idxSS, 2) = -1; + // SS_ad + _stoichiometry.native(_idxSS_ad, 0) = (1 - r) * (1 - fSI); + _stoichiometry.native(_idxSS_ad, 1) = -(1 - r); + _stoichiometry.native(_idxSS_ad, 2) = -(1 - r); + + // SS_nad + _stoichiometry.native(_idxSS_nad, 0) = r * (1 - fSI); + _stoichiometry.native(_idxSS_nad, 1) = -r; + _stoichiometry.native(_idxSS_nad, 2) = -r; // SNH _stoichiometry.native(_idxSNH, 0) = c1n; @@ -332,8 +358,11 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSALK, 10) = c11a; _stoichiometry.native(_idxSALK, 11) = c12a; - // SI - _stoichiometry.native(_idxSI, 0) = fSI; + // SI_bio + _stoichiometry.native(_idxSI_bio, 0) = (1 - s) * fSI; + + //SI_nbio + _stoichiometry.native(_idxSI_nbio, 0) = s * fSI; // XI _stoichiometry.native(_idxXI, 5) = fXI; @@ -422,7 +451,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double bAUT = static_cast(baut20) * ft105; StateType SO = y[_idxSO]; - StateType SS = y[_idxSS]; + StateType SS_ad = y[_idxSS_ad]; + StateType SS_nad = 0.0; + if (_distribution) + SS_nad = y[_idxSS_nad]; StateType SNH = y[_idxSNH]; StateType SNO = y[_idxSNO]; StateType SN2 = y[_idxSN2]; @@ -443,10 +475,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase // p2: Aerobic storage of SS - fluxes[1] = k_sto * SO / (SO + kho2) * SS / (SS + khss) * XH; + fluxes[1] = k_sto * SO / (SO + kho2) * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * XH; // p3: Anoxic storage of SS - fluxes[2] = k_sto * etahno3 * kho2 / (SO + kho2) * SS / (SS + khss) * SNO / (SNO + khn03) * XH; + fluxes[2] = k_sto * etahno3 * kho2 / (SO + kho2) * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * SNO / (SNO + khn03) * XH; // p4: Aerobic growth of heterotrophic biomass (XH) fluxes[3] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * ((XSTO/XH)) / (((XSTO/XH)) + khsto) * XH; @@ -542,42 +574,31 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double muAUT = static_cast(muAUT20) * ft105; const double bAUT = static_cast(baut20) * ft105; - double SO = y[0]; - double SS = y[1]; - double SNH = y[2]; - double SNO = y[3]; - double SN2 = y[4]; - double SALK = y[5]; - double SI = y[6]; // unused - double XI = y[7]; // unused - double XS = y[8]; - double XH = y[9]; - double XSTO = y[10]; - double XA = y[11]; - double XMI = y[12]; // unused + double SO = y[_idxSO]; + double SS_ad = y[_idxSS_ad]; + double SS_nad = 0.0; + if (_distribution) + SS_nad = y[_idxSS_nad]; + double SNH = y[_idxSNH]; + double SNO = y[_idxSNO]; + double SN2 = y[_idxSN2]; + double SALK = y[_idxSALK]; + //double SI_bio = y[_idxSI_bio]; // unused + //double XI = y[8]; // unused + double XS = y[_idxXS]; + double XH = y[_idxXH]; + double XSTO = y[_idxXSTO]; + double XA = y[_idxXA]; + //double XMI = y[13]; // unused //XH_S = max(XH, 0.1) - const size_t idxSO = 0; - const size_t idxSS = 1; - const size_t idxSNH = 2; - const size_t idxSNO = 3; - const size_t idxSN2 = 4; - const size_t idxSALK = 5; - const size_t idxSI = 6; - const size_t idxXI = 7; - const size_t idxXS = 8; - const size_t idxXH = 9; - const size_t idxXSTO = 10; - const size_t idxXA = 11; - const size_t idxXMI = 12; - double d[13][13] = {}; // p1: Hydrolysis: kh20 * ft04 * XS/XH_S / (XS/XH_S + kx) * XH; - d[0][idxXS] = kh20 * ft04 + d[0][_idxXS] = kh20 * ft04 * XH / ((XS + XH * kx) * (XS + XH * kx)) * XH; - d[0][idxXH] = kh20 * ft04 + d[0][_idxXH] = kh20 * ft04 * (XS * XS) / ((XS + kx * XH) * (XS + kx * XH)); if (XH < 0.1) @@ -587,57 +608,65 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[0][idxXH] = 0.0; } - // p2: Aerobic storage of SS: k_sto * SO / (SO + kho2) * SS / (SS + khss) * XH; - d[1][idxSO] = k_sto - * SS / (SS + khss) + // p2: Aerobic storage of SS: k_sto * SO / (SO + kho2) * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * XH; + d[1][_idxSO] = k_sto + * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * kho2 / ((SO + kho2) * (SO + kho2)) * XH; - d[1][idxSS] = k_sto + // todo ableitung SS_ad und SS_nad + d[1][_idxSS_ad] = k_sto * SO / (SO + kho2) - * khss / ((SS + khss) * (SS + khss)) * XH; - d[1][idxXH] = k_sto + * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; + d[1][_idxSS_nad] = k_sto * SO / (SO + kho2) - * SS / (SS + khss); + * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; + d[1][_idxXH] = k_sto + * SO / (SO + kho2) + * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) ; - // p3: Anoxic storage of SS: k_sto * etahno3 * kho2 / (SO + kho2) * SS / (SS + khss) * SNO / (SNO + khn03) * XH; - d[2][idxSO] = k_sto * etahno3 + // p3: Anoxic storage of SS: k_sto * etahno3 * kho2 / (SO + kho2) * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * SNO / (SNO + khn03) * XH; + d[2][_idxSO] = k_sto * etahno3 * -kho2 / ((SO + kho2) * (SO + kho2)) - * SS / (SS + khss) + * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) + * SNO / (SNO + khn03) * XH; + d[2][_idxSS_ad] = k_sto * etahno3 + * kho2 / (SO + kho2) + * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * SNO / (SNO + khn03) * XH; - d[2][idxSS] = k_sto * etahno3 + d[2][_idxSS_nad] = k_sto * etahno3 * kho2 / (SO + kho2) - * khss / ((SS + khss) * (SS + khss)) + * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * SNO / (SNO + khn03) * XH; - d[2][idxSNO] = k_sto * etahno3 + d[2][_idxSNO] = k_sto * etahno3 * kho2 / (SO + kho2) - * SS / (SS + khss) + * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * khn03 / ((SNO + khn03) * (SNO + khn03)) * XH; - d[2][idxXH] = k_sto * etahno3 + d[2][_idxXH] = k_sto * etahno3 * kho2 / (SO + kho2) - * SS / (SS + khss) + * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * SNO / (SNO + khn03); // p4: Aerobic growth: muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (XSTO/XH_S) / ((XSTO/XH_S) + khsto) * XH; - d[3][idxSO] = muH + d[3][_idxSO] = muH * kho2 / ((SO + kho2) * (SO + kho2)) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (XSTO / XH) / ((XSTO / XH) + khsto) * XH; - d[3][idxSNH] = muH + d[3][_idxSNH] = muH * SO / (SO + kho2) * khnh4 / ((SNH + khnh4) * (SNH + khnh4)) * SALK / (SALK + khalk) * (XSTO / XH) / ((XSTO / XH) + khsto) * XH; - d[3][idxSALK] = muH + d[3][_idxSALK] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * khalk / ((SALK + khalk) * (SALK + khalk)) * (XSTO / XH) / ((XSTO / XH) + khsto) * XH; - d[3][idxXSTO] = muH + d[3][_idxXSTO] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (khsto * XH) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)) * XH; - d[3][idxXH] = muH + d[3][_idxXH] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) @@ -645,62 +674,62 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase if (XH < 0.1) { - d[3][idxSO] = muH + d[3][_idxSO] = muH * -kho2 / ((SO + kho2) * (SO + kho2)) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; - d[3][idxSNH] = muH + d[3][_idxSNH] = muH * SO / (SO + kho2) * khnh4 / ((SNH + khnh4) * (SNH + khnh4)) * SALK / (SALK + khalk) * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; - d[3][idxSALK] = muH + d[3][_idxSALK] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * khalk / ((SALK + khalk) * (SALK + khalk)) * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; - d[3][idxXSTO] = muH + d[3][_idxXSTO] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (khsto * 0.1) / ((XSTO + khsto * 0.1) * (XSTO + khsto * 0.1)) * 0.1; - d[3][idxXH] = 0.0; + d[3][_idxXH] = 0.0; } // p5: Anoxic growth: muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * SNO / (khn03 + SNO)* (XSTO / XH_S) / (khsto + (XSTO/XH)_S) * XH; - d[4][idxSO] = muH * etahno3 + d[4][_idxSO] = muH * etahno3 * -kho2 / ((kho2 + SO) * (kho2 + SO)) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (XSTO / XH) / (khsto + (XSTO / XH)) * SNO / (khn03 + SNO) * XH; - d[4][idxSNH] = muH * etahno3 + d[4][_idxSNH] = muH * etahno3 * kho2 / (kho2 + SO) * khnh4 / ((khnh4 + SNH) * (khnh4 + SNH)) * SALK / (khalk + SALK) * (XSTO / XH) / (khsto + (XSTO / XH)) * SNO / (khn03 + SNO) * XH; - d[4][idxSALK] = muH * etahno3 + d[4][_idxSALK] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * khalk / ((khalk + SALK) * (khalk + SALK)) * (XSTO / XH) / (khsto + (XSTO / XH)) * SNO / (khn03 + SNO) * XH; - d[4][idxSNO] = muH * etahno3 + d[4][_idxSNO] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (XSTO / XH) / (khsto + (XSTO / XH)) * khn03 / ((khn03 + SNO) * (khn03 + SNO)) * XH; - d[4][idxXSTO] = muH * etahno3 + d[4][_idxXSTO] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (1.0 / XH) * SNO / (khn03 + SNO) * (khsto * XH * XH) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)) * XH; - d[4][idxXH] = muH * etahno3 + d[4][_idxXH] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) @@ -710,110 +739,110 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase if (XH < 0.1) { - d[4][idxSO] = muH * etahno3 + d[4][_idxSO] = muH * etahno3 * -kho2 / ((kho2 + SO) * (kho2 + SO)) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) * SNO / (khn03 + SNO) * 0.1; - d[4][idxSNH] = muH * etahno3 + d[4][_idxSNH] = muH * etahno3 * kho2 / (kho2 + SO) * khnh4 / ((khnh4 + SNH) * (khnh4 + SNH)) * SALK / (khalk + SALK) * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) * SNO / (khn03 + SNO) * 0.1; - d[4][idxSALK] = muH * etahno3 + d[4][_idxSALK] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * khalk / ((khalk + SALK) * (khalk + SALK)) * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) * SNO / (khn03 + SNO) * 0.1; - d[4][idxSNO] = muH * etahno3 + d[4][_idxSNO] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (XSTO / 0.1) / (khsto + (XSTO / 0.1)) * khn03 / ((khn03 + SNO) * (khn03 + SNO)) * 0.1; - d[4][idxXSTO] = muH * etahno3 + d[4][_idxXSTO] = muH * etahno3 * kho2 / (kho2 + SO) * SNH / (khnh4 + SNH) * SALK / (khalk + SALK) * (1.0 / 0.1) * SNO / (khn03 + SNO) * (khsto * 0.1 * 0.1) / ((XSTO + khsto * 0.1) * (XSTO + khsto * 0.1)) * 0.1; - d[4][idxXH] = 0.0; + d[4][_idxXH] = 0.0; } //reaction6: bH * SO / (SO + kho2) * XH; - d[5][idxSO] = bH + d[5][_idxSO] = bH * kho2 / ((SO + kho2) * (SO + kho2)) * XH; - d[5][idxXH] = bH * SO / (SO + kho2); + d[5][_idxXH] = bH * SO / (SO + kho2); //reaction7: bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03) * XH; - d[6][idxSO] = bH * etahend + d[6][_idxSO] = bH * etahend * -kho2 / ((SO + kho2) * (SO + kho2)) * SNO / (SNO + khn03) * XH; - d[6][idxSNO] = bH * etahend + d[6][_idxSNO] = bH * etahend * kho2 / (SO + kho2) * khn03 / ((SNO + khn03) * (SNO + khn03)) * XH; - d[6][idxXH] = bH * etahend + d[6][_idxXH] = bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03); //reaction8: bH * SO / (SO + kho2) * XSTO; - d[7][idxSO] = bH + d[7][_idxSO] = bH * kho2 / ((SO + kho2) * (SO + kho2)) * XSTO; - d[7][idxXSTO] = bH * SO / (SO + kho2); + d[7][_idxXSTO] = bH * SO / (SO + kho2); //reaction9: bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03) * XSTO; - d[8][idxSO] = bH * etahend + d[8][_idxSO] = bH * etahend * -kho2 / ((SO + kho2) * (SO + kho2)) * SNO / (SNO + khn03) * XSTO; - d[8][idxSNO] = bH * etahend + d[8][_idxSNO] = bH * etahend * kho2 / (SO + kho2) * khn03 / ((SNO + khn03) * (SNO + khn03)) * XSTO; - d[8][idxXSTO] = bH * etahend + d[8][_idxXSTO] = bH * etahend * kho2 / (SO + kho2) * SNO / (SNO + khn03); //reaction10: muAUT * SO / (SO + kno2) * SNH / (SNH + knnh4) * SALK / (SALK + knalk) * XA; - d[9][idxSO] = muAUT + d[9][_idxSO] = muAUT * kno2 / ((SO + kno2) * (SO + kno2)) * SNH / (SNH + knnh4) * SALK / (SALK + knalk) * XA; - d[9][idxSALK] = muAUT + d[9][_idxSALK] = muAUT * SO / (SO + kno2) * SNH / (SNH + knnh4) * knalk / ((SALK + knalk) * (SALK + knalk)) * XA; - d[9][idxSNH] = muAUT + d[9][_idxSNH] = muAUT * SO / (SO + kno2) * SALK / (SALK + knalk) * knnh4 / ((SNH + knnh4) * (SNH + knnh4)) * XA; - d[9][idxXA] = muAUT + d[9][_idxXA] = muAUT * SO / (SO + kno2) * SNH / (SNH + knnh4) * SALK / (SALK + knalk); //reaction11: bAUT * SO / (SO + kho2) * XA; - d[10][idxSO] = bAUT + d[10][_idxSO] = bAUT * kho2 / ((SO + kho2) * (SO + kho2)) * XA; - d[10][idxXA] = bAUT + d[10][_idxXA] = bAUT * SO / (SO + kho2); //reaction12: bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2) * XA; - d[11][idxSO] = bAUT * etanend + d[11][_idxSO] = bAUT * etanend * SNO / (SNO + khn03) * -kho2 / ((SO + kho2) * (SO + kho2)) * XA; - d[11][idxSNO] = bAUT * etanend + d[11][_idxSNO] = bAUT * etanend * kho2 / (SO + kho2) * khn03 / ((SNO + khn03) * (SNO + khn03)) * XA; - d[11][idxXA] = bAUT * etanend + d[11][_idxXA] = bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2); RowIterator curJac = jac; - for (size_t rIdx = 0; rIdx < 13; rIdx++) + for (size_t rIdx = 0; rIdx < _stoichiometry.columns(); rIdx++) { RowIterator curJac = jac; for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) From f3341970d1212e1f26f52c90a9c1848cf03b7720 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Mon, 30 Jun 2025 12:00:15 +0200 Subject: [PATCH 40/48] fixup! Seperation of SS and SI and flexible index for jacobinan --- src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 1a4b1a109..db04f42fd 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -682,6 +682,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[3][_idxSNH] = muH * SO / (SO + kho2) * khnh4 / ((SNH + khnh4) * (SNH + khnh4)) + * khnh4 / ((SNH + khnh4) * (SNH + khnh4)) * SALK / (SALK + khalk) * (XSTO / 0.1) / ((XSTO / 0.1) + khsto) * 0.1; d[3][_idxSALK] = muH From 12a4376ed1623d9fbd007b2bafbf6b6c0dca2d20 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Mon, 30 Jun 2025 14:56:38 +0200 Subject: [PATCH 41/48] Add time dependent fraction --- .../reaction/ActivatedSludgeModelThree.cpp | 174 +++++++++--------- 1 file changed, 87 insertions(+), 87 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index db04f42fd..55e96d698 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -173,7 +173,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase unsigned int _idxSNO = 3; //!< SNO component index, default 3 unsigned int _idxSN2 = 4; //!< SN2 component index, default 4 unsigned int _idxSALK = 5; //!< SALK component index, default 5 - unsigned int _idxSI_bio = 6; //!< SI component index, default 6 + unsigned int _idxSI_ad = 6; //!< SI component index, default 6 unsigned int _idxXI = 7; //!< XI component index, default 7 unsigned int _idxXS = 8; //!< XS component index, default 8 unsigned int _idxXH = 9; //!< XH component index, default 9 @@ -181,11 +181,11 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase unsigned int _idxXA = 11; //!< XA component index, default 11 unsigned int _idxXMI = 12; //!< XMI component index, default 12 unsigned int _idxSS_nad = 13; //!< SS_nad component index, default 13 - unsigned int _idxSI_nbio = 14; //!< SI_nbio component index, default 14 + unsigned int _idxSI_nad = 14; //!< SI_nbio component index, default 14 - bool _distribution = false; - + bool _fractionate = false; + virtual bool configureStoich(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx) { @@ -198,40 +198,42 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _paramHandler.configure(paramProvider, _stoichiometry.columns(), _nComp, _nBoundStates); _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); - + _stoichiometry.resize(_nComp, 13); _stoichiometry.setAll(0); - if (paramProvider.exists("ASM3_DISTRIBUTION")) - _distribution = paramProvider.getBool("ASM3_DISTRIBUTION") + if (paramProvider.exists("ASM3_FRACTIONATE")) + _fractionate = paramProvider.getBool("ASM3_FRACTIONATE"); if (paramProvider.exists("ASM3_COMP_IDX")) { const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); if (compIdx.size() != 13) { throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); - } else { + } + else { LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; - // todo index fuer SS_nad und SI_nbio nach unten _idxSO = compIdx[0]; - _idxSS_ad = compIdx[1]; + _idxSS_nad = compIdx[1]; _idxSNH = compIdx[2]; _idxSNO = compIdx[3]; _idxSN2 = compIdx[4]; _idxSALK = compIdx[5]; - _idxSI_bio = compIdx[6]; + _idxSI_nad = compIdx[6]; _idxXI = compIdx[7]; _idxXS = compIdx[8]; _idxXH = compIdx[9]; _idxXSTO = compIdx[10]; _idxXA = compIdx[11]; _idxXMI = compIdx[12]; - if (_distribution) + if (_fractionate) { - _idxSS_nad = compIdx[13]; - _idxSI_nbio = compIdx[14]; + //todo warníng + _idxSS_ad = compIdx[13]; + _idxSI_ad = compIdx[14]; } } - } else { + } + else { LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; } @@ -260,14 +262,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double fiSS_BM_prod = paramProvider.getDouble("ASM3_FISS_BM_PROD"); const double iVSS_BM = paramProvider.getDouble("ASM3_IVSS_BM"); const double iTSS_VSS_BM = paramProvider.getDouble("ASM3_ITSS_VSS_BM"); - - double r = 0.0; - if (paramProvider.exists("ASM3_R")) - r = paramProvider.getDouble("ASM3_R"); - - double s = 0.0; - if (!paramProvider.exists("ASM3_S")) - s = paramProvider.getDouble("ASM3_S"); // internal variables const double fXMI_BM = fiSS_BM_prod * fXI * iVSS_BM * (iTSS_VSS_BM - 1); @@ -304,19 +298,19 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSO, 3) = 1 - 1 / YH_aer; _stoichiometry.native(_idxSO, 5) = -1 * (1 - fXI); _stoichiometry.native(_idxSO, 7) = -1; - _stoichiometry.native(_idxSO, 9) = -(64.0/14.0) * 1/YA + 1; + _stoichiometry.native(_idxSO, 9) = -(64.0 / 14.0) * 1 / YA + 1; _stoichiometry.native(_idxSO, 10) = -1 * (1 - fXI); _stoichiometry.native(_idxSO, 12) = 1; // SS_ad - _stoichiometry.native(_idxSS_ad, 0) = (1 - r) * (1 - fSI); - _stoichiometry.native(_idxSS_ad, 1) = -(1 - r); - _stoichiometry.native(_idxSS_ad, 2) = -(1 - r); + _stoichiometry.native(_idxSS_ad, 0) = (1 - fSI); + _stoichiometry.native(_idxSS_ad, 1) = -1; + _stoichiometry.native(_idxSS_ad, 2) = -1; // SS_nad - _stoichiometry.native(_idxSS_nad, 0) = r * (1 - fSI); - _stoichiometry.native(_idxSS_nad, 1) = -r; - _stoichiometry.native(_idxSS_nad, 2) = -r; + _stoichiometry.native(_idxSS_nad, 0) = (1 - fSI); + _stoichiometry.native(_idxSS_nad, 1) = -1; + _stoichiometry.native(_idxSS_nad, 2) = -1; // SNH _stoichiometry.native(_idxSNH, 0) = c1n; @@ -358,11 +352,11 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSALK, 10) = c11a; _stoichiometry.native(_idxSALK, 11) = c12a; - // SI_bio - _stoichiometry.native(_idxSI_bio, 0) = (1 - s) * fSI; + // SI_ad + _stoichiometry.native(_idxSI_bio, 0) = fSI; - //SI_nbio - _stoichiometry.native(_idxSI_nbio, 0) = s * fSI; + //SI_nad + _stoichiometry.native(_idxSI_nbio, 0) = fSI; // XI _stoichiometry.native(_idxXI, 5) = fXI; @@ -399,15 +393,12 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxXMI, 11) = fXMI_BM; - - - //registerCompRowMatrix(_parameters, unitOpIdx, parTypeIdx, "MM_STOICHIOMETRY_BULK", _stoichiometryBulk); todo return true; } - template + template int residualLiquidImpl(double t, unsigned int secIdx, const ColumnPosition& colPos, StateType const* y, ResidualType* res, const FactorType& factor, LinearBufferAllocator workSpace) const { @@ -451,10 +442,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double bAUT = static_cast(baut20) * ft105; StateType SO = y[_idxSO]; - StateType SS_ad = y[_idxSS_ad]; - StateType SS_nad = 0.0; - if (_distribution) - SS_nad = y[_idxSS_nad]; + StateType SS_nad = y[_idxSS_ad]; + StateType SS_ad = 0.0; + if (_fractionate) + SS_ad = y[_idxSS_nad]; StateType SNH = y[_idxSNH]; StateType SNO = y[_idxSNO]; StateType SN2 = y[_idxSN2]; @@ -541,28 +532,28 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase typename ParamHandler_t::ParamsHandle const p = _paramHandler.update(t, secIdx, colPos, _nComp, _nBoundStates, workSpace); //parameters - const double kh20 = static_cast(p->kh20); - const double T = static_cast(p->T); - const double io2 = static_cast(p->io2); - const double V = static_cast(p->V); - const double k_sto20 = static_cast(p->k_sto20); - const double kx = static_cast(p->kx); - const double kho2 = static_cast(p->kho2); - const double khss = static_cast(p->khss); - const double khn03 = static_cast(p->khn03); - const double etahno3 = static_cast(p->etahno3); - const double khnh4 = static_cast(p->khnh4); - const double khalk = static_cast(p->khalk); - const double khsto = static_cast(p->khsto); - const double muh2o = static_cast(p->muh2o); - const double etahend = static_cast(p->etahend); - const double bh20 = static_cast(p->bh20); - const double muAUT20 = static_cast(p->muAUT20); - const double kno2 = static_cast(p->kno2); - const double knnh4 = static_cast(p->knnh4); - const double knalk = static_cast(p->knalk); - const double baut20 = static_cast(p->baut20); - const double etanend = static_cast(p->etanend); + const double kh20 = static_cast(p->kh20); + const double T = static_cast(p->T); + const double io2 = static_cast(p->io2); + const double V = static_cast(p->V); + const double k_sto20 = static_cast(p->k_sto20); + const double kx = static_cast(p->kx); + const double kho2 = static_cast(p->kho2); + const double khss = static_cast(p->khss); + const double khn03 = static_cast(p->khn03); + const double etahno3 = static_cast(p->etahno3); + const double khnh4 = static_cast(p->khnh4); + const double khalk = static_cast(p->khalk); + const double khsto = static_cast(p->khsto); + const double muh2o = static_cast(p->muh2o); + const double etahend = static_cast(p->etahend); + const double bh20 = static_cast(p->bh20); + const double muAUT20 = static_cast(p->muAUT20); + const double kno2 = static_cast(p->kno2); + const double knnh4 = static_cast(p->knnh4); + const double knalk = static_cast(p->knalk); + const double baut20 = static_cast(p->baut20); + const double etanend = static_cast(p->etanend); // derived parameters const double ft04 = exp(-0.04 * (20.0 - static_cast(T))); @@ -577,13 +568,14 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase double SO = y[_idxSO]; double SS_ad = y[_idxSS_ad]; double SS_nad = 0.0; - if (_distribution) + if (_fractionate) SS_nad = y[_idxSS_nad]; double SNH = y[_idxSNH]; double SNO = y[_idxSNO]; double SN2 = y[_idxSN2]; double SALK = y[_idxSALK]; - //double SI_bio = y[_idxSI_bio]; // unused + double SI_ad = y[_idxSI_ad]; // unused + double SI_nad = y[_idxSI_nad]; // unused //double XI = y[8]; // unused double XS = y[_idxXS]; double XH = y[_idxXH]; @@ -592,27 +584,27 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase //double XMI = y[13]; // unused //XH_S = max(XH, 0.1) - double d[13][13] = {}; - + double d[13][15] = {}; + + // p1: Hydrolysis: kh20 * ft04 * XS/XH_S / (XS/XH_S + kx) * XH; d[0][_idxXS] = kh20 * ft04 * XH / ((XS + XH * kx) - * (XS + XH * kx)) * XH; + * (XS + XH * kx)) * XH; d[0][_idxXH] = kh20 * ft04 * (XS * XS) / ((XS + kx * XH) - * (XS + kx * XH)); + * (XS + kx * XH)); if (XH < 0.1) { - d[0][idxXS] = kh20 * ft04 + d[0][_idxXS] = kh20 * ft04 * 0.1 / ((XS + 0.1 * kx) * (XS + 0.1 * kx)) * XH; - d[0][idxXH] = 0.0; + d[0][_idxXH] = 0.0; } // p2: Aerobic storage of SS: k_sto * SO / (SO + kho2) * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * XH; d[1][_idxSO] = k_sto - * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) + * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss) * kho2 / ((SO + kho2) * (SO + kho2)) * XH; - // todo ableitung SS_ad und SS_nad d[1][_idxSS_ad] = k_sto * SO / (SO + kho2) * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; @@ -621,12 +613,12 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; d[1][_idxXH] = k_sto * SO / (SO + kho2) - * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) ; + * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss); // p3: Anoxic storage of SS: k_sto * etahno3 * kho2 / (SO + kho2) * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) * SNO / (SNO + khn03) * XH; d[2][_idxSO] = k_sto * etahno3 * -kho2 / ((SO + kho2) * (SO + kho2)) - * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) + * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss) * SNO / (SNO + khn03) * XH; d[2][_idxSS_ad] = k_sto * etahno3 * kho2 / (SO + kho2) @@ -638,11 +630,11 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase * SNO / (SNO + khn03) * XH; d[2][_idxSNO] = k_sto * etahno3 * kho2 / (SO + kho2) - * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) + * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss) * khn03 / ((SNO + khn03) * (SNO + khn03)) * XH; d[2][_idxXH] = k_sto * etahno3 * kho2 / (SO + kho2) - * ( SS_ad + SS_nad ) / ( ( SS_ad + SS_nad ) + khss ) + * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss) * SNO / (SNO + khn03); // p4: Aerobic growth: muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (XSTO/XH_S) / ((XSTO/XH_S) + khsto) * XH; @@ -666,7 +658,7 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase * SNH / (SNH + khnh4) * SALK / (SALK + khalk) * (khsto * XH) / ((XSTO + khsto * XH) * (XSTO + khsto * XH)) * XH; - d[3][_idxXH] = muH + d[3][_idxXH] = muH * SO / (SO + kho2) * SNH / (SNH + khnh4) * SALK / (SALK + khalk) @@ -819,9 +811,9 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase * SO / (SO + kno2) * SALK / (SALK + knalk) * knnh4 / ((SNH + knnh4) * (SNH + knnh4)) * XA; - d[9][_idxXA] = muAUT - * SO / (SO + kno2) - * SNH / (SNH + knnh4) + d[9][_idxXA] = muAUT + * SO / (SO + kno2) + * SNH / (SNH + knnh4) * SALK / (SALK + knalk); //reaction11: bAUT * SO / (SO + kho2) * XA; @@ -840,23 +832,31 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[11][_idxXA] = bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2); - - + + RowIterator curJac = jac; for (size_t rIdx = 0; rIdx < _stoichiometry.columns(); rIdx++) { RowIterator curJac = jac; - for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) + for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { const double colFactor = static_cast(_stoichiometry.native(row, rIdx)); - for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) + for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) { + if (compIdx == _idxSI_ad) + colFactor *= SI_ad / (SI_ad + SI_nad); + if (compIdx == _idxSI_nad) + colFactor *= SI_nad / (SI_ad + SI_nad); + if (compIdx == _idxSS_ad) + colFactor *= SS_ad / (SS_ad + SS_nad); + if (compIdx == _idxSS_nad) + colFactor *= SS_nad / (SS_ad + SS_nad); curJac[compIdx - static_cast(row)] += colFactor * d[rIdx][compIdx]; } } } - + } template From b0f2bfc68453d4db9e1ddf61e038b61f205cfb01 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Mon, 4 Aug 2025 11:32:40 +0200 Subject: [PATCH 42/48] clean up for fractionate option --- .../reaction/ActivatedSludgeModelThree.cpp | 87 ++++++++++--------- 1 file changed, 48 insertions(+), 39 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index 55e96d698..bc85259c5 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -168,20 +168,20 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase linalg::ActiveDenseMatrix _stoichiometry; unsigned int _idxSO = 0; //!< SO component index, default 0 - unsigned int _idxSS_ad = 1; //!< SS / SS_ad component index, default 1 + unsigned int _idxSS_nad = 1; //!< SS / SS_ad component index, default 1 unsigned int _idxSNH = 2; //!< SNH component index, default 2 unsigned int _idxSNO = 3; //!< SNO component index, default 3 unsigned int _idxSN2 = 4; //!< SN2 component index, default 4 unsigned int _idxSALK = 5; //!< SALK component index, default 5 - unsigned int _idxSI_ad = 6; //!< SI component index, default 6 + unsigned int _idxSI_nad = 6; //!< SI component index, default 6 unsigned int _idxXI = 7; //!< XI component index, default 7 unsigned int _idxXS = 8; //!< XS component index, default 8 unsigned int _idxXH = 9; //!< XH component index, default 9 unsigned int _idxXSTO = 10; //!< XSTO component index, default 10 unsigned int _idxXA = 11; //!< XA component index, default 11 unsigned int _idxXMI = 12; //!< XMI component index, default 12 - unsigned int _idxSS_nad = 13; //!< SS_nad component index, default 13 - unsigned int _idxSI_nad = 14; //!< SI_nbio component index, default 14 + unsigned int _idxSI_ad = 13; + unsigned int _idxSS_ad = 14; bool _fractionate = false; @@ -302,16 +302,18 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSO, 10) = -1 * (1 - fXI); _stoichiometry.native(_idxSO, 12) = 1; - // SS_ad - _stoichiometry.native(_idxSS_ad, 0) = (1 - fSI); - _stoichiometry.native(_idxSS_ad, 1) = -1; - _stoichiometry.native(_idxSS_ad, 2) = -1; - // SS_nad _stoichiometry.native(_idxSS_nad, 0) = (1 - fSI); _stoichiometry.native(_idxSS_nad, 1) = -1; _stoichiometry.native(_idxSS_nad, 2) = -1; + // SS_ad + if (_fractionate) + { + _stoichiometry.native(_idxSS_ad, 0) = (1 - fSI); + _stoichiometry.native(_idxSS_ad, 1) = -1; + _stoichiometry.native(_idxSS_ad, 2) = -1; + } // SNH _stoichiometry.native(_idxSNH, 0) = c1n; _stoichiometry.native(_idxSNH, 1) = c2n; @@ -353,10 +355,11 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSALK, 11) = c12a; // SI_ad - _stoichiometry.native(_idxSI_bio, 0) = fSI; + _stoichiometry.native(_idxSI_nad, 0) = fSI; //SI_nad - _stoichiometry.native(_idxSI_nbio, 0) = fSI; + if(_fractionate) + _stoichiometry.native(_idxSI_ad, 0) = fSI; // XI _stoichiometry.native(_idxXI, 5) = fXI; @@ -442,10 +445,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double bAUT = static_cast(baut20) * ft105; StateType SO = y[_idxSO]; - StateType SS_nad = y[_idxSS_ad]; + StateType SS_nad = y[_idxSS_nad]; StateType SS_ad = 0.0; if (_fractionate) - SS_ad = y[_idxSS_nad]; + SS_ad = y[_idxSS_ad]; StateType SNH = y[_idxSNH]; StateType SNO = y[_idxSNO]; StateType SN2 = y[_idxSN2]; @@ -566,23 +569,25 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const double bAUT = static_cast(baut20) * ft105; double SO = y[_idxSO]; - double SS_ad = y[_idxSS_ad]; - double SS_nad = 0.0; - if (_fractionate) - SS_nad = y[_idxSS_nad]; + double SS_nad = y[_idxSS_nad]; + double SS_ad = 0.0; double SNH = y[_idxSNH]; double SNO = y[_idxSNO]; double SN2 = y[_idxSN2]; double SALK = y[_idxSALK]; - double SI_ad = y[_idxSI_ad]; // unused - double SI_nad = y[_idxSI_nad]; // unused - //double XI = y[8]; // unused + double SI_nad = y[_idxSI_nad]; + double SI_ad = 0.0; double XS = y[_idxXS]; double XH = y[_idxXH]; double XSTO = y[_idxXSTO]; double XA = y[_idxXA]; - //double XMI = y[13]; // unused - //XH_S = max(XH, 0.1) + + if (_fractionate) + { + SS_ad = y[_idxSS_ad]; + SI_ad = y[_idxSI_ad]; + } + double d[13][15] = {}; @@ -605,9 +610,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[1][_idxSO] = k_sto * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss) * kho2 / ((SO + kho2) * (SO + kho2)) * XH; - d[1][_idxSS_ad] = k_sto - * SO / (SO + kho2) - * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; + if (_fractionate) + d[1][_idxSS_ad] = k_sto + * SO / (SO + kho2) + * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; d[1][_idxSS_nad] = k_sto * SO / (SO + kho2) * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) * XH; @@ -620,10 +626,11 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase * -kho2 / ((SO + kho2) * (SO + kho2)) * (SS_ad + SS_nad) / ((SS_ad + SS_nad) + khss) * SNO / (SNO + khn03) * XH; - d[2][_idxSS_ad] = k_sto * etahno3 - * kho2 / (SO + kho2) - * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) - * SNO / (SNO + khn03) * XH; + if(_fractionate) + d[2][_idxSS_ad] = k_sto * etahno3 + * kho2 / (SO + kho2) + * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) + * SNO / (SNO + khn03) * XH; d[2][_idxSS_nad] = k_sto * etahno3 * kho2 / (SO + kho2) * khss / ((SS_ad + SS_nad + khss) * (SS_ad + SS_nad + khss)) @@ -840,18 +847,20 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase RowIterator curJac = jac; for (int row = 0; row < _stoichiometry.rows(); ++row, ++curJac) { - const double colFactor = static_cast(_stoichiometry.native(row, rIdx)); + double colFactor = static_cast(_stoichiometry.native(row, rIdx)); for (size_t compIdx = 0; compIdx < _stoichiometry.rows(); compIdx++) { - if (compIdx == _idxSI_ad) - colFactor *= SI_ad / (SI_ad + SI_nad); - if (compIdx == _idxSI_nad) - colFactor *= SI_nad / (SI_ad + SI_nad); - if (compIdx == _idxSS_ad) - colFactor *= SS_ad / (SS_ad + SS_nad); - if (compIdx == _idxSS_nad) - colFactor *= SS_nad / (SS_ad + SS_nad); - + if (_fractionate) + { + if (compIdx == _idxSI_ad) + colFactor *= SI_ad / (SI_ad + SI_nad); + if (compIdx == _idxSI_nad) + colFactor *= SI_nad / (SI_ad + SI_nad); + if (compIdx == _idxSS_ad) + colFactor *= SS_ad / (SS_ad + SS_nad); + if (compIdx == _idxSS_nad) + colFactor *= SS_nad / (SS_ad + SS_nad); + } curJac[compIdx - static_cast(row)] += colFactor * d[rIdx][compIdx]; } } From 512cae747f217b3bcbc0e97f86c03c2a0dd9397d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Florian=20H=C3=BClsmann?= Date: Mon, 15 Sep 2025 15:17:24 +0200 Subject: [PATCH 43/48] asm3: fix warning when using fractionated SS/SI --- .../reaction/ActivatedSludgeModelThree.cpp | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index bc85259c5..ec0784710 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -207,31 +207,31 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase if (paramProvider.exists("ASM3_COMP_IDX")) { const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); - if (compIdx.size() != 13) { - throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); - } - else { - LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; - _idxSO = compIdx[0]; - _idxSS_nad = compIdx[1]; - _idxSNH = compIdx[2]; - _idxSNO = compIdx[3]; - _idxSN2 = compIdx[4]; - _idxSALK = compIdx[5]; - _idxSI_nad = compIdx[6]; - _idxXI = compIdx[7]; - _idxXS = compIdx[8]; - _idxXH = compIdx[9]; - _idxXSTO = compIdx[10]; - _idxXA = compIdx[11]; - _idxXMI = compIdx[12]; - if (_fractionate) - { - //todo warníng - _idxSS_ad = compIdx[13]; - _idxSI_ad = compIdx[14]; + if (_fractionate) + { + if (compIdx.size() != 15) { + throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 15 elements"); } + _idxSS_ad = compIdx[13]; + _idxSI_ad = compIdx[14]; + } + else if (compIdx.size() != 13) { + throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); } + LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; + _idxSO = compIdx[0]; + _idxSS_nad = compIdx[1]; + _idxSNH = compIdx[2]; + _idxSNO = compIdx[3]; + _idxSN2 = compIdx[4]; + _idxSALK = compIdx[5]; + _idxSI_nad = compIdx[6]; + _idxXI = compIdx[7]; + _idxXS = compIdx[8]; + _idxXH = compIdx[9]; + _idxXSTO = compIdx[10]; + _idxXA = compIdx[11]; + _idxXMI = compIdx[12]; } else { LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; From b0d95523c154b7c57b0224d38cad0b35b48e4405 Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 19 Nov 2025 17:21:25 +0100 Subject: [PATCH 44/48] change internal parameter names --- test/MatrixHelper.hpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/MatrixHelper.hpp b/test/MatrixHelper.hpp index edb6c15b4..9dacdeea3 100644 --- a/test/MatrixHelper.hpp +++ b/test/MatrixHelper.hpp @@ -56,9 +56,9 @@ Matrix_t createBandMatrix(unsigned int rows, unsigned int lower, unsigned int up double val = 1.0; for (int row = 0; row < bm.rows(); ++row) { - const int lower = std::max(-static_cast(bm.lowerBandwidth()), -static_cast(row)); - const int upper = std::min(static_cast(bm.upperBandwidth()), static_cast(bm.rows() - row) - 1); - for (int col = lower; col <= upper; ++col) + const int low = std::max(-static_cast(bm.lowerBandwidth()), -static_cast(row)); + const int up = std::min(static_cast(bm.upperBandwidth()), static_cast(bm.rows() - row) - 1); + for (int col = low; col <= up; ++col) { bm.centered(row, col) = val; val += 1.0; From de0973fb9bde95791cbc1f52838c661532cb4aae Mon Sep 17 00:00:00 2001 From: "a.berger" Date: Wed, 19 Nov 2025 17:26:19 +0100 Subject: [PATCH 45/48] fix ASM3 test --- test/ReactionModels.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/ReactionModels.cpp b/test/ReactionModels.cpp index 4bb7fe81f..7f477986d 100644 --- a/test/ReactionModels.cpp +++ b/test/ReactionModels.cpp @@ -165,7 +165,7 @@ TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD without inhibition", TEST_CASE("MichaelisMenten kinetic analytic Jacobian vs AD with inhibition", "[MichaelisMenten],[ReactionModel],[Jacobian],[AD]") { - const unsigned int nBound[] = {1.0, 2.0, 1.0}; + const unsigned int nBound[] = {1, 2, 1}; const double point[] = {1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8}; cadet::test::reaction::testDynamicJacobianAD("MICHAELIS_MENTEN", 3, nBound, R"json({ @@ -184,7 +184,7 @@ TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD { const unsigned int nBound[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ,0, 0, 0 }; unsigned int ncomp = 13; - const double point[] = { 1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8, 1.5, 1.0, 4.2, 1.4, 0.3, 1.4}; + const double point[] = { 1.0, 2.0, 1.4, 2.1, 0.2, 1.1, 1.8, 1.5, 1.0, 4.2, 1.4, 0.3, 1.4 }; cadet::test::reaction::testLiquidReactionJacobianAD("ACTIVATED_SLUDGE_MODEL3", ncomp, nBound, R"json({ "ASM3_FISS_BM_PROD": 1.0, @@ -230,6 +230,7 @@ TEST_CASE("ASM3 analytic Jacobian vs AD", "[ASM3],[ReactionModel],[Jacobian],[AD })json", point, 1e-15, 1e-15 ); +} TEST_CASE("MassActionLaw old interface vs. two separate reactions", "[MassActionLaw],[ReactionModel],[Simulation],[CI]") { From 33a1456348adf11061ee2cdfe991956fcaecaa65 Mon Sep 17 00:00:00 2001 From: AntoniaBerger Date: Wed, 26 Nov 2025 16:21:30 +0100 Subject: [PATCH 46/48] Add documentation --- .../reaction/activated_sludge_model.rst | 321 +++++++++++++++++- doc/modelling/reaction/images/netzwerk.jpg | Bin 0 -> 234865 bytes 2 files changed, 318 insertions(+), 3 deletions(-) create mode 100644 doc/modelling/reaction/images/netzwerk.jpg diff --git a/doc/modelling/reaction/activated_sludge_model.rst b/doc/modelling/reaction/activated_sludge_model.rst index 09b380544..f6c7a7c26 100644 --- a/doc/modelling/reaction/activated_sludge_model.rst +++ b/doc/modelling/reaction/activated_sludge_model.rst @@ -1,11 +1,326 @@ .. _activated_sludge_model: Activated Sludge Model (ASM3h) -------------------------- +=============================== -Activated Sludge Models are a group of reaction models published by the International Water Association (IWA) +Activated Sludge Models (ASM) are a group of reaction models published by the International Water Association (IWA) and mostly used for modelling biological processes in wastewater treatment plants. -The ASM3h model implemented in CADET is a variant of the ASM3 model and documented in the SIMBA# software suite. +It consists of 13 components representing different species in wastewater treatment plants. + +The used components with their abbreviations are listed in the following table: + +.. list-table:: + :header-rows: 1 + :widths: 20 80 + + * - Abbreviation + - Component + * - :math:`SO` + - Dissolved oxygen + * - :math:`SS` + - Readily biodegradable substrate + * - :math:`SNH` + - Ammonium + * - :math:`SNO` + - Nitrite and nitrate + * - :math:`SN2` + - Dinitrogen, released by denitrification + * - :math:`SALK` + - Alkalinity, bicarbonate + * - :math:`SI` + - Soluble inert organic + * - :math:`XI` + - Inert Particulate organic + * - :math:`XS` + - Slowly biodegradable substrate + * - :math:`XH` + - Heterotrophic biomass + * - :math:`XSTO` + - Organics stored by heterotrophs + * - :math:`XA` + - Autotrophic, nitrifying biomass + * - :math:`XMI` + - Mineral particulate matter from biomass + +The net flux for each of the components in the ASM is calculated with a stoichiometric +matrix :math:`S \in \mathbb{R}^{13 \times 13}` and a reaction rate vector +:math:`\varphi_j(c)`, where :math:`j` is the reaction number. +The list of reactions included in the ASM3h model is as follows: + +Reaction Rates +~~~~~~~~~~~~~~ + +.. list-table:: + :header-rows: 1 + :widths: 15 35 50 + + * - Reaction Number + - Reaction Description + - Rate Equation + * - :math:`r_1` + - Hydrolysis of organic structures + - :math:`k_{h,20} \cdot f_T^{0.04} \cdot \frac{XS/XH}{XS/XH + K_X} \cdot XH` + * - :math:`r_2` + - Aerobic storage of SS + - :math:`k_{STO} \cdot \frac{SO}{SO + K_{h,O2}} \cdot \frac{SS}{SS + K_{h,SS}} \cdot XH` + * - :math:`r_3` + - Anoxic storage of SS + - :math:`k_{STO} \cdot \eta_{h,NO3} \cdot \frac{K_{h,O2}}{SO + K_{h,O2}} \cdot \frac{SNO}{SNO + K_{h,NO3}} \cdot \frac{SS}{SS + K_{h,SS}} \cdot XH` + * - :math:`r_4` + - Aerobic growth of XH + - :math:`\mu_H \cdot \frac{SO}{SO + K_{h,O2}} \cdot \frac{XSTO/XH}{XSTO/XH + K_{h,STO}} \cdot \frac{SNH}{SNH + K_{h,NH4}} \cdot \frac{SALK}{SALK + K_{h,ALK}} \cdot XH` + * - :math:`r_5` + - Anoxic growth of XH (denitrification) + - :math:`\mu_H \cdot \eta_{h,NO3} \cdot \frac{K_{h,O2}}{SO + K_{h,O2}} \cdot \frac{SNO}{SNO + K_{h,NO3}} \cdot \frac{XSTO/XH}{XSTO/XH + K_{h,STO}} \cdot \frac{SNH}{SNH + K_{h,NH4}} \cdot \frac{SALK}{SALK + K_{h,ALK}} \cdot XH` + * - :math:`r_6` + - Aerobic endogenous respiration of XH + - :math:`b_H \cdot \frac{SO}{SO + K_{h,O2}} \cdot XH` + * - :math:`r_7` + - Anoxic endogenous respiration of XH + - :math:`b_H \cdot \eta_{h,end} \cdot \frac{K_{h,O2}}{SO + K_{h,O2}} \cdot \frac{SNO}{SNO + K_{h,NO3}} \cdot XH` + * - :math:`r_8` + - Aerobic respiration of internal cell storage products + - :math:`b_H \cdot \frac{SO}{SO + K_{h,O2}} \cdot \frac{XSTO}{XSTO + K_{h,STO}} \cdot XH` + * - :math:`r_9` + - Anoxic respiration of internal cell storage products + - :math:`b_H \cdot \eta_{h,end} \cdot \frac{K_{h,O2}}{SO + K_{h,O2}} \cdot \frac{SNO}{SNO + K_{h,NO3}} \cdot \frac{XSTO}{XSTO + K_{h,STO}} \cdot XH` + * - :math:`r_{10}` + - Aerobic growth of XA + - :math:`\mu_{AUT} \cdot \frac{SO}{SO + K_{N,O2}} \cdot \frac{SNH}{SNH + K_{N,NH4}} \cdot \frac{SALK}{SALK + K_{N,ALK}} \cdot XA` + * - :math:`r_{11}` + - Aerobic endogenous respiration of XA + - :math:`b_{AUT} \cdot \frac{SO}{SO + K_{N,O2}} \cdot XA` + * - :math:`r_{12}` + - Anoxic endogenous respiration of XA + - :math:`b_{AUT} \cdot \eta_{N,end} \cdot \frac{K_{N,O2}}{SO + K_{N,O2}} \cdot \frac{SNO}{SNO + K_{h,NO3}} \cdot XA` + * - :math:`r_{13}` + - Aeration + - :math:`\frac{iO_2}{1000}` + + +And the stoichiometric equations are represented in the stoichiometric matrix :math:`S`, which is given by: + +.. list-table:: + :header-rows: 1 + :widths: 10 7 7 7 7 7 7 7 7 7 7 7 7 7 + + * - Component\\Reaction + - :math:`r_1` + - :math:`r_2` + - :math:`r_3` + - :math:`r_4` + - :math:`r_5` + - :math:`r_6` + - :math:`r_7` + - :math:`r_8` + - :math:`r_9` + - :math:`r_{10}` + - :math:`r_{11}` + - :math:`r_{12}` + - :math:`r_{13}` + * - :math:`SO` + - 0 + - :math:`Y_{STO,aer} - 1` + - :math:`Y_{STO,anox} - 1` + - :math:`1 - 1/Y_{H,aer}` + - :math:`1 - 1/Y_{H,anox}` + - :math:` f_{XI} - 1` + - :math:`f_{XI} - 1` + - -1 + - -1 + - :math:`-(64/14) \cdot 1/Y_A + 1` + - :math:`f_{XI} - 1` + - :math:`f_{XI} - 1` + - 1 + * - :math:`SS` + - :math:`1 - f_{SI}` + - -1 + - -1 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + * - :math:`SNH` + - :math:`i_{N,XS} - i_{N,SI} \cdot f_{SI} - (1 - f_{SI}) \cdot i_{N,SS}` + - :math:`i_{N,SS}` + - :math:`i_{N,SS}` + - :math:`-i_{N,BM}` + - :math:`-i_{N,BM}` + - :math:`i_{N,BM} - f_{XI} \cdot i_{N,XI}` + - :math:`i_{N,BM} -f_{XI} \cdot i_{N,XI}` + - 0 + - 0 + - :math:`-1/Y_A - i_{N,BM}` + - :math:`f_{XI} \cdot i_{N,XI} + i_{N,BM}` + - :math:`f_{XI} \cdot i_{N,XI} + i_{N,BM}` + - 0 + * - :math:`SNO` + - 0 + - 0 + - :math:`(Y_{STO,anox} - 1) / (40/14)` + - 0 + - :math:`(1 - 1/Y_{H,anox}) / (40/14)` + - 0 + - :math:`(f_{XI} - 1) / (40/14)` + - 0 + - :math:`-14/40` + - :math:`1/Y_A` + - 0 + - :math:`(f_{XI} - 1) / (40/14)` + - 0 + * - :math:`SN2` + - 0 + - 0 + - :math:`(1 - Y_{STO,anox}) / (40/14)` + - 0 + - :math:`(1/Y_{H,anox} - 1) / (40/14)` + - 0 + - :math:`(-f_{XI} - 1) / (40/14)` + - 0 + - :math:`--14/40` + - 0 + - 0 + - :math:`(1 + f_{XI} ) / (40/14)` + - 0 + * - :math:`SALK` + - :math:`(i_{N,XS} - i_{N,SI} \cdot f_{SI} - (1 - f_{SI}) \cdot i_{N,SS})/14` + - :math:`i_{N,SS} / 14` + - :math:`i_{N,SS} / 14 - (Y_{STO,anox} - 1) / 40` + - :math:`-i_{N,BM} / 14` + - :math:`-i_{N,BM} / 14 - (1 - 1/Y_{H,anox}) / 40` + - :math:`(-f_{XI} \cdot i_{N,XI} + i_{N,BM}) / 14` + - :math:`(-f_{XI} \cdot i_{N,XI} + i_{N,BM}) / 14 (-f_{XI} + 1) / 40` + - 0 + - :math:`1/40` + - :math:`-(1/Y_A) 1/7- i_{N,BM}/14` + - :math:`f_{XI} \cdot i_{N,XI} + i_{N,BM} / 14` + - :math:`(-f_{XI} \cdot i_{N,XI} + i_{N,BM})/14 - (f_{XI} - 1) / 40` + - 0 + * - :math:`SI` + - :math:`f_{SI}` + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + * - :math:`XI` + - 0 + - 0 + - 0 + - 0 + - 0 + - :math:`f_{XI}` + - :math:`f_{XI}` + - 0 + - 0 + - 0 + - :math:`f_{XI}` + - :math:`f_{XI}` + - 0 + * - :math:`XS` + - -1 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + * - :math:`XH` + - 0 + - 0 + - 0 + - 1 + - 1 + - -1 + - -1 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + * - :math:`XSTO` + - 0 + - :math:`Y_{STO,aer}` + - :math:`Y_{STO,anox}` + - :math:`-1/Y_{H,aer}` + - :math:`-1/Y_{H,anox}` + - 0 + - 0 + - -1 + - -1 + - 0 + - 0 + - 0 + - 0 + * - :math:`XA` + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 0 + - 1 + - -1 + - -1 + - 0 + * - :math:`XMI` + - 0 + - 0 + - 0 + - 0 + - 0 + - :math:`f_{XMI,BI}` + - :math:`f_{XMI,BI}` + - 0 + - 0 + - 0 + - :math:`f_{XMI,BI}` + - :math:`f_{XMI,BI}` + - 0 + + +.. figure:: images/netzwerk.jpg + :width: 80% + :align: center + + Network representation of the AMS3h model. For more information on model parameters required to define in CADET file format, see :ref:`activated_sludge_model_config`. + +Combining the ASM3 model +~~~~~~~~~~~~~~~~~~~~~~~~ + +The ASM3h model is a konkrete implementation of of the ASM framework which is aimled to be used as ASM3h in SIMBA. +But it is also flexible to to be used in the general CADET framework. +Here the reaction :math:`r_{13}` (aeration) is replaced by the usual interface of CADET, where the input of component is defined in the +inlet unit operation. In this cade the volume parameter taken into accound. +Moreover the model can be used in any unit operation and can be combined with other implemented reaction models. + + +References +~~~~~~~~~~ +- TODO \ No newline at end of file diff --git a/doc/modelling/reaction/images/netzwerk.jpg b/doc/modelling/reaction/images/netzwerk.jpg new file mode 100644 index 0000000000000000000000000000000000000000..fae20be83073655e1520992d73ca6ae420772fb7 GIT binary patch literal 234865 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

WG6V@Uh(5`u$x$tUBFk?dEcW}7xt?or_)26|X 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 z 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 literal 0 HcmV?d00001 From e40819324251a944e53ed43b20d8f3df7e421794 Mon Sep 17 00:00:00 2001 From: AntoniaBerger Date: Thu, 27 Nov 2025 11:47:55 +0100 Subject: [PATCH 47/48] Add option to deactive active aeration --- .../reaction/ActivatedSludgeModelThree.cpp | 78 +++++++++++++------ 1 file changed, 56 insertions(+), 22 deletions(-) diff --git a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp index ec0784710..6309310e5 100644 --- a/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp +++ b/src/libcadet/model/reaction/ActivatedSludgeModelThree.cpp @@ -183,8 +183,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase unsigned int _idxSI_ad = 13; unsigned int _idxSS_ad = 14; - bool _fractionate = false; - + bool _fractionate = false; + bool _activeAeration = true; virtual bool configureStoich(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx) @@ -198,26 +198,30 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _paramHandler.configure(paramProvider, _stoichiometry.columns(), _nComp, _nBoundStates); _paramHandler.registerParameters(_parameters, unitOpIdx, parTypeIdx, _nComp, _nBoundStates); - - _stoichiometry.resize(_nComp, 13); - _stoichiometry.setAll(0); - if (paramProvider.exists("ASM3_FRACTIONATE")) _fractionate = paramProvider.getBool("ASM3_FRACTIONATE"); - if (paramProvider.exists("ASM3_COMP_IDX")) { + if (_nComp < 13 && !_fractionate) + throw InvalidParameterException("ASM3 configuration: To use the ASM3 model the number of components must be at least 13"); + else if (_nComp < 15 && _fractionate) + throw InvalidParameterException("ASM3 configuration: To use the ASM3 model with fractionation the number of components must be at least 15"); + + if (paramProvider.exists("ASM3_COMP_IDX")) + { const std::vector compIdx = paramProvider.getUint64Array("ASM3_COMP_IDX"); if (_fractionate) { - if (compIdx.size() != 15) { + if (compIdx.size() != 15) throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 15 elements"); - } + _idxSS_ad = compIdx[13]; _idxSI_ad = compIdx[14]; } - else if (compIdx.size() != 13) { + else if (compIdx.size() != 13) + { throw InvalidParameterException("ASM3 configuration: ASM3_COMP_IDX must have 13 elements"); } + LOG(Debug) << "ASM3_COMP_IDX set: " << compIdx; _idxSO = compIdx[0]; _idxSS_nad = compIdx[1]; @@ -233,10 +237,30 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _idxXA = compIdx[11]; _idxXMI = compIdx[12]; } - else { + else + { LOG(Debug) << "ASM3_COMP_IDX not set, using defaults"; } + // handle optional Aeration + const double V = paramProvider.getDouble("ASM3_V"); + const double IO2 = paramProvider.getDouble("ASM3_IO2"); + + if ( V > 0 ) + { + LOG(Debug) << "Activating reaction aeration in ASM3"; + _activeAeration = true; + _stoichiometry.resize(_nComp, 13); + } + else + { + LOG(Debug) << "Deactivating reaction aeration in ASM3"; + _activeAeration = false; + _stoichiometry.resize(_nComp, 12); + } + + _stoichiometry.setAll(0); + // parameter set ASM3h const double iNSI = paramProvider.getDouble("ASM3_INSI"); const double iNSS = paramProvider.getDouble("ASM3_INSS"); @@ -300,8 +324,10 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxSO, 7) = -1; _stoichiometry.native(_idxSO, 9) = -(64.0 / 14.0) * 1 / YA + 1; _stoichiometry.native(_idxSO, 10) = -1 * (1 - fXI); - _stoichiometry.native(_idxSO, 12) = 1; + if (_activeAeration) + _stoichiometry.native(_idxSO, 12) = 1; + // SS_nad _stoichiometry.native(_idxSS_nad, 0) = (1 - fSI); _stoichiometry.native(_idxSS_nad, 1) = -1; @@ -395,9 +421,6 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase _stoichiometry.native(_idxXMI, 10) = fXMI_BM; _stoichiometry.native(_idxXMI, 11) = fXMI_BM; - - //registerCompRowMatrix(_parameters, unitOpIdx, parTypeIdx, "MM_STOICHIOMETRY_BULK", _stoichiometryBulk); todo - return true; } @@ -413,8 +436,18 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase const flux_t kh20 = static_cast::type>(p->kh20); const flux_t T = static_cast::type>(p->T); - const flux_t io2 = static_cast::type>(p->io2); - const flux_t V = static_cast::type>(p->V); + if (T < 0) + throw InvalidParameterException("ASM3 configuration: Temperature T must be non-negative"); + + flux_t io2; + flux_t V; + if (_activeAeration) + { + io2 = static_cast::type>(p->io2); + V = static_cast::type>(p->V); + if(V < 0) + throw InvalidParameterException("ASM3 configuration: Aeration volume V must be non-negative"); + } const flux_t k_sto20 = static_cast::type>(p->k_sto20); const flux_t kx = static_cast::type>(p->kx); const flux_t kho2 = static_cast::type>(p->kho2); @@ -506,8 +539,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase fluxes[11] = bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2) * XA; // r13: Aeration - // TODO: is V in litres? - fluxes[12] = io2 / V; + if (_activeAeration) + fluxes[12] = io2 / V; // Add reaction terms to residual _stoichiometry.multiplyVector(static_cast(fluxes), factor, res); @@ -588,10 +621,8 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase SI_ad = y[_idxSI_ad]; } - + // initialize jacobian double d[13][15] = {}; - - // p1: Hydrolysis: kh20 * ft04 * XS/XH_S / (XS/XH_S + kx) * XH; d[0][_idxXS] = kh20 * ft04 * XH / ((XS + XH * kx) @@ -839,6 +870,9 @@ class ActivatedSludgeModelThreeBase : public DynamicReactionModelBase d[11][_idxXA] = bAUT * etanend * SNO / (SNO + khn03) * kho2 / (SO + kho2); + + //reaction13: Aeration: io2 / V; + // no jacobian terms RowIterator curJac = jac; From de8c6b2a3b9a6c83ddaaa26cc02ef55c1d8e90fb Mon Sep 17 00:00:00 2001 From: AntoniaBerger Date: Thu, 27 Nov 2025 11:48:19 +0100 Subject: [PATCH 48/48] Update documentation for the active Aeration --- .../reaction/activated_sludge_model.rst | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/doc/modelling/reaction/activated_sludge_model.rst b/doc/modelling/reaction/activated_sludge_model.rst index f6c7a7c26..594c6b83a 100644 --- a/doc/modelling/reaction/activated_sludge_model.rst +++ b/doc/modelling/reaction/activated_sludge_model.rst @@ -311,14 +311,24 @@ And the stoichiometric equations are represented in the stoichiometric matrix :m For more information on model parameters required to define in CADET file format, see :ref:`activated_sludge_model_config`. -Combining the ASM3 model -~~~~~~~~~~~~~~~~~~~~~~~~ - +Combining the ASM3 model within CADET +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ The ASM3h model is a konkrete implementation of of the ASM framework which is aimled to be used as ASM3h in SIMBA. -But it is also flexible to to be used in the general CADET framework. -Here the reaction :math:`r_{13}` (aeration) is replaced by the usual interface of CADET, where the input of component is defined in the -inlet unit operation. In this cade the volume parameter taken into accound. -Moreover the model can be used in any unit operation and can be combined with other implemented reaction models. +But with different configuations it is also flexible to to be used in the general CADET framework. + +Active Aeration +--------------- +In configuation of the ASM3 model, there is the option to set the volume parameter of the aeration reaction. +This volume refers to the volume of the aeration tank. +But we recomend to model the aeration by setting up the Inlet unit Operation and connect it to the unit operation where the ASM3 model is used. +This way the aeration can be handled more flexible, see :ref:`inlet_operation`. +To deactivate the aeration reaction in the ASM3 model, set the volume parameter to zero. + +Fractionation +------------- + +TODO + References