diff --git a/CMakeLists.txt b/CMakeLists.txt index 406cac7b9..96f1b48ab 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -458,7 +458,7 @@ endif() target_compile_options(CADET::CompileOptions INTERFACE $<$,$,$>: -Wall -pedantic-errors -Wextra -Wno-unused-parameter -Wno-unused-function> #-Wconversion -Wsign-conversion $<$: - /W4> + /W4 /wd4100> ) add_library(CADET::AD INTERFACE IMPORTED) diff --git a/doc/interface/input_group.rst b/doc/interface/input_group.rst index e8e940176..12f98689d 100644 --- a/doc/interface/input_group.rst +++ b/doc/interface/input_group.rst @@ -15,5 +15,6 @@ Input Group return_data sensitivities solver + parameter_dependencies diff --git a/doc/interface/parameter_dependencies.rst b/doc/interface/parameter_dependencies.rst new file mode 100644 index 000000000..c632de17a --- /dev/null +++ b/doc/interface/parameter_dependencies.rst @@ -0,0 +1,79 @@ +.. _parameter_dependencies: + +Parameter Dependencies +====================== + +Some parameters depend on other parameters (parameter-parameter dependency) or the solution variables (parameter-state dependency). +Parameter dependencies are defined in the unit operation scope. + +Parameter-Parameter Dependencies +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Group /input/model/unit_XXX +--------------------------- + +``COL_DISPERSION_DEP`` + + Parameter dependence of column dispersion on the interstitial velocity. Available for the LRM, LRMP and GRM units (with FV discretization only at the moment) + + ================ ===================================== ============= + **Type:** string **Range:** :math:`\texttt{POWER_LAW}` **Length:** 1 + ================ ===================================== ============= + +``FILM_DIFFUSION_DEP`` + + Parameter dependence of film diffusion on the interstitial velocity. Available for the LRMP unit (with FV discretization only at the moment) + + ================ ===================================== ============= + **Type:** string **Range:** :math:`\texttt{POWER_LAW}` **Length:** 1 + ================ ===================================== ============= + + +**Correlations** +"""""""""""""""" + +Different types of parameter correlations are can be applied. +The following correlations can be used for all parameter-parameter dependencies, but we specify the required input fields only for ``COL_DISPERSION_DEP``, for the sake of conciseness. + +**Power Law** + +.. math:: + + \begin{aligned} + p_{dep} &= p_{dep} \cdot b \ |p_{on}^x| + \end{aligned} + +Here, :math:`p_{dep}` is the dependent parameter and :math:`p_{on}` is the parameter it depends on. + +``COL_DISPERSION_DEP_BASE`` + + Base :math:`b` of the power law parameter dependence. Optional, defaults to :math:`1.0` + + ================ ============================= ============= + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ============= + +``COL_DISPERSION_DEP_EXPONENT`` + + Exponent :math:`x` of the power law parameter dependence + + ================ ============================= ============= + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** 1 + ================ ============================= ============= + +``COL_DISPERSION_DEP_ABS`` + + Specifies whether or not the absolute value should be computed. Optional, defaults to :math:`1` + + ============= =========================== ============= + **Type:** int **Range:** :math:`\{0, 1\}` **Length:** 1 + ============= =========================== ============= + + +Parameter-State Dependencies +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Group /input/model/unit_XXX +--------------------------- + +Parameter-State Dependencies are not fully implemented yet. diff --git a/doc/interface/unit_operations/index.rst b/doc/interface/unit_operations/index.rst index 42cb27dd3..66f6ab81b 100644 --- a/doc/interface/unit_operations/index.rst +++ b/doc/interface/unit_operations/index.rst @@ -13,4 +13,5 @@ Unit Operations lumped_rate_model_without_pores 2d_general_rate_model cstr + radial_flow_models diff --git a/doc/interface/unit_operations/radial_flow_models.rst b/doc/interface/unit_operations/radial_flow_models.rst new file mode 100644 index 000000000..3349785ad --- /dev/null +++ b/doc/interface/unit_operations/radial_flow_models.rst @@ -0,0 +1,109 @@ +.. _radial_flow_models_config: + +Radial FLow Models +================== + +Radial flow models are available for the LRM, LRMP and GRM. +The configurations specified here complement the description for the respective model, i.e. please additionally refer to :ref:`lumped_rate_model_without_pores_config` or :ref:`lumped_rate_model_with_pores_config` or :ref:`general_rate_model_config`, respectively. +If input variables are described in both files, then only the description provided here applies for radial flow models. + +The unit type input must be specified with the prefix :math:`\texttt{RADIAL_}` followed by the respective transport model name. +In this document, we specify the unit type as the radial GRM, but the LRM and LRMP are also available and the changes to input variables are the same for all three models. + +Group /input/model/unit_XXX - UNIT_TYPE - RADIAL_GENERAL_RATE_MODEL +------------------------------------------------------------------- + +For information on model equations, refer to :ref:`lumped_rate_model_without_pores` or :ref:`lumped_rate_model_with_pores` or :ref:`general_rate_model`, respectively. + + +``UNIT_TYPE`` + + Specifies the type of unit operation model + + ================ ===================================================== ============= + **Type:** string **Range:** :math:`\texttt{RADIAL_GENERAL_RATE_MODEL}` **Length:** 1 + ================ ===================================================== ============= + +``COL_DISPERSION`` + + ´Radial dispersion coefficient + + **Unit:** :math:`\mathrm{m}_{\mathrm{IV}}^{2}\,\mathrm{s}^{-1}` + + ================ ========================= ========================================================= + **Type:** double **Range:** :math:`\geq 0` **Length:** see :math:`\texttt{COL_DISPERSION_MULTIPLEX}` + ================ ========================= ========================================================= + + In addition to the multiplex specification (e.g. component dependency, see :ref:`general_rate_model_model`), the dispersion coefficient for radial flow model usually depends on other parameters. + Parameter dependencies are described here :ref:`parameter_dependencies`. + + +``COL_RADIUS_INNER`` + + Inner column radius + + **Unit:** :math:`\mathrm{m}` + + ================ ====================== ============= + **Type:** double **Range:** :math:`> 0` **Length:** 1 + ================ ====================== ============= + +``COL_RADIUS_OUTER`` + + Outer column radius + + **Unit:** :math:`\mathrm{m}` + + ================ ====================== ============= + **Type:** double **Range:** :math:`> 0` **Length:** 1 + ================ ====================== ============= + +``CROSS_SECTION_AREA`` + + Is not explicitly specified. Both the inner and outer cross section areas are implicitly given by the volumetric flow rates and either the velocity coefficient or column length + +``COL_LENGTH`` + + Column length/height (optional if :math:`\texttt{VELOCITY_COEFF}` is present, see Section :ref:`MUOPGRMflow`) + + **Unit:** :math:`\mathrm{m}` + + ================ ====================== ============= + **Type:** double **Range:** :math:`> 0` **Length:** 1 + ================ ====================== ============= + +``VELOCITY_COEFF`` + + Interstitial velocity coefficient of the mobile phase (optional :math:`\texttt{COL_LENGTH}` is present, see Section :ref:`MUOPGRMflow`). + This input replaces the ``VELOCITY`` field, which is used for axial flow models. The distinction is made to emphasize that radial flow models do not incorporate a global velocity but a variable velocity field that depends on the spatial position, for details see Section :ref:`MUOPGRMradialFlow`. + + **Unit:** :math:`\mathrm{m}\,\mathrm{s}^{-1}` + + ================ ============================= ======================================= + **Type:** double **Range:** :math:`\mathbb{R}` **Length:** :math:`1 / \texttt{NSEC}` + ================ ============================= ======================================= + + +Group /input/model/unit_XXX/discretization - UNIT_TYPE - RADIAL_GENERAL_RATE_MODEL +---------------------------------------------------------------------------------------- + +``NCOL`` + + Number of radial column discretization points + + ============= ========================= ============= + **Type:** int **Range:** :math:`\geq 1` **Length:** 1 + ============= ========================= ============= + +Currently, there is only a first order FV spatial discretization available. Higher order spatial discretizations are planned for the future. +Accordingly, the following specifications can be left out for radial flow models. + +``RECONSTRUCTION`` + + Type of reconstruction method for fluxes + + ================ ================================ ============= + **Type:** string **Range:** :math:`\texttt{NONE}` **Length:** 1 + ================ ================================ ============= + +Parameters specified under :ref:`flux_restruction_methods` can also be ignored. \ No newline at end of file diff --git a/doc/modelling/unit_operations/general_rate_model.rst b/doc/modelling/unit_operations/general_rate_model.rst index c34dd2e16..384fc83a0 100644 --- a/doc/modelling/unit_operations/general_rate_model.rst +++ b/doc/modelling/unit_operations/general_rate_model.rst @@ -10,7 +10,7 @@ The main assumptions are: - The cross sections of the column are homogenous in terms of interstitial volume, fluid flow, and distribution of components. Thus, only one spatial coordinate in axial direction is needed and radial transport is neglected in the column bulk volume. -- The bead radii :math:`r_{p}` are much smaller than the column radius :math:`r_c` and the column length :math:`L`. +- The bead radii :math:`r_{p}` are much smaller than the column radius :math:`\mathrm{P}` and the column length :math:`L`. Therefore, the beads can be seen as continuously distributed inside the column (i.e., at each point there is interstitial and bead volume). .. _table_features: @@ -276,7 +276,7 @@ The direction of flow inside the unit operation is governed by the sign of the i A positive sign results in (standard) forward flow, whereas a negative sign reverses the flow direction. Note that in case of reversed flow, the chromatogram is returned at the unit operation’s `INLET`, which may not be returned from simulation by default. -The final behavior is controlled by the interplay of cross section area and interstitial velocity: +The final behavior for axial flow models is controlled by the interplay of cross section area and interstitial velocity: - If cross section area :math:`A` is given and :math:`u` is not, :math:`u` is inferred from the volumetric flow rate. @@ -284,4 +284,64 @@ The final behavior is controlled by the interplay of cross section area and inte - If both cross section area :math:`A` and interstitial velocity :math:`u` are given, the magnitude of the actual interstitial velocity :math:`u` is inferred from the volumetric flow rate and the flow direction is given by the sign of the provided :math:`u`. +The final behavior for radial flow models is controlled by the interplay of column length/height and interstitial velocity coefficient: + +- If :math:`L` is given, the interstitial velocity field is inferred from the volumetric flow rate. + +- If :math:`u` is given and :math:`L` is not, the provided interstitial velocity coefficient is used to calculate the interstitial velocity field. + + For information on model parameters see :ref:`general_rate_model_config`. + +.. _MUOPGRMradialFlow: + +Radial flow GRM +^^^^^^^^^^^^^^^ + +The radial flow GRM describes transport of solute molecules through the interstitial column volume by radial convective flow, band broadening caused by radial dispersion, mass transfer resistance through a stagnant film around the beads, pore (and surface) diffusion in the porous beads :cite:`Ma1996,Schneider1968a,Miyabe2007`, and adsorption to the inner bead surfaces. + +The main assumptions are: + +- The shells of the column are homogenous in terms of interstitial volume, fluid flow, and distribution of components. + Thus, only one spatial coordinate in radial direction :math:`\rho` is needed and axial transport is neglected in the column bulk volume. + +- The bead radii :math:`r_{p}` are much smaller than the column radius :math:`\mathrm{P}-\mathrm{P}_c`, with :math:`\mathrm{P}` and :math:`\mathrm{P}_c` being the inner and outer column radius respectively, and the column length :math:`L`. + Therefore, the beads can be seen as continuously distributed inside the column (i.e., at each point there is interstitial and bead volume). + +- The fluids are incompressible, i.e. the velocity field :math:`\mathrm{V} \colon \mathbb{R}^3 \to \mathbb{R}^3` submits to :math:`\operatorname{div}\left( \mathrm{V} \right) \equiv 0`. + That is, the volumetric flow rate at the inner and outer column radius are the same. + +Consider a hollow (double walled) column with inner column diameter :math:`\mathrm{P}_c>0` and outer diameter :math:`\mathrm{P}>\mathrm{P}_c`, filled with spherical beads of (possibly) multiple types with radius :math:`r_{p,j} \ll L` (see :numref:`ModelGRMColumn`), where :math:`j` is the particle type index. The mass balance in the interstitial column volume is described by + +.. math:: + :label: ModelRadialColumn + + \begin{aligned} + \frac{\partial c^l_i}{\partial t} = -\frac{u}{\rho} \frac{\partial c^l_i}{\partial \rho} + D_{\text{rad},i} \frac{1}{\rho} \frac{\partial}{\partial \rho} \left(\rho \frac{\partial c^l_i}{\partial \rho} \right) &- \frac{1}{\beta_c} \sum_j d_j \frac{3}{r_{p,j}} k_{f,j,i} \left[ c^l_i - c^p_{j,i}(\cdot, \cdot, r_{p,j}) \right] \\ + &+ f_{\text{react},i}^l\left(c^l\right). + \end{aligned} + +Here, :math:`c^l_i\colon \left[0, T_{\text{end}}\right] \times [\mathrm{P}_c, \mathrm{P}] \rightarrow \mathbb{R}^{\geq 0}` denotes the concentration in the interstitial column volume, :math:`c^p_{j,i}\colon \left[0, T_{\text{end}}\right] \times [P_c, P] \times [r_{c,j}, r_{p,j}] \rightarrow \mathbb{R}^{\geq 0}` the liquid phase concentration in the beads, :math:`k_{f,j,i}` the film diffusion coefficient, :math:`D_{\text{rad},i}` the dispersion coefficient, :math:`u` the interstitial velocity, :math:`d_j` the volume fraction of particle type :math:`j`, and :math:`\beta_c = \varepsilon_c / (1 - \varepsilon_c)` the column phase ratio, where :math:`\varepsilon_c` is the column porosity (ratio of interstitial volume to total column volume). +If reactions are considered, the term :math:`f_{\text{react},i}^l\left(c^l\right)` represents the net change of concentration :math:`c_i` due to reactions involving component :math:`i`. + +Danckwerts boundary conditions :cite:`Danckwerts1953` are applied to inlet and outlet of the column: + +.. math:: + :label: BCOutlet + + \begin{aligned} + u c_{\text{in},i}(t) &= u c^l_i(t,0) - D_{\text{rad},i} \frac{\partial c^l_i}{\partial \rho}(t, 0) & \forall t > 0, + \end{aligned} + +.. math:: + :label: BCInlet + + \begin{aligned} + \frac{\partial c^l_i}{\partial \rho}(t, \mathrm{P}) &= 0 & \forall t > 0. + \end{aligned} + +Note that the outlet boundary condition Eq. :eq:`BCOutlet` is also known as “do nothing” or natural outflow condition. + +The complementing mass transport and binding equations for the liquid and solid phases of the porous beads are described by the same equations as for the axial GRM. + +For information on model parameters see :ref:`radial_flow_models_config` in addition to :ref:`general_rate_model_config`. diff --git a/doc/modelling/unit_operations/index.rst b/doc/modelling/unit_operations/index.rst index f98059d85..19f953386 100644 --- a/doc/modelling/unit_operations/index.rst +++ b/doc/modelling/unit_operations/index.rst @@ -50,6 +50,7 @@ is given in :numref:`table_features_unit_operations`. Moreover, the pseudo unit operations :ref:`inlet_model`, and :ref:`outlet_model` act as sources and sinks for the system. +We further note that radial flow model variants are available for the LRM, LRMP and GRM. .. toctree:: diff --git a/doc/modelling/unit_operations/lumped_rate_model_with_pores.rst b/doc/modelling/unit_operations/lumped_rate_model_with_pores.rst index 49bd53c7f..b370bece0 100644 --- a/doc/modelling/unit_operations/lumped_rate_model_with_pores.rst +++ b/doc/modelling/unit_operations/lumped_rate_model_with_pores.rst @@ -14,6 +14,7 @@ Hence, the model equations are given by \end{aligned} .. math:: + :label: ModelParticleLRMP \begin{aligned} \frac{\partial c^p_{j,i}}{\partial t} + \frac{1 - \varepsilon_{p,j}}{F_{\text{acc},j,i} \varepsilon_{p,j}} \frac{\partial}{\partial t} \sum_{m_{j,i}} c^s_{j,i,m_{j,i}} &= \frac{3}{F_{\text{acc},j,i} \varepsilon_{p,j} r_{p,j}}k_{f,j,i}\left[ c^l_i - c^p_{j,i} \right] \\ @@ -52,3 +53,40 @@ This model can also be used to simulate :ref:`MUOPGRMSizeExclusion`. For the specification of flow rate and direction, the same holds as for the general rate model (see Section :ref:`MUOPGRMflow`). For information on model parameters see :ref:`lumped_rate_model_with_pores_config`. + +Radial flow LRMP +^^^^^^^^^^^^^^^^ + +The radial flow LRMP describes transport of solute molecules through the interstitial column volume by radial convective flow, band broadening caused by radial dispersion, mass transfer resistance through a stagnant film around the beads, and adsorption to the inner bead surfaces. + +The main assumptions are: + +- The shells of the column are homogenous in terms of interstitial volume, fluid flow, and distribution of components. + Thus, only one spatial coordinate in radial direction :math:`\rho` is needed and axial transport is neglected in the column bulk volume. + +- The bead radii :math:`r_{p}` are much smaller than the column radius :math:`\mathrm{P}-\mathrm{P}_c`, with :math:`\mathrm{P}` and :math:`\mathrm{P}_c` being the inner and outer column radius respectively, and the column length :math:`L`. + Therefore, the beads can be seen as continuously distributed inside the column (i.e., at each point there is interstitial and bead volume). + +- The fluids are incompressible, i.e. the velocity field :math:`\mathrm{V} \colon \mathbb{R}^3 \to \mathbb{R}^3` submits to :math:`\operatorname{div}\left( \mathrm{V} \right) \equiv 0`. + That is, the volumetric flow rate at the inner and outer column radius are the same. + +Consider a hollow (double walled) column with inner column diameter :math:`\mathrm{P}_c>0` and outer diameter :math:`\mathrm{P}>\mathrm{P}_c`, filled with spherical beads of (possibly) multiple types with radius :math:`r_{p,j} \ll L` (see :numref:`ModelGRMColumn`), where :math:`j` is the particle type index. The mass balance in the interstitial column volume is described by + +.. math:: + + \begin{aligned} + \frac{\partial c^l_i}{\partial t} &= -\frac{u}{\rho} \frac{\partial c^l_i}{\partial \rho} + D_{\text{rad},i} \frac{1}{\rho} \frac{\partial}{\partial \rho} \left( \rho \frac{\partial c^l_i}{\partial \rho} \right) - \frac{1}{\beta_c} \sum_{j} d_j \frac{3}{r_{p,j}} k_{f,j,i}\left[ c^l_i - c^p_{j,i} \right] + f_{\text{react},i}^l\left(c^l\right), + \end{aligned} + +The equations are complemented by Eq. :ref:`ModelParticleLRMP` and the Danckwerts boundary conditions :cite:`Danckwerts1953` + +.. math:: + + \begin{aligned} + u c_{\text{in},i}(t) &= u c^l_i(t,0) - D_{\text{rad},i} \frac{\partial c^l_i}{\partial \rho}(t, 0) & \forall t > 0,\\ + \frac{\partial c^l_i}{\partial \rho}(t, \mathrm{P}) &= 0 & \forall t > 0. + \end{aligned} + +The complementing binding equations are described by the same equations as for the axial LRMP. + +For information on model parameters see :ref:`radial_flow_models_config` in addition to :ref:`lumped_rate_model_with_pores_config`. diff --git a/doc/modelling/unit_operations/lumped_rate_model_without_pores.rst b/doc/modelling/unit_operations/lumped_rate_model_without_pores.rst index d68d7aa3d..cfc9f57d6 100644 --- a/doc/modelling/unit_operations/lumped_rate_model_without_pores.rst +++ b/doc/modelling/unit_operations/lumped_rate_model_without_pores.rst @@ -53,3 +53,41 @@ Note that by setting :math:`\varepsilon_t = 1`, removing all bound states by set For the specification of flow rate and direction, the same holds as for the general rate model (see Section :ref:`MUOPGRMflow`). For information on model parameters see :ref:`lumped_rate_model_without_pores_config`. + +Radial flow LRM +^^^^^^^^^^^^^^^ + +The radial flow LRM describes transport of solute molecules through the interstitial column volume by radial convective flow, band broadening caused by radial dispersion, and adsorption to the bead surfaces. + +The main assumptions are: + +- The shells of the column are homogenous in terms of interstitial volume, fluid flow, and distribution of components. + Thus, only one spatial coordinate in radial direction :math:`\rho` is needed and axial transport is neglected in the column bulk volume. + +- The bead radii :math:`r_{p}` are much smaller than the column radius :math:`\mathrm{P}-\mathrm{P}_c`, with :math:`\mathrm{P}` and :math:`\mathrm{P}_c` being the inner and outer column radius respectively, and the column length :math:`L`. + Therefore, the beads can be seen as continuously distributed inside the column (i.e., at each point there is interstitial and bead volume). + +- The fluids are incompressible, i.e. the velocity field :math:`\mathrm{V} \colon \mathbb{R}^3 \to \mathbb{R}^3` submits to :math:`\operatorname{div}\left( \mathrm{V} \right) \equiv 0`. + That is, the volumetric flow rate at the inner and outer column radius are the same. + +Consider a hollow (double walled) column with inner column diameter :math:`\mathrm{P}_c>0` and outer diameter :math:`\mathrm{P}>\mathrm{P}_c`, filled with spherical beads. The mass balance in the interstitial column volume is described by + +.. math:: + :label: ModelRadialColumn + + \begin{aligned} + \frac{\partial c^l_i}{\partial t} + \frac{1}{\beta_t} \frac{\partial}{\partial t} \sum_{m_i} c^s_{i,m_i} &= -\frac{u}{\rho} \frac{\partial c^l_i}{\partial \rho} + D_{\text{rad},i} \frac{1}{\rho} \frac{\partial}{\partial \rho} \left( \rho \frac{\partial c^l_i}{\partial \rho} \right) + f_{\text{react},i}^l\left( c^l, c^s \right) + \frac{1}{\beta_t} f_{\text{react},i}^s\left( c^l, c^s \right), + \end{aligned} + +The equations are complemented by Danckwerts boundary conditions :cite:`Danckwerts1953` + +.. math:: + + \begin{aligned} + u c_{\text{in},i}(t) &= u c^l_i(t,0) - D_{\text{rad},i} \frac{\partial c^l_i}{\partial \rho}(t, 0) & \forall t > 0,\\ + \frac{\partial c^l_i}{\partial \rho}(t, \mathrm{P}) &= 0 & \forall t > 0. + \end{aligned} + +The complementing binding equations are described by the same equations as for the axial LRM. + +For information on model parameters see :ref:`radial_flow_models_config` in addition to :ref:`lumped_rate_model_without_pores_config`. diff --git a/src/libcadet/CMakeLists.txt b/src/libcadet/CMakeLists.txt index 2bd8d6422..aa8312a41 100644 --- a/src/libcadet/CMakeLists.txt +++ b/src/libcadet/CMakeLists.txt @@ -133,14 +133,11 @@ set(LIBCADET_SOURCES ${CMAKE_SOURCE_DIR}/src/libcadet/model/StirredTankModel.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithoutPores.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithPores.cpp - ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithPores-LinearSolver.cpp - ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithPores-InitialConditions.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/GeneralRateModel.cpp - ${CMAKE_SOURCE_DIR}/src/libcadet/model/GeneralRateModel-LinearSolver.cpp - ${CMAKE_SOURCE_DIR}/src/libcadet/model/GeneralRateModel-InitialConditions.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/ParameterMultiplexing.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/parts/ConvectionDispersionOperator.cpp - ${CMAKE_SOURCE_DIR}/src/libcadet/model/parts/ConvectionDispersionKernel.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/parts/AxialConvectionDispersionKernel.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/parts/RadialConvectionDispersionKernel.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/reaction/ReactionModelBase.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/binding/BindingModelBase.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/inlet/PiecewiseCubicPoly.cpp @@ -149,6 +146,8 @@ set(LIBCADET_SOURCES ${CMAKE_SOURCE_DIR}/src/libcadet/model/paramdep/ParameterDependenceBase.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/paramdep/LiquidSaltSolidParameterDependence.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/paramdep/DummyParameterDependence.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/paramdep/IdentityParameterDependence.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/paramdep/PowerLawParameterDependence.cpp ) # LIBCADET_NONLINALG_SOURCES holds all source files for LIBCADET_NONLINALG target diff --git a/src/libcadet/ConfigurationHelper.hpp b/src/libcadet/ConfigurationHelper.hpp index a7d305c4f..1924ee850 100644 --- a/src/libcadet/ConfigurationHelper.hpp +++ b/src/libcadet/ConfigurationHelper.hpp @@ -31,7 +31,8 @@ class IExternalFunction; { class IBindingModel; class IDynamicReactionModel; - class IParameterDependence; + class IParameterStateDependence; + class IParameterParameterDependence; } /** @@ -80,19 +81,34 @@ class IConfigHelper virtual bool isValidDynamicReactionModel(const std::string& name) const = 0; /** - * @brief Creates an IParameterDependence object of the given @p name - * @details The caller owns the returned IParameterDependence object. - * @param [in] name Name of the IParameterDependence object - * @return Object of the given IParameterDependence @p name or @c nullptr if that name does not exist + * @brief Creates an IParameterStateDependence object of the given @p name + * @details The caller owns the returned IParameterStateDependence object. + * @param [in] name Name of the IParameterStateDependence object + * @return Object of the given IParameterStateDependence @p name or @c nullptr if that name does not exist */ - virtual model::IParameterDependence* createParameterDependence(const std::string& name) const = 0; + virtual model::IParameterStateDependence* createParameterStateDependence(const std::string& name) const = 0; /** - * @brief Checks if there is an IParameterDependence of the given @p name - * @param [in] name Name of the IParameterDependence object + * @brief Checks if there is an IParameterStateDependence of the given @p name + * @param [in] name Name of the IParameterStateDependence object * @return @c true if a dynamic reaction model of this name exists, otherwise @c false */ - virtual bool isValidParameterDependence(const std::string& name) const = 0; + virtual bool isValidParameterStateDependence(const std::string& name) const = 0; + + /** + * @brief Creates an IParameterParameterDependence object of the given @p name + * @details The caller owns the returned IParameterParameterDependence object. + * @param [in] name Name of the IParameterParameterDependence object + * @return Object of the given IParameterParameterDependence @p name or @c nullptr if that name does not exist + */ + virtual model::IParameterParameterDependence* createParameterParameterDependence(const std::string& name) const = 0; + + /** + * @brief Checks if there is an IParameterParameterDependence of the given @p name + * @param [in] name Name of the IParameterParameterDependence object + * @return @c true if a dynamic reaction model of this name exists, otherwise @c false + */ + virtual bool isValidParameterParameterDependence(const std::string& name) const = 0; /** * @brief Creates an IExternalFunction object of the given @p type diff --git a/src/libcadet/ModelBuilderImpl.cpp b/src/libcadet/ModelBuilderImpl.cpp index 7004ee565..2067a1877 100644 --- a/src/libcadet/ModelBuilderImpl.cpp +++ b/src/libcadet/ModelBuilderImpl.cpp @@ -274,14 +274,24 @@ namespace cadet return _reactionModels.existsDynamic(name); } - model::IParameterDependence* ModelBuilder::createParameterDependence(const std::string& name) const + model::IParameterStateDependence* ModelBuilder::createParameterStateDependence(const std::string& name) const { - return _paramDeps.create(name); + return _paramDeps.createStateDependence(name); } - bool ModelBuilder::isValidParameterDependence(const std::string& name) const + bool ModelBuilder::isValidParameterStateDependence(const std::string& name) const { - return _paramDeps.exists(name); + return _paramDeps.stateDependenceExists(name); + } + + model::IParameterParameterDependence* ModelBuilder::createParameterParameterDependence(const std::string& name) const + { + return _paramDeps.createParameterDependence(name); + } + + bool ModelBuilder::isValidParameterParameterDependence(const std::string& name) const + { + return _paramDeps.parameterDependenceExists(name); } } // namespace cadet diff --git a/src/libcadet/ModelBuilderImpl.hpp b/src/libcadet/ModelBuilderImpl.hpp index 7a1648a6a..1f8920eee 100644 --- a/src/libcadet/ModelBuilderImpl.hpp +++ b/src/libcadet/ModelBuilderImpl.hpp @@ -63,8 +63,10 @@ class ModelBuilder : public IModelBuilder, public IConfigHelper virtual bool isValidBindingModel(const std::string& name) const; virtual model::IDynamicReactionModel* createDynamicReactionModel(const std::string& name) const; virtual bool isValidDynamicReactionModel(const std::string& name) const; - virtual model::IParameterDependence* createParameterDependence(const std::string& name) const; - virtual bool isValidParameterDependence(const std::string& name) const; + virtual model::IParameterStateDependence* createParameterStateDependence(const std::string& name) const; + virtual bool isValidParameterStateDependence(const std::string& name) const; + virtual model::IParameterParameterDependence* createParameterParameterDependence(const std::string& name) const; + virtual bool isValidParameterParameterDependence(const std::string& name) const; virtual IExternalFunction* createExternalFunction(const std::string& type) const; protected: @@ -87,7 +89,7 @@ class ModelBuilder : public IModelBuilder, public IConfigHelper BindingModelFactory _bindingModels; //!< Factory for IBindingModel implementations ReactionModelFactory _reactionModels; //!< Factory for IDynamicReactionModel implementations - ParameterDependenceFactory _paramDeps; //!< Factory for IParameterDependence implementations + ParameterDependenceFactory _paramDeps; //!< Factory for IParameterStateDependence implementations typedef std::unordered_map> ModelFactoryContainer_t; typedef std::unordered_map> InletFactoryContainer_t; diff --git a/src/libcadet/ParameterDependenceFactory.cpp b/src/libcadet/ParameterDependenceFactory.cpp index 19d7ac88d..ad165dfe9 100644 --- a/src/libcadet/ParameterDependenceFactory.cpp +++ b/src/libcadet/ParameterDependenceFactory.cpp @@ -19,38 +19,74 @@ namespace cadet { namespace paramdep { - void registerLiquidSaltSolidParamDependence(std::unordered_map>& paramDeps); - void registerDummyParamDependence(std::unordered_map>& paramDeps); + void registerLiquidSaltSolidParamDependence(std::unordered_map>& paramDeps); + void registerDummyParamDependence(std::unordered_map>& paramDeps); + void registerDummyParamDependence(std::unordered_map>& paramDeps); + void registerIdentityParamDependence(std::unordered_map>& paramDeps); + void registerIdentityParamDependence(std::unordered_map>& paramDeps); + void registerPowerLawParamDependence(std::unordered_map>& paramDeps); } } ParameterDependenceFactory::ParameterDependenceFactory() { - // Register all ParameterDependencies here - model::paramdep::registerLiquidSaltSolidParamDependence(_paramDeps); - model::paramdep::registerDummyParamDependence(_paramDeps); + // Register all ParamState dependencies + model::paramdep::registerLiquidSaltSolidParamDependence(_paramStateDeps); + model::paramdep::registerDummyParamDependence(_paramStateDeps); + model::paramdep::registerIdentityParamDependence(_paramStateDeps); + + // Register all ParamParam dependencies + model::paramdep::registerDummyParamDependence(_paramParamDeps); + model::paramdep::registerIdentityParamDependence(_paramParamDeps); + model::paramdep::registerPowerLawParamDependence(_paramParamDeps); } ParameterDependenceFactory::~ParameterDependenceFactory() { } template - void ParameterDependenceFactory::registerModel(const std::string& name) + void ParameterDependenceFactory::registerStateDependence(const std::string& name) { - _paramDeps[name] = []() { return new ParamDep_t(); }; + _paramStateDeps[name] = []() { return new ParamDep_t(); }; } template - void ParameterDependenceFactory::registerModel() + void ParameterDependenceFactory::registerStateDependence() { - registerModel(ParamDep_t::identifier()); + registerStateDependence(ParamDep_t::identifier()); + } + + template + void ParameterDependenceFactory::registerParameterDependence(const std::string& name) + { + _paramParamDeps[name] = []() { return new ParamDep_t(); }; + } + + template + void ParameterDependenceFactory::registerParameterDependence() + { + registerParameterDependence(ParamDep_t::identifier()); + } + + model::IParameterStateDependence* ParameterDependenceFactory::createStateDependence(const std::string& name) const + { + const auto it = _paramStateDeps.find(name); + if (it == _paramStateDeps.end()) + { + // ParameterDependence was not found + return nullptr; + } + + // Call factory function (thanks to type erasure of std::function we can store + // all factory functions in one container) + return it->second(); } - model::IParameterDependence* ParameterDependenceFactory::create(const std::string& name) const + model::IParameterParameterDependence* ParameterDependenceFactory::createParameterDependence(const std::string& name) const { - const auto it = _paramDeps.find(name); - if (it == _paramDeps.end()) + const auto it = _paramParamDeps.find(name); + if (it == _paramParamDeps.end()) { - // ParameterDependencieswas not found + // ParameterDependence was not found return nullptr; } @@ -59,16 +95,29 @@ namespace cadet return it->second(); } - void ParameterDependenceFactory::registerModel(const std::string& name, std::function factory) + void ParameterDependenceFactory::registerModel(const std::string& name, std::function factory) { - if (_paramDeps.find(name) == _paramDeps.end()) - _paramDeps[name] = factory; + if (_paramStateDeps.find(name) == _paramStateDeps.end()) + _paramStateDeps[name] = factory; else - throw InvalidParameterException("IParameterDependence implementation with the name " + name + " is already registered and cannot be overwritten"); + throw InvalidParameterException("IParameterStateDependence implementation with the name " + name + " is already registered and cannot be overwritten"); + } + + void ParameterDependenceFactory::registerModel(const std::string& name, std::function factory) + { + if (_paramParamDeps.find(name) == _paramParamDeps.end()) + _paramParamDeps[name] = factory; + else + throw InvalidParameterException("IParameterParameterDependence implementation with the name " + name + " is already registered and cannot be overwritten"); + } + + bool ParameterDependenceFactory::stateDependenceExists(const std::string& name) const + { + return _paramStateDeps.find(name) != _paramStateDeps.end(); } - bool ParameterDependenceFactory::exists(const std::string& name) const + bool ParameterDependenceFactory::parameterDependenceExists(const std::string& name) const { - return _paramDeps.find(name) != _paramDeps.end(); + return _paramParamDeps.find(name) != _paramParamDeps.end(); } } // namespace cadet diff --git a/src/libcadet/ParameterDependenceFactory.hpp b/src/libcadet/ParameterDependenceFactory.hpp index dbbffc21d..9128c9ea0 100644 --- a/src/libcadet/ParameterDependenceFactory.hpp +++ b/src/libcadet/ParameterDependenceFactory.hpp @@ -27,7 +27,8 @@ namespace cadet namespace model { - class IParameterDependence; + class IParameterStateDependence; + class IParameterParameterDependence; } /** @@ -49,40 +50,78 @@ namespace cadet * @param [in] name Name of the parameter dependence * @return The parameter dependence or @c NULL if a parameter dependence with this name does not exist */ - model::IParameterDependence* create(const std::string& name) const; + model::IParameterStateDependence* createStateDependence(const std::string& name) const; + + /** + * @brief Creates parameter dependencies with the given @p name + * @param [in] name Name of the parameter dependence + * @return The parameter dependence or @c NULL if a parameter dependence with this name does not exist + */ + model::IParameterParameterDependence* createParameterDependence(const std::string& name) const; + + /** + * @brief Registers the given parameter dependence implementation + * @param [in] name Name of the IParameterStateDependence implementation + * @param [in] factory Function that creates an object of the IParameterStateDependence class + */ + void registerModel(const std::string& name, std::function factory); /** * @brief Registers the given parameter dependence implementation - * @param [in] name Name of the IParameterDependence implementation - * @param [in] factory Function that creates an object of the IParameterDependence class + * @param [in] name Name of the IParameterParameterDependence implementation + * @param [in] factory Function that creates an object of the IParameterParameterDependence class */ - void registerModel(const std::string& name, std::function factory); + void registerModel(const std::string& name, std::function factory); /** * @brief Returns whether a parameter dependence of the given name @p name exists * @param [in] name Name of the parameter dependence * @return @c true if a parameter dependence of this name exists, otherwise @c false */ - bool exists(const std::string& name) const; + bool stateDependenceExists(const std::string& name) const; + + /** + * @brief Returns whether a parameter dependence of the given name @p name exists + * @param [in] name Name of the parameter dependence + * @return @c true if a parameter dependence of this name exists, otherwise @c false + */ + bool parameterDependenceExists(const std::string& name) const; protected: /** - * @brief Registers an IParameterDependence + * @brief Registers an IParameterStateDependence + * @param [in] name Name of the parameter dependence + * @tparam ParamDep_t Type of the parameter dependence + */ + template + void registerStateDependence(const std::string& name); + + /** + * @brief Registers an IParameterStateDependence + * @details The name of the parameter dependence is inferred from the static function IParameterStateDependence::identifier(). + * @tparam ParamDep_t Type of the parameter dependence + */ + template + void registerStateDependence(); + + /** + * @brief Registers an IParameterParameterDependence * @param [in] name Name of the parameter dependence * @tparam ParamDep_t Type of the parameter dependence */ template - void registerModel(const std::string& name); + void registerParameterDependence(const std::string& name); /** - * @brief Registers an IParameterDependence - * @details The name of the parameter dependence is inferred from the static function IParameterDependence::identifier(). + * @brief Registers an IParameterParameterDependence + * @details The name of the parameter dependence is inferred from the static function IParameterParameterDependence::identifier(). * @tparam ParamDep_t Type of the parameter dependence */ template - void registerModel(); + void registerParameterDependence(); - std::unordered_map> _paramDeps; //!< Map with factory functions + std::unordered_map> _paramStateDeps; //!< Map with factory functions + std::unordered_map> _paramParamDeps; //!< Map with factory functions }; } // namespace cadet diff --git a/src/libcadet/SimulatableModel.hpp b/src/libcadet/SimulatableModel.hpp index 248c06456..c1ccfa035 100644 --- a/src/libcadet/SimulatableModel.hpp +++ b/src/libcadet/SimulatableModel.hpp @@ -91,7 +91,7 @@ class ISimulatableModel : public IModelSystem * @param [in] helper Used to inject or create required objects * @return @c true if the configuration was successful, otherwise @c false */ - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) = 0; + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) = 0; /** * @brief (Re-)configures the model by extracting all non-structural parameters (e.g., model parameters) from the given @p paramProvider diff --git a/src/libcadet/model/GeneralRateModel-InitialConditions.cpp b/src/libcadet/model/GeneralRateModel-InitialConditions.cpp index 1ae434851..5827e900d 100644 --- a/src/libcadet/model/GeneralRateModel-InitialConditions.cpp +++ b/src/libcadet/model/GeneralRateModel-InitialConditions.cpp @@ -38,7 +38,8 @@ namespace cadet namespace model { -int GeneralRateModel::multiplexInitialConditions(const cadet::ParameterId& pId, unsigned int adDirection, double adValue) +template +int GeneralRateModel::multiplexInitialConditions(const cadet::ParameterId& pId, unsigned int adDirection, double adValue) { if (_singleBinding) { @@ -81,7 +82,8 @@ int GeneralRateModel::multiplexInitialConditions(const cadet::ParameterId& pId, return 0; } -int GeneralRateModel::multiplexInitialConditions(const cadet::ParameterId& pId, double val, bool checkSens) +template +int GeneralRateModel::multiplexInitialConditions(const cadet::ParameterId& pId, double val, bool checkSens) { if (_singleBinding) { @@ -132,7 +134,8 @@ int GeneralRateModel::multiplexInitialConditions(const cadet::ParameterId& pId, return 0; } -void GeneralRateModel::applyInitialCondition(const SimulationState& simState) const +template +void GeneralRateModel::applyInitialCondition(const SimulationState& simState) const { Indexer idxr(_disc); @@ -188,7 +191,8 @@ void GeneralRateModel::applyInitialCondition(const SimulationState& simState) co } } -void GeneralRateModel::readInitialCondition(IParameterProvider& paramProvider) +template +void GeneralRateModel::readInitialCondition(IParameterProvider& paramProvider) { _initState.clear(); _initStateDot.clear(); @@ -302,7 +306,8 @@ void GeneralRateModel::readInitialCondition(IParameterProvider& paramProvider) * @param [in] errorTol Error tolerance for algebraic equations * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void GeneralRateModel::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +template +void GeneralRateModel::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -355,7 +360,7 @@ void GeneralRateModel::consistentInitialState(const SimulationTime& simTime, dou linalg::DenseMatrixView fullJacobianMatrix(_jacPdisc[type * _disc.nCol + pblk].data(), nullptr, mask.len, mask.len); // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = (0.5 + static_cast(pblk)) / static_cast(_disc.nCol); + const double z = _convDispOp.relativeCoordinate(pblk); // Get workspace memory BufferedArray nonlinMemBuffer = tlmAlloc.array(_nonlinearSolver->workspaceSize(probSize)); @@ -641,7 +646,8 @@ void GeneralRateModel::consistentInitialState(const SimulationTime& simTime, dou * @param [in] vecStateY Consistently initialized state vector * @param [in,out] vecStateYdot On entry, residual without taking time derivatives into account. On exit, consistent state time derivatives. */ -void GeneralRateModel::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) +template +void GeneralRateModel::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -670,7 +676,7 @@ void GeneralRateModel::consistentInitialTimeDerivative(const SimulationTime& sim const unsigned int par = pblk % _disc.nCol; // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = (0.5 + static_cast(par)) / static_cast(_disc.nCol); + const double z = _convDispOp.relativeCoordinate(par); // Assemble linalg::FactorizableBandMatrix& fbm = _jacPdisc[pblk]; @@ -793,7 +799,8 @@ void GeneralRateModel::consistentInitialTimeDerivative(const SimulationTime& sim * @param [in,out] adJac Jacobian information for AD (AD vectors for residual and state, direction offset) * @param [in] errorTol Error tolerance for algebraic equations */ -void GeneralRateModel::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +template +void GeneralRateModel::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) { if (isSectionDependent(_parDiffusionMode) || isSectionDependent(_parSurfDiffusionMode)) LOG(Warning) << "Lean consistent initialization is not appropriate for section-dependent pore and surface diffusion"; @@ -819,7 +826,7 @@ void GeneralRateModel::leanConsistentInitialState(const SimulationTime& simTime, LinearBufferAllocator tlmAlloc = threadLocalMem.get(); // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = (0.5 + static_cast(pblk)) / static_cast(_disc.nCol); + const double z = _convDispOp.relativeCoordinate(pblk); const int localOffsetToParticle = idxr.offsetCp(ParticleTypeIndex{type}, ParticleIndex{static_cast(pblk)}); for(std::size_t shell = 0; shell < static_cast(_disc.nParCell[type]); ++shell) @@ -889,7 +896,8 @@ void GeneralRateModel::leanConsistentInitialState(const SimulationTime& simTime, * @param [in,out] vecStateYdot On entry, inconsistent state time derivatives. On exit, partially consistent state time derivatives. * @param [in] res On entry, residual without taking time derivatives into account. The data is overwritten during execution of the function. */ -void GeneralRateModel::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) +template +void GeneralRateModel::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) { if (isSectionDependent(_parDiffusionMode) || isSectionDependent(_parSurfDiffusionMode)) LOG(Warning) << "Lean consistent initialization is not appropriate for section-dependent pore and surface diffusion"; @@ -925,7 +933,8 @@ void GeneralRateModel::leanConsistentInitialTimeDerivative(double t, double cons solveForFluxes(vecStateYdot, idxr); } -void GeneralRateModel::initializeSensitivityStates(const std::vector& vecSensY) const +template +void GeneralRateModel::initializeSensitivityStates(const std::vector& vecSensY) const { Indexer idxr(_disc); for (std::size_t param = 0; param < vecSensY.size(); ++param) @@ -1020,7 +1029,8 @@ void GeneralRateModel::initializeSensitivityStates(const std::vector& v * @param [in] adRes Pointer to residual vector of AD datatypes with parameter sensitivities * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void GeneralRateModel::consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, +template +void GeneralRateModel::consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1248,7 +1258,8 @@ void GeneralRateModel::consistentInitialSensitivity(const SimulationTime& simTim * @param [in] adRes Pointer to residual vector of AD datatypes with parameter sensitivities * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void GeneralRateModel::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, +template +void GeneralRateModel::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) { if (isSectionDependent(_parDiffusionMode) || isSectionDependent(_parSurfDiffusionMode)) @@ -1305,7 +1316,8 @@ void GeneralRateModel::leanConsistentInitialSensitivity(const SimulationTime& si * on exit the solution @f$ j_f. @f$ * @param [in] idxr Indexer */ -void GeneralRateModel::solveForFluxes(double* const vecState, const Indexer& idxr) const +template +void GeneralRateModel::solveForFluxes(double* const vecState, const Indexer& idxr) const { // We have j_f - k_f * (c - c_p) == 0 // Thus, jacFC contains -k_f and jacFP +k_f. diff --git a/src/libcadet/model/GeneralRateModel-LinearSolver.cpp b/src/libcadet/model/GeneralRateModel-LinearSolver.cpp index b22fa9848..4bb75df09 100644 --- a/src/libcadet/model/GeneralRateModel-LinearSolver.cpp +++ b/src/libcadet/model/GeneralRateModel-LinearSolver.cpp @@ -109,7 +109,8 @@ namespace model * @param [in] simState State of the simulation (state vector and its time derivatives) at which the Jacobian is evaluated * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error */ -int GeneralRateModel::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, +template +int GeneralRateModel::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, const ConstSimulationState& simState) { BENCH_SCOPE(_timerLinearSolve); @@ -375,7 +376,8 @@ int GeneralRateModel::linearSolve(double t, double alpha, double outerTol, doubl * @param [out] z Result of the matrix-vector multiplication * @return @c 0 if successful, any other value in case of failure */ -int GeneralRateModel::schurComplementMatrixVector(double const* x, double* z) const +template +int GeneralRateModel::schurComplementMatrixVector(double const* x, double* z) const { BENCH_SCOPE(_timerMatVec); @@ -484,7 +486,8 @@ int GeneralRateModel::schurComplementMatrixVector(double const* x, double* z) co * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) * @param [in] idxr Indexer */ -void GeneralRateModel::assembleDiscretizedJacobianParticleBlock(unsigned int parType, unsigned int pblk, double alpha, const Indexer& idxr) +template +void GeneralRateModel::assembleDiscretizedJacobianParticleBlock(unsigned int parType, unsigned int pblk, double alpha, const Indexer& idxr) { linalg::FactorizableBandMatrix& fbm = _jacPdisc[_disc.nCol * parType + pblk]; const linalg::BandMatrix& bm = _jacP[_disc.nCol * parType + pblk]; @@ -511,7 +514,8 @@ void GeneralRateModel::assembleDiscretizedJacobianParticleBlock(unsigned int par * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) * @param [in] parType Index of the particle type */ -void GeneralRateModel::addTimeDerivativeToJacobianParticleShell(linalg::FactorizableBandMatrix::RowIterator& jac, const Indexer& idxr, double alpha, unsigned int parType) +template +void GeneralRateModel::addTimeDerivativeToJacobianParticleShell(linalg::FactorizableBandMatrix::RowIterator& jac, const Indexer& idxr, double alpha, unsigned int parType) { parts::cell::addTimeDerivativeToJacobianParticleShell(jac, alpha, static_cast(_parPorosity[parType]), _disc.nComp, _disc.nBound + _disc.nComp * parType, _poreAccessFactor.data() + _disc.nComp * parType, _disc.strideBound[parType], _disc.boundOffset + _disc.nComp * parType, _binding[parType]->reactionQuasiStationarity()); diff --git a/src/libcadet/model/GeneralRateModel.cpp b/src/libcadet/model/GeneralRateModel.cpp index 42a3cbbc3..5154f6b37 100644 --- a/src/libcadet/model/GeneralRateModel.cpp +++ b/src/libcadet/model/GeneralRateModel.cpp @@ -57,14 +57,16 @@ constexpr double SurfVolRatioSphere = 3.0; constexpr double SurfVolRatioCylinder = 2.0; constexpr double SurfVolRatioSlab = 1.0; +template int schurComplementMultiplierGRM(void* userData, double const* x, double* z) { - GeneralRateModel* const grm = static_cast(userData); + GeneralRateModel* const grm = static_cast*>(userData); return grm->schurComplementMatrixVector(x, z); } -GeneralRateModel::GeneralRateModel(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), +template +GeneralRateModel::GeneralRateModel(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), _hasSurfaceDiffusion(0, false), _dynReactionBulk(nullptr), _jacP(nullptr), _jacPdisc(nullptr), _jacPF(nullptr), _jacFP(nullptr), _jacInlet(), _hasParDepSurfDiffusion(false), _analyticJac(true), _jacobianAdDirs(0), _factorizeJacobian(false), _tempState(nullptr), @@ -72,7 +74,8 @@ GeneralRateModel::GeneralRateModel(UnitOpIdx unitOpIdx) : UnitOperationBase(unit { } -GeneralRateModel::~GeneralRateModel() CADET_NOEXCEPT +template +GeneralRateModel::~GeneralRateModel() CADET_NOEXCEPT { delete[] _tempState; @@ -95,7 +98,8 @@ GeneralRateModel::~GeneralRateModel() CADET_NOEXCEPT delete[] _disc.nBoundBeforeType; } -unsigned int GeneralRateModel::numDofs() const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::numDofs() const CADET_NOEXCEPT { // Column bulk DOFs: nCol * nComp // Particle DOFs: nCol * nParType particles each having nComp (liquid phase) + sum boundStates (solid phase) DOFs @@ -105,7 +109,8 @@ unsigned int GeneralRateModel::numDofs() const CADET_NOEXCEPT return _disc.nCol * (_disc.nComp * (1 + _disc.nParType)) + _disc.parTypeOffset[_disc.nParType] + _disc.nComp; } -unsigned int GeneralRateModel::numPureDofs() const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::numPureDofs() const CADET_NOEXCEPT { // Column bulk DOFs: nCol * nComp // Particle DOFs: nCol particles each having nComp (liquid phase) + sum boundStates (solid phase) DOFs @@ -115,7 +120,8 @@ unsigned int GeneralRateModel::numPureDofs() const CADET_NOEXCEPT } -bool GeneralRateModel::usesAD() const CADET_NOEXCEPT +template +bool GeneralRateModel::usesAD() const CADET_NOEXCEPT { #ifdef CADET_CHECK_ANALYTIC_JACOBIAN // We always need AD if we want to check the analytical Jacobian @@ -126,7 +132,8 @@ bool GeneralRateModel::usesAD() const CADET_NOEXCEPT #endif } -void GeneralRateModel::clearParDepSurfDiffusion() +template +void GeneralRateModel::clearParDepSurfDiffusion() { if (_singleParDepSurfDiffusion) { @@ -135,14 +142,15 @@ void GeneralRateModel::clearParDepSurfDiffusion() } else { - for (IParameterDependence* pd : _parDepSurfDiffusion) + for (IParameterStateDependence* pd : _parDepSurfDiffusion) delete pd; } _parDepSurfDiffusion.clear(); } -bool GeneralRateModel::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +template +bool GeneralRateModel::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // ==== Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -326,7 +334,7 @@ bool GeneralRateModel::configureModelDiscretization(IParameterProvider& paramPro // Initialize and configure GMRES for solving the Schur-complement _gmres.initialize(_disc.nCol * _disc.nComp * _disc.nParType, paramProvider.getInt("MAX_KRYLOV"), linalg::toOrthogonalization(paramProvider.getInt("GS_TYPE")), paramProvider.getInt("MAX_RESTARTS")); - _gmres.matrixVectorMultiplier(&schurComplementMultiplierGRM, this); + _gmres.matrixVectorMultiplier(&schurComplementMultiplierGRM, this); _schurSafety = paramProvider.getDouble("SCHUR_SAFETY"); // Allocate space for initial conditions @@ -362,43 +370,43 @@ bool GeneralRateModel::configureModelDiscretization(IParameterProvider& paramPro { _hasParDepSurfDiffusion = false; _singleParDepSurfDiffusion = true; - _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); + _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); } else { - IParameterDependence* const pd = helper.createParameterDependence(psdDepNames[0]); + IParameterStateDependence* const pd = helper.createParameterStateDependence(psdDepNames[0]); if (!pd) throw InvalidParameterException("Unknown parameter dependence " + psdDepNames[0]); - _parDepSurfDiffusion = std::vector(_disc.nParType, pd); + _parDepSurfDiffusion = std::vector(_disc.nParType, pd); parSurfDiffDepConfSuccess = pd->configureModelDiscretization(paramProvider, _disc.nComp, _disc.nBound, _disc.boundOffset); _hasParDepSurfDiffusion = true; } } else { - _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); + _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); for (unsigned int i = 0; i < _disc.nParType; ++i) { if ((psdDepNames[0] == "") || (psdDepNames[0] == "NONE") || (psdDepNames[0] == "DUMMY")) continue; - _parDepSurfDiffusion[i] = helper.createParameterDependence(psdDepNames[i]); + _parDepSurfDiffusion[i] = helper.createParameterStateDependence(psdDepNames[i]); if (!_parDepSurfDiffusion[i]) throw InvalidParameterException("Unknown parameter dependence " + psdDepNames[i]); parSurfDiffDepConfSuccess = _parDepSurfDiffusion[i]->configureModelDiscretization(paramProvider, _disc.nComp, _disc.nBound + i * _disc.nComp, _disc.boundOffset + i * _disc.nComp) && parSurfDiffDepConfSuccess; } - _hasParDepSurfDiffusion = std::any_of(_parDepSurfDiffusion.cbegin(), _parDepSurfDiffusion.cend(), [](IParameterDependence const* pd) -> bool { return pd; }); + _hasParDepSurfDiffusion = std::any_of(_parDepSurfDiffusion.cbegin(), _parDepSurfDiffusion.cend(), [](IParameterStateDependence const* pd) -> bool { return pd; }); } } else { _hasParDepSurfDiffusion = false; _singleParDepSurfDiffusion = true; - _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); + _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); } if (optimizeParticleJacobianBandwidth) @@ -437,7 +445,7 @@ bool GeneralRateModel::configureModelDiscretization(IParameterProvider& paramPro _hasSurfaceDiffusion = std::vector(_disc.nParType, true); } - const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nCol); + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nCol); // ==== Construct and configure binding model clearBindingModels(); @@ -636,7 +644,8 @@ bool GeneralRateModel::configureModelDiscretization(IParameterProvider& paramPro return transportSuccess && parSurfDiffDepConfSuccess && bindingConfSuccess && reactionConfSuccess; } -bool GeneralRateModel::configure(IParameterProvider& paramProvider) +template +bool GeneralRateModel::configure(IParameterProvider& paramProvider) { _parameters.clear(); @@ -860,7 +869,8 @@ bool GeneralRateModel::configure(IParameterProvider& paramProvider) return transportSuccess && parSurfDiffDepConfSuccess && bindingConfSuccess && dynReactionConfSuccess; } -unsigned int GeneralRateModel::threadLocalMemorySize() const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::threadLocalMemorySize() const CADET_NOEXCEPT { LinearMemorySizer lms; @@ -903,7 +913,8 @@ unsigned int GeneralRateModel::threadLocalMemorySize() const CADET_NOEXCEPT return lms.bufferSize(); } -unsigned int GeneralRateModel::numAdDirsForJacobian() const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::numAdDirsForJacobian() const CADET_NOEXCEPT { // We need as many directions as the highest bandwidth of the diagonal blocks: // The bandwidth of the column block depends on the size of the WENO stencil, whereas @@ -919,7 +930,8 @@ unsigned int GeneralRateModel::numAdDirsForJacobian() const CADET_NOEXCEPT return std::max(_convDispOp.requiredADdirs(), maxStride); } -void GeneralRateModel::useAnalyticJacobian(const bool analyticJac) +template +void GeneralRateModel::useAnalyticJacobian(const bool analyticJac) { #ifndef CADET_CHECK_ANALYTIC_JACOBIAN _analyticJac = analyticJac; @@ -934,7 +946,8 @@ void GeneralRateModel::useAnalyticJacobian(const bool analyticJac) #endif } -void GeneralRateModel::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) +template +void GeneralRateModel::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) { // Setup flux Jacobian blocks at the beginning of the simulation or in case of // section dependent film or particle diffusion coefficients @@ -943,22 +956,21 @@ void GeneralRateModel::notifyDiscontinuousSectionTransition(double t, unsigned i Indexer idxr(_disc); - // ConvectionDispersionOperator tells us whether flow direction has changed + // AxialConvectionDispersionOperator tells us whether flow direction has changed if (!_convDispOp.notifyDiscontinuousSectionTransition(t, secIdx, adJac)) return; // Setup the matrix connecting inlet DOFs to first column cells _jacInlet.clear(); - const double h = static_cast(_convDispOp.columnLength()) / static_cast(_disc.nCol); - const double u = static_cast(_convDispOp.currentVelocity()); + const double v = _convDispOp.inletJacobianFactor(); - if (u >= 0.0) + if (_convDispOp.forwardFlow()) { // Forwards flow // Place entries for inlet DOF to first column cell conversion for (unsigned int comp = 0; comp < _disc.nComp; ++comp) - _jacInlet.addElement(comp * idxr.strideColComp(), comp, -u / h); + _jacInlet.addElement(comp * idxr.strideColComp(), comp, -v); } else { @@ -967,30 +979,34 @@ void GeneralRateModel::notifyDiscontinuousSectionTransition(double t, unsigned i // Place entries for inlet DOF to last column cell conversion const unsigned int offset = (_disc.nCol - 1) * idxr.strideColCell(); for (unsigned int comp = 0; comp < _disc.nComp; ++comp) - _jacInlet.addElement(offset + comp * idxr.strideColComp(), comp, u / h); + _jacInlet.addElement(offset + comp * idxr.strideColComp(), comp, v); } } -void GeneralRateModel::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT +template +void GeneralRateModel::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT { _convDispOp.setFlowRates(in[0], out[0], _colPorosity); } -void GeneralRateModel::reportSolution(ISolutionRecorder& recorder, double const* const solution) const +template +void GeneralRateModel::reportSolution(ISolutionRecorder& recorder, double const* const solution) const { Exporter expr(_disc, *this, solution); recorder.beginUnitOperation(_unitOpIdx, *this, expr); recorder.endUnitOperation(); } -void GeneralRateModel::reportSolutionStructure(ISolutionRecorder& recorder) const +template +void GeneralRateModel::reportSolutionStructure(ISolutionRecorder& recorder) const { Exporter expr(_disc, *this, nullptr); recorder.unitOperationStructure(_unitOpIdx, *this, expr); } -unsigned int GeneralRateModel::requiredADdirs() const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::requiredADdirs() const CADET_NOEXCEPT { #ifndef CADET_CHECK_ANALYTIC_JACOBIAN return _jacobianAdDirs; @@ -1000,7 +1016,8 @@ unsigned int GeneralRateModel::requiredADdirs() const CADET_NOEXCEPT #endif } -void GeneralRateModel::prepareADvectors(const AdJacobianParams& adJac) const +template +void GeneralRateModel::prepareADvectors(const AdJacobianParams& adJac) const { // Early out if AD is disabled if (!adJac.adY) @@ -1029,7 +1046,8 @@ void GeneralRateModel::prepareADvectors(const AdJacobianParams& adJac) const * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void GeneralRateModel::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) +template +void GeneralRateModel::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) { Indexer idxr(_disc); @@ -1055,7 +1073,8 @@ void GeneralRateModel::extractJacobianFromAD(active const* const adRes, unsigned * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void GeneralRateModel::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const +template +void GeneralRateModel::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const { Indexer idxr(_disc); @@ -1080,7 +1099,8 @@ void GeneralRateModel::checkAnalyticJacobianAgainstAd(active const* const adRes, #endif -int GeneralRateModel::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) +template +int GeneralRateModel::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidual); @@ -1088,7 +1108,8 @@ int GeneralRateModel::residual(const SimulationTime& simTime, const ConstSimulat return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); } -int GeneralRateModel::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +template +int GeneralRateModel::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidual); @@ -1096,7 +1117,8 @@ int GeneralRateModel::residualWithJacobian(const SimulationTime& simTime, const return residual(simTime, simState, res, adJac, threadLocalMem, true, false); } -int GeneralRateModel::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, +template +int GeneralRateModel::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem, bool updateJacobian, bool paramSensitivity) { if (updateJacobian) @@ -1202,8 +1224,9 @@ int GeneralRateModel::residual(const SimulationTime& simTime, const ConstSimulat } } +template template -int GeneralRateModel::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) +int GeneralRateModel::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) { BENCH_START(_timerResidualPar); @@ -1236,10 +1259,11 @@ int GeneralRateModel::residualImpl(double t, unsigned int secIdx, StateType cons return 0; } +template template -int GeneralRateModel::residualBulk(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) +int GeneralRateModel::residualBulk(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) { - _convDispOp.residual(t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); + _convDispOp.residual(*this, t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); if (!_dynReactionBulk || (_dynReactionBulk->numReactionsLiquid() == 0)) return 0; @@ -1264,8 +1288,9 @@ int GeneralRateModel::residualBulk(double t, unsigned int secIdx, StateType cons return 0; } +template template -int GeneralRateModel::residualParticle(double t, unsigned int parType, unsigned int colCell, unsigned int secIdx, StateType const* yBase, +int GeneralRateModel::residualParticle(double t, unsigned int parType, unsigned int colCell, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) { Indexer idxr(_disc); @@ -1285,7 +1310,7 @@ int GeneralRateModel::residualParticle(double t, unsigned int parType, unsigned active const* const parSurfDiff = getSectionDependentSlice(_parSurfDiffusion, _disc.strideBound[_disc.nParType], secIdx) + _disc.nBoundBeforeType[parType]; // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = (0.5 + static_cast(colCell)) / static_cast(_disc.nCol); + const double z = _convDispOp.relativeCoordinate(colCell); // Reset Jacobian if (wantJac) @@ -1828,8 +1853,9 @@ int GeneralRateModel::residualParticle(double t, unsigned int parType, unsigned return 0; } +template template -int GeneralRateModel::residualFlux(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase) +int GeneralRateModel::residualFlux(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase) { Indexer idxr(_disc); @@ -1974,7 +2000,8 @@ int GeneralRateModel::residualFlux(double t, unsigned int secIdx, StateType cons return 0; } -parts::cell::CellParameters GeneralRateModel::makeCellResidualParams(unsigned int parType, int const* qsReaction) const +template +parts::cell::CellParameters GeneralRateModel::makeCellResidualParams(unsigned int parType, int const* qsReaction) const { return parts::cell::CellParameters { @@ -1998,7 +2025,8 @@ parts::cell::CellParameters GeneralRateModel::makeCellResidualParams(unsigned in * @param [in] secIdx Index of the current section * @param [in] vecStateY Current state vector */ -void GeneralRateModel::assembleOffdiagJac(double t, unsigned int secIdx, double const* vecStateY) +template +void GeneralRateModel::assembleOffdiagJac(double t, unsigned int secIdx, double const* vecStateY) { // Clear matrices for new assembly _jacCF.clear(); @@ -2177,7 +2205,8 @@ void GeneralRateModel::assembleOffdiagJac(double t, unsigned int secIdx, double * @param [in] secIdx Index of the current section * @param [in] vecStateY Current state vector */ -void GeneralRateModel::assembleOffdiagJacFluxParticle(double t, unsigned int secIdx, double const* vecStateY) +template +void GeneralRateModel::assembleOffdiagJacFluxParticle(double t, unsigned int secIdx, double const* vecStateY) { for (unsigned int pblk = 0; pblk < _disc.nCol * _disc.nParType; ++pblk) _jacFP[pblk].clear(); @@ -2303,7 +2332,8 @@ void GeneralRateModel::assembleOffdiagJacFluxParticle(double t, unsigned int sec _discParFlux.destroy(); } -int GeneralRateModel::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +template +int GeneralRateModel::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidualSens); @@ -2312,7 +2342,8 @@ int GeneralRateModel::residualSensFwdWithJacobian(const SimulationTime& simTime, return residual(simTime, simState, nullptr, adJac, threadLocalMem, true, true); } -int GeneralRateModel::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) +template +int GeneralRateModel::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidualSens); @@ -2320,7 +2351,8 @@ int GeneralRateModel::residualSensFwdAdOnly(const SimulationTime& simTime, const return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, adRes, threadLocalMem); } -int GeneralRateModel::residualSensFwdCombine(const SimulationTime& simTime, const ConstSimulationState& simState, +template +int GeneralRateModel::residualSensFwdCombine(const SimulationTime& simTime, const ConstSimulationState& simState, const std::vector& yS, const std::vector& ySdot, const std::vector& resS, active const* adRes, double* const tmp1, double* const tmp2, double* const tmp3) { @@ -2371,7 +2403,8 @@ int GeneralRateModel::residualSensFwdCombine(const SimulationTime& simTime, cons * @param [in] beta Factor @f$ \beta @f$ in front of @f$ z @f$ * @param [in,out] ret Vector @f$ z @f$ which stores the result of the operation */ -void GeneralRateModel::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) +template +void GeneralRateModel::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) { Indexer idxr(_disc); @@ -2435,7 +2468,8 @@ void GeneralRateModel::multiplyWithJacobian(const SimulationTime& simTime, const * @param [in] sDot Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ * @param [out] ret Vector @f$ z @f$ which stores the result of the operation */ -void GeneralRateModel::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) +template +void GeneralRateModel::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) { Indexer idxr(_disc); @@ -2481,7 +2515,8 @@ void GeneralRateModel::multiplyWithDerivativeJacobian(const SimulationTime& simT std::fill_n(ret, _disc.nComp, 0.0); } -void GeneralRateModel::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) +template +void GeneralRateModel::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) { for (IBindingModel* bm : _binding) { @@ -2490,10 +2525,11 @@ void GeneralRateModel::setExternalFunctions(IExternalFunction** extFuns, unsigne } } -unsigned int GeneralRateModel::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT { // Inlets are duplicated so need to be accounted for - if (static_cast(_convDispOp.currentVelocity()) >= 0.0) + if (_convDispOp.forwardFlow()) // Forward Flow: outlet is last cell return _disc.nComp + (_disc.nCol - 1) * _disc.nComp; else @@ -2501,23 +2537,27 @@ unsigned int GeneralRateModel::localOutletComponentIndex(unsigned int port) cons return _disc.nComp; } -unsigned int GeneralRateModel::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT { // Always 0 due to dedicated inlet DOFs return 0; } -unsigned int GeneralRateModel::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT { return 1; } -unsigned int GeneralRateModel::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT +template +unsigned int GeneralRateModel::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT { return 1; } -void GeneralRateModel::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) +template +void GeneralRateModel::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) { // @todo Write this function } @@ -2525,7 +2565,8 @@ void GeneralRateModel::expandErrorTol(double const* errorSpec, unsigned int erro /** * @brief Computes equidistant radial nodes in the beads */ -void GeneralRateModel::setEquidistantRadialDisc(unsigned int parType) +template +void GeneralRateModel::setEquidistantRadialDisc(unsigned int parType) { active* const ptrCenterRadius = _parCenterRadius.data() + _disc.nParCellsBeforeType[parType]; active* const ptrOuterSurfAreaPerVolume = _parOuterSurfAreaPerVolume.data() + _disc.nParCellsBeforeType[parType]; @@ -2588,7 +2629,8 @@ void GeneralRateModel::setEquidistantRadialDisc(unsigned int parType) /** * @brief Computes the radial nodes in the beads in such a way that all shells have the same volume */ -void GeneralRateModel::setEquivolumeRadialDisc(unsigned int parType) +template +void GeneralRateModel::setEquivolumeRadialDisc(unsigned int parType) { active* const ptrCellSize = _parCellSize.data() + _disc.nParCellsBeforeType[parType]; active* const ptrCenterRadius = _parCenterRadius.data() + _disc.nParCellsBeforeType[parType]; @@ -2670,7 +2712,8 @@ void GeneralRateModel::setEquivolumeRadialDisc(unsigned int parType) * @brief Computes all helper quantities for radial bead discretization from given radial cell boundaries * @details Calculates surface areas per volume for every shell and the radial shell centers. */ -void GeneralRateModel::setUserdefinedRadialDisc(unsigned int parType) +template +void GeneralRateModel::setUserdefinedRadialDisc(unsigned int parType) { active* const ptrCellSize = _parCellSize.data() + _disc.nParCellsBeforeType[parType]; active* const ptrCenterRadius = _parCenterRadius.data() + _disc.nParCellsBeforeType[parType]; @@ -2736,7 +2779,8 @@ void GeneralRateModel::setUserdefinedRadialDisc(unsigned int parType) } } -void GeneralRateModel::updateRadialDisc() +template +void GeneralRateModel::updateRadialDisc() { for (unsigned int i = 0; i < _disc.nParType; ++i) { @@ -2749,7 +2793,8 @@ void GeneralRateModel::updateRadialDisc() } } -bool GeneralRateModel::setParameter(const ParameterId& pId, double value) +template +bool GeneralRateModel::setParameter(const ParameterId& pId, double value) { if (pId.unitOperation == _unitOpIdx) { @@ -2804,7 +2849,8 @@ bool GeneralRateModel::setParameter(const ParameterId& pId, double value) return result; } -bool GeneralRateModel::setParameter(const ParameterId& pId, int value) +template +bool GeneralRateModel::setParameter(const ParameterId& pId, int value) { if ((pId.unitOperation != _unitOpIdx) && (pId.unitOperation != UnitOpIndep)) return false; @@ -2815,7 +2861,8 @@ bool GeneralRateModel::setParameter(const ParameterId& pId, int value) return UnitOperationBase::setParameter(pId, value); } -bool GeneralRateModel::setParameter(const ParameterId& pId, bool value) +template +bool GeneralRateModel::setParameter(const ParameterId& pId, bool value) { if ((pId.unitOperation != _unitOpIdx) && (pId.unitOperation != UnitOpIndep)) return false; @@ -2826,7 +2873,8 @@ bool GeneralRateModel::setParameter(const ParameterId& pId, bool value) return UnitOperationBase::setParameter(pId, value); } -void GeneralRateModel::setSensitiveParameterValue(const ParameterId& pId, double value) +template +void GeneralRateModel::setSensitiveParameterValue(const ParameterId& pId, double value) { if (pId.unitOperation == _unitOpIdx) { @@ -2879,7 +2927,8 @@ void GeneralRateModel::setSensitiveParameterValue(const ParameterId& pId, double updateRadialDisc(); } -bool GeneralRateModel::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) +template +bool GeneralRateModel::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) { if (pId.unitOperation == _unitOpIdx) { @@ -2976,7 +3025,8 @@ bool GeneralRateModel::setSensitiveParameter(const ParameterId& pId, unsigned in return result; } -std::unordered_map GeneralRateModel::getAllParameterValues() const +template +std::unordered_map GeneralRateModel::getAllParameterValues() const { std::unordered_map data = UnitOperationBase::getAllParameterValues(); model::getAllParameterValues(data, _parDepSurfDiffusion, _singleParDepSurfDiffusion); @@ -2984,7 +3034,8 @@ std::unordered_map GeneralRateModel::getAllParameterValues( return data; } -double GeneralRateModel::getParameterDouble(const ParameterId& pId) const +template +double GeneralRateModel::getParameterDouble(const ParameterId& pId) const { double val = 0.0; if (model::getParameterDouble(pId, _parDepSurfDiffusion, _singleParDepSurfDiffusion, val)) @@ -2994,7 +3045,8 @@ double GeneralRateModel::getParameterDouble(const ParameterId& pId) const return UnitOperationBase::getParameterDouble(pId); } -bool GeneralRateModel::hasParameter(const ParameterId& pId) const +template +bool GeneralRateModel::hasParameter(const ParameterId& pId) const { if (model::hasParameter(pId, _parDepSurfDiffusion, _singleParDepSurfDiffusion)) return true; @@ -3003,14 +3055,16 @@ bool GeneralRateModel::hasParameter(const ParameterId& pId) const } -int GeneralRateModel::Exporter::writeMobilePhase(double* buffer) const +template +int GeneralRateModel::Exporter::writeMobilePhase(double* buffer) const { const int blockSize = numMobilePhaseDofs(); std::copy_n(_idx.c(_data), blockSize, buffer); return blockSize; } -int GeneralRateModel::Exporter::writeSolidPhase(double* buffer) const +template +int GeneralRateModel::Exporter::writeSolidPhase(double* buffer) const { int numWritten = 0; for (unsigned int i = 0; i < _disc.nParType; ++i) @@ -3022,7 +3076,8 @@ int GeneralRateModel::Exporter::writeSolidPhase(double* buffer) const return numWritten; } -int GeneralRateModel::Exporter::writeParticleMobilePhase(double* buffer) const +template +int GeneralRateModel::Exporter::writeParticleMobilePhase(double* buffer) const { int numWritten = 0; for (unsigned int i = 0; i < _disc.nParType; ++i) @@ -3034,7 +3089,8 @@ int GeneralRateModel::Exporter::writeParticleMobilePhase(double* buffer) const return numWritten; } -int GeneralRateModel::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const +template +int GeneralRateModel::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const { cadet_assert(parType < _disc.nParType); @@ -3052,7 +3108,8 @@ int GeneralRateModel::Exporter::writeSolidPhase(unsigned int parType, double* bu return _disc.nCol * _disc.nParCell[parType] * _disc.strideBound[parType]; } -int GeneralRateModel::Exporter::writeParticleMobilePhase(unsigned int parType, double* buffer) const +template +int GeneralRateModel::Exporter::writeParticleMobilePhase(unsigned int parType, double* buffer) const { cadet_assert(parType < _disc.nParType); @@ -3070,38 +3127,43 @@ int GeneralRateModel::Exporter::writeParticleMobilePhase(unsigned int parType, d return _disc.nCol * _disc.nParCell[parType] * _disc.nComp; } -int GeneralRateModel::Exporter::writeParticleFlux(double* buffer) const +template +int GeneralRateModel::Exporter::writeParticleFlux(double* buffer) const { const int blockSize = numParticleFluxDofs(); std::copy_n(_idx.jf(_data), blockSize, buffer); return blockSize; } -int GeneralRateModel::Exporter::writeParticleFlux(unsigned int parType, double* buffer) const +template +int GeneralRateModel::Exporter::writeParticleFlux(unsigned int parType, double* buffer) const { const unsigned int blockSize = _disc.nComp * _disc.nCol; std::copy_n(_idx.jf(_data) + blockSize * parType, blockSize, buffer); return blockSize; } -int GeneralRateModel::Exporter::writeInlet(unsigned int port, double* buffer) const +template +int GeneralRateModel::Exporter::writeInlet(unsigned int port, double* buffer) const { cadet_assert(port == 0); std::copy_n(_data, _disc.nComp, buffer); return _disc.nComp; } -int GeneralRateModel::Exporter::writeInlet(double* buffer) const +template +int GeneralRateModel::Exporter::writeInlet(double* buffer) const { std::copy_n(_data, _disc.nComp, buffer); return _disc.nComp; } -int GeneralRateModel::Exporter::writeOutlet(unsigned int port, double* buffer) const +template +int GeneralRateModel::Exporter::writeOutlet(unsigned int port, double* buffer) const { cadet_assert(port == 0); - if (_model._convDispOp.currentVelocity() >= 0) + if (_model._convDispOp.forwardFlow()) std::copy_n(&_idx.c(_data, _disc.nCol - 1, 0), _disc.nComp, buffer); else std::copy_n(&_idx.c(_data, 0, 0), _disc.nComp, buffer); @@ -3109,9 +3171,10 @@ int GeneralRateModel::Exporter::writeOutlet(unsigned int port, double* buffer) c return _disc.nComp; } -int GeneralRateModel::Exporter::writeOutlet(double* buffer) const +template +int GeneralRateModel::Exporter::writeOutlet(double* buffer) const { - if (_model._convDispOp.currentVelocity() >= 0) + if (_model._convDispOp.forwardFlow()) std::copy_n(&_idx.c(_data, _disc.nCol - 1, 0), _disc.nComp, buffer); else std::copy_n(&_idx.c(_data, 0, 0), _disc.nComp, buffer); @@ -3119,11 +3182,33 @@ int GeneralRateModel::Exporter::writeOutlet(double* buffer) const return _disc.nComp; } +} // namespace model + +} // namespace cadet + +#include "model/GeneralRateModel-InitialConditions.cpp" +#include "model/GeneralRateModel-LinearSolver.cpp" + +namespace cadet +{ + +namespace model +{ + +// Template instantiations +template class GeneralRateModel; +template class GeneralRateModel; void registerGeneralRateModel(std::unordered_map>& models) { - models[GeneralRateModel::identifier()] = [](UnitOpIdx uoId) { return new GeneralRateModel(uoId); }; - models["GRM"] = [](UnitOpIdx uoId) { return new GeneralRateModel(uoId); }; + typedef GeneralRateModel AxialGRM; + typedef GeneralRateModel RadialGRM; + + models[AxialGRM::identifier()] = [](UnitOpIdx uoId) { return new AxialGRM(uoId); }; + models["GRM"] = [](UnitOpIdx uoId) { return new AxialGRM(uoId); }; + + models[RadialGRM::identifier()] = [](UnitOpIdx uoId) { return new RadialGRM(uoId); }; + models["RGRM"] = [](UnitOpIdx uoId) { return new RadialGRM(uoId); }; } } // namespace model diff --git a/src/libcadet/model/GeneralRateModel.hpp b/src/libcadet/model/GeneralRateModel.hpp index fb8d84d8e..f0dee376f 100644 --- a/src/libcadet/model/GeneralRateModel.hpp +++ b/src/libcadet/model/GeneralRateModel.hpp @@ -35,6 +35,24 @@ #include "Benchmark.hpp" +namespace +{ + template + struct GeneralRateModelName { }; + + template <> + struct GeneralRateModelName + { + static const char* identifier() CADET_NOEXCEPT { return "GENERAL_RATE_MODEL"; } + }; + + template <> + struct GeneralRateModelName + { + static const char* identifier() CADET_NOEXCEPT { return "RADIAL_GENERAL_RATE_MODEL"; } + }; +} + namespace cadet { @@ -50,7 +68,7 @@ namespace parts } class IDynamicReactionModel; -class IParameterDependence; +class IParameterStateDependence; /** * @brief General rate model of liquid column chromatography @@ -73,6 +91,7 @@ u c_{\text{in},i}(t) &= u c_i(t,0) - D_{\text{ax},i} \frac{\partial c_i}{\partia \end{align} @f] * Methods are described in @cite VonLieres2010a (WENO, linear solver), @cite Puttmann2013 @cite Puttmann2016 (forward sensitivities, AD, band compression) */ +template class GeneralRateModel : public UnitOperationBase { public: @@ -92,10 +111,10 @@ class GeneralRateModel : public UnitOperationBase virtual unsigned int numOutletPorts() const CADET_NOEXCEPT { return 1; } virtual bool canAccumulate() const CADET_NOEXCEPT { return false; } - static const char* identifier() { return "GENERAL_RATE_MODEL"; } + static const char* identifier() { return GeneralRateModelName::identifier(); } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); @@ -293,7 +312,7 @@ class GeneralRateModel : public UnitOperationBase std::vector _hasSurfaceDiffusion; //!< Determines whether surface diffusion is present in each particle type // IExternalFunction* _extFun; //!< External function (owned by library user) - parts::ConvectionDispersionOperator _convDispOp; //!< Convection dispersion operator for interstitial volume transport + ConvDispOperator _convDispOp; //!< Convection dispersion operator for interstitial volume transport IDynamicReactionModel* _dynReactionBulk; //!< Dynamic reactions in the bulk volume linalg::BandMatrix* _jacP; //!< Particle jacobian diagonal blocks (all of them) @@ -327,7 +346,7 @@ class GeneralRateModel : public UnitOperationBase MultiplexMode _parSurfDiffusionMode; std::vector _poreAccessFactor; //!< Pore accessibility factor \f$ F_{\text{acc}} \f$ MultiplexMode _poreAccessFactorMode; - std::vector _parDepSurfDiffusion; //!< Parameter dependencies for particle surface diffusion + std::vector _parDepSurfDiffusion; //!< Parameter dependencies for particle surface diffusion bool _singleParDepSurfDiffusion; //!< Determines whether a single parameter dependence for particle surface diffusion is used bool _hasParDepSurfDiffusion; //!< Determines whether particle surface diffusion parameter dependencies are present @@ -368,6 +387,7 @@ class GeneralRateModel : public UnitOperationBase BENCH_TIMER(_timerGmres) // Wrapper for calling the corresponding function in GeneralRateModel class + template friend int schurComplementMultiplierGRM(void* userData, double const* x, double* z); class Indexer @@ -476,9 +496,8 @@ class GeneralRateModel : public UnitOperationBase virtual int writePrimaryCoordinates(double* coords) const { - const double h = static_cast(_model._convDispOp.columnLength()) / static_cast(_disc.nCol); for (unsigned int i = 0; i < _disc.nCol; ++i) - coords[i] = (i + 0.5) * h; + coords[i] = _model._convDispOp.cellCenter(i); return _disc.nCol; } virtual int writeSecondaryCoordinates(double* coords) const { return 0; } @@ -498,6 +517,9 @@ class GeneralRateModel : public UnitOperationBase }; }; +extern template class GeneralRateModel; +extern template class GeneralRateModel; + } // namespace model } // namespace cadet diff --git a/src/libcadet/model/GeneralRateModel2D.cpp b/src/libcadet/model/GeneralRateModel2D.cpp index 00b4b1a23..7bd46dccf 100644 --- a/src/libcadet/model/GeneralRateModel2D.cpp +++ b/src/libcadet/model/GeneralRateModel2D.cpp @@ -390,7 +390,7 @@ bool GeneralRateModel2D::usesAD() const CADET_NOEXCEPT #endif } -bool GeneralRateModel2D::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool GeneralRateModel2D::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // ==== Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -723,7 +723,7 @@ bool GeneralRateModel2D::configureModelDiscretization(IParameterProvider& paramP } } - const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nCol, _disc.nRad, _dynReactionBulk); + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nCol, _disc.nRad, _dynReactionBulk); // Setup the memory for tempState based on state vector _tempState = new double[numDofs()]; @@ -1336,7 +1336,7 @@ int GeneralRateModel2D::residualImpl(double t, unsigned int secIdx, StateType co template int GeneralRateModel2D::residualBulk(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) { - _convDispOp.residual(t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); + _convDispOp.residual(*this, t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); if (!_dynReactionBulk || (_dynReactionBulk->numReactionsLiquid() == 0)) return 0; diff --git a/src/libcadet/model/GeneralRateModel2D.hpp b/src/libcadet/model/GeneralRateModel2D.hpp index d9f018ecf..f06d85891 100644 --- a/src/libcadet/model/GeneralRateModel2D.hpp +++ b/src/libcadet/model/GeneralRateModel2D.hpp @@ -86,7 +86,7 @@ class GeneralRateModel2D : public UnitOperationBase static const char* identifier() { return "GENERAL_RATE_MODEL_2D"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); diff --git a/src/libcadet/model/GeneralRateModelDG.cpp b/src/libcadet/model/GeneralRateModelDG.cpp index b472470dc..cb68fa5bd 100644 --- a/src/libcadet/model/GeneralRateModelDG.cpp +++ b/src/libcadet/model/GeneralRateModelDG.cpp @@ -121,14 +121,14 @@ void GeneralRateModelDG::clearParDepSurfDiffusion() } else { - for (IParameterDependence* pd : _parDepSurfDiffusion) + for (IParameterStateDependence* pd : _parDepSurfDiffusion) delete pd; } _parDepSurfDiffusion.clear(); } -bool GeneralRateModelDG::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool GeneralRateModelDG::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // ==== Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -373,43 +373,43 @@ bool GeneralRateModelDG::configureModelDiscretization(IParameterProvider& paramP { _hasParDepSurfDiffusion = false; _singleParDepSurfDiffusion = true; - _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); + _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); } else { - IParameterDependence* const pd = helper.createParameterDependence(psdDepNames[0]); + IParameterStateDependence* const pd = helper.createParameterStateDependence(psdDepNames[0]); if (!pd) throw InvalidParameterException("Unknown parameter dependence " + psdDepNames[0]); - _parDepSurfDiffusion = std::vector(_disc.nParType, pd); + _parDepSurfDiffusion = std::vector(_disc.nParType, pd); parSurfDiffDepConfSuccess = pd->configureModelDiscretization(paramProvider, _disc.nComp, _disc.nBound, _disc.boundOffset); _hasParDepSurfDiffusion = true; } } else { - _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); + _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); for (unsigned int i = 0; i < _disc.nParType; ++i) { if ((psdDepNames[0] == "") || (psdDepNames[0] == "NONE") || (psdDepNames[0] == "DUMMY")) continue; - _parDepSurfDiffusion[i] = helper.createParameterDependence(psdDepNames[i]); + _parDepSurfDiffusion[i] = helper.createParameterStateDependence(psdDepNames[i]); if (!_parDepSurfDiffusion[i]) throw InvalidParameterException("Unknown parameter dependence " + psdDepNames[i]); parSurfDiffDepConfSuccess = _parDepSurfDiffusion[i]->configureModelDiscretization(paramProvider, _disc.nComp, _disc.nBound + i * _disc.nComp, _disc.boundOffset + i * _disc.nComp) && parSurfDiffDepConfSuccess; } - _hasParDepSurfDiffusion = std::any_of(_parDepSurfDiffusion.cbegin(), _parDepSurfDiffusion.cend(), [](IParameterDependence const* pd) -> bool { return pd; }); + _hasParDepSurfDiffusion = std::any_of(_parDepSurfDiffusion.cbegin(), _parDepSurfDiffusion.cend(), [](IParameterStateDependence const* pd) -> bool { return pd; }); } } else { _hasParDepSurfDiffusion = false; _singleParDepSurfDiffusion = true; - _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); + _parDepSurfDiffusion = std::vector(_disc.nParType, nullptr); } if (optimizeParticleJacobianBandwidth) @@ -448,9 +448,9 @@ bool GeneralRateModelDG::configureModelDiscretization(IParameterProvider& paramP _hasSurfaceDiffusion = std::vector(_disc.nParType, true); } - const bool transportSuccess = _convDispOpB.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nPoints, 0); // strideCell not needed for DG, so just set to zero _disc.dispersion = Eigen::VectorXd::Zero(_disc.nComp); // fill later on with convDispOpB (section and component dependent) + const bool transportSuccess = _convDispOpB.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nPoints, 0); // strideCell not needed for DG, so just set to zero _disc.velocity = static_cast(_convDispOpB.currentVelocity()); // updated later on (section dependent) _disc.curSection = -1; diff --git a/src/libcadet/model/GeneralRateModelDG.hpp b/src/libcadet/model/GeneralRateModelDG.hpp index 3551f2aec..a63ea7c3d 100644 --- a/src/libcadet/model/GeneralRateModelDG.hpp +++ b/src/libcadet/model/GeneralRateModelDG.hpp @@ -55,7 +55,7 @@ namespace parts } class IDynamicReactionModel; -class IParameterDependence; +class IParameterStateDependence; /** * @brief General rate model of liquid column chromatography @@ -100,7 +100,7 @@ class GeneralRateModelDG : public UnitOperationBase static const char* identifier() { return "GENERAL_RATE_MODEL_DG"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); @@ -951,7 +951,7 @@ class GeneralRateModelDG : public UnitOperationBase std::vector _hasSurfaceDiffusion; //!< Determines whether surface diffusion is present in each particle type // IExternalFunction* _extFun; //!< External function (owned by library user) - parts::ConvectionDispersionOperatorBase _convDispOpB; //!< Convection dispersion operator base for interstitial volume transport + parts::AxialConvectionDispersionOperatorBase _convDispOpB; //!< Convection dispersion operator base for interstitial volume transport IDynamicReactionModel* _dynReactionBulk; //!< Dynamic reactions in the bulk volume Eigen::SparseLU> _globalSolver; //!< linear solver @@ -984,7 +984,7 @@ class GeneralRateModelDG : public UnitOperationBase MultiplexMode _parSurfDiffusionMode; std::vector _poreAccessFactor; //!< Pore accessibility factor \f$ F_{\text{acc}} \f$ MultiplexMode _poreAccessFactorMode; - std::vector _parDepSurfDiffusion; //!< Parameter dependencies for particle surface diffusion + std::vector _parDepSurfDiffusion; //!< Parameter dependencies for particle surface diffusion bool _singleParDepSurfDiffusion; //!< Determines whether a single parameter dependence for particle surface diffusion is used bool _hasParDepSurfDiffusion; //!< Determines whether particle surface diffusion parameter dependencies are present diff --git a/src/libcadet/model/InletModel.cpp b/src/libcadet/model/InletModel.cpp index 851dc44db..3bb9908f0 100644 --- a/src/libcadet/model/InletModel.cpp +++ b/src/libcadet/model/InletModel.cpp @@ -62,7 +62,7 @@ bool InletModel::usesAD() const CADET_NOEXCEPT return false; } -bool InletModel::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool InletModel::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { _nComp = paramProvider.getInt("NCOMP"); const std::string inType = paramProvider.getString("INLET_TYPE"); diff --git a/src/libcadet/model/InletModel.hpp b/src/libcadet/model/InletModel.hpp index ea336caaf..9e2376f69 100644 --- a/src/libcadet/model/InletModel.hpp +++ b/src/libcadet/model/InletModel.hpp @@ -66,7 +66,7 @@ class InletModel : public IUnitOperation static const char* identifier() { return "INLET"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); diff --git a/src/libcadet/model/LumpedRateModelWithPores-InitialConditions.cpp b/src/libcadet/model/LumpedRateModelWithPores-InitialConditions.cpp index f4fac2f76..8e6c0ad0d 100644 --- a/src/libcadet/model/LumpedRateModelWithPores-InitialConditions.cpp +++ b/src/libcadet/model/LumpedRateModelWithPores-InitialConditions.cpp @@ -38,7 +38,8 @@ namespace cadet namespace model { -int LumpedRateModelWithPores::multiplexInitialConditions(const cadet::ParameterId& pId, unsigned int adDirection, double adValue) +template +int LumpedRateModelWithPores::multiplexInitialConditions(const cadet::ParameterId& pId, unsigned int adDirection, double adValue) { if (_singleBinding) { @@ -81,7 +82,8 @@ int LumpedRateModelWithPores::multiplexInitialConditions(const cadet::ParameterI return 0; } -int LumpedRateModelWithPores::multiplexInitialConditions(const cadet::ParameterId& pId, double val, bool checkSens) +template +int LumpedRateModelWithPores::multiplexInitialConditions(const cadet::ParameterId& pId, double val, bool checkSens) { if (_singleBinding) { @@ -132,7 +134,8 @@ int LumpedRateModelWithPores::multiplexInitialConditions(const cadet::ParameterI return 0; } -void LumpedRateModelWithPores::applyInitialCondition(const SimulationState& simState) const +template +void LumpedRateModelWithPores::applyInitialCondition(const SimulationState& simState) const { Indexer idxr(_disc); @@ -182,7 +185,8 @@ void LumpedRateModelWithPores::applyInitialCondition(const SimulationState& simS } } -void LumpedRateModelWithPores::readInitialCondition(IParameterProvider& paramProvider) +template +void LumpedRateModelWithPores::readInitialCondition(IParameterProvider& paramProvider) { _initState.clear(); _initStateDot.clear(); @@ -296,7 +300,8 @@ void LumpedRateModelWithPores::readInitialCondition(IParameterProvider& paramPro * @param [in] errorTol Error tolerance for algebraic equations * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void LumpedRateModelWithPores::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithPores::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -643,7 +648,8 @@ void LumpedRateModelWithPores::consistentInitialState(const SimulationTime& simT * @param [in] vecStateY Consistently initialized state vector * @param [in,out] vecStateYdot On entry, residual without taking time derivatives into account. On exit, consistent state time derivatives. */ -void LumpedRateModelWithPores::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithPores::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -789,7 +795,8 @@ void LumpedRateModelWithPores::consistentInitialTimeDerivative(const SimulationT * @param [in,out] adJac Jacobian information for AD (AD vectors for residual and state, direction offset) * @param [in] errorTol Error tolerance for algebraic equations */ -void LumpedRateModelWithPores::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithPores::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -846,7 +853,8 @@ void LumpedRateModelWithPores::leanConsistentInitialState(const SimulationTime& * @param [in,out] vecStateYdot On entry, inconsistent state time derivatives. On exit, partially consistent state time derivatives. * @param [in] res On entry, residual without taking time derivatives into account. The data is overwritten during execution of the function. */ -void LumpedRateModelWithPores::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithPores::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -879,7 +887,8 @@ void LumpedRateModelWithPores::leanConsistentInitialTimeDerivative(double t, dou solveForFluxes(vecStateYdot, idxr); } -void LumpedRateModelWithPores::initializeSensitivityStates(const std::vector& vecSensY) const +template +void LumpedRateModelWithPores::initializeSensitivityStates(const std::vector& vecSensY) const { Indexer idxr(_disc); for (std::size_t param = 0; param < vecSensY.size(); ++param) @@ -968,7 +977,8 @@ void LumpedRateModelWithPores::initializeSensitivityStates(const std::vector +void LumpedRateModelWithPores::consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1189,7 +1199,8 @@ void LumpedRateModelWithPores::consistentInitialSensitivity(const SimulationTime * @param [in] adRes Pointer to residual vector of AD datatypes with parameter sensitivities * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void LumpedRateModelWithPores::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, +template +void LumpedRateModelWithPores::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1243,7 +1254,8 @@ void LumpedRateModelWithPores::leanConsistentInitialSensitivity(const Simulation * on exit the solution @f$ j_f. @f$ * @param [in] idxr Indexer */ -void LumpedRateModelWithPores::solveForFluxes(double* const vecState, const Indexer& idxr) +template +void LumpedRateModelWithPores::solveForFluxes(double* const vecState, const Indexer& idxr) { // We have j_f - k_f * (c - c_p) == 0 // Thus, jacFC contains -k_f and jacFP +k_f. diff --git a/src/libcadet/model/LumpedRateModelWithPores-LinearSolver.cpp b/src/libcadet/model/LumpedRateModelWithPores-LinearSolver.cpp index f8c5414ef..60b688621 100644 --- a/src/libcadet/model/LumpedRateModelWithPores-LinearSolver.cpp +++ b/src/libcadet/model/LumpedRateModelWithPores-LinearSolver.cpp @@ -108,7 +108,8 @@ namespace model * @param [in] simState State of the simulation (state vector and its time derivatives) at which the Jacobian is evaluated * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error */ -int LumpedRateModelWithPores::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, +template +int LumpedRateModelWithPores::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, const ConstSimulationState& simState) { BENCH_SCOPE(_timerLinearSolve); @@ -358,7 +359,8 @@ int LumpedRateModelWithPores::linearSolve(double t, double alpha, double outerTo * @param [out] z Result of the matrix-vector multiplication * @return @c 0 if successful, any other value in case of failure */ -int LumpedRateModelWithPores::schurComplementMatrixVector(double const* x, double* z) const +template +int LumpedRateModelWithPores::schurComplementMatrixVector(double const* x, double* z) const { BENCH_SCOPE(_timerMatVec); @@ -460,7 +462,8 @@ int LumpedRateModelWithPores::schurComplementMatrixVector(double const* x, doubl * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) * @param [in] idxr Indexer */ -void LumpedRateModelWithPores::assembleDiscretizedJacobianParticleBlock(unsigned int parType, double alpha, const Indexer& idxr) +template +void LumpedRateModelWithPores::assembleDiscretizedJacobianParticleBlock(unsigned int parType, double alpha, const Indexer& idxr) { linalg::FactorizableBandMatrix& fbm = _jacPdisc[parType]; const linalg::BandMatrix& bm = _jacP[parType]; @@ -487,7 +490,8 @@ void LumpedRateModelWithPores::assembleDiscretizedJacobianParticleBlock(unsigned * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) * @param [in] parType Index of the particle type */ -void LumpedRateModelWithPores::addTimeDerivativeToJacobianParticleBlock(linalg::FactorizableBandMatrix::RowIterator& jac, const Indexer& idxr, double alpha, unsigned int parType) +template +void LumpedRateModelWithPores::addTimeDerivativeToJacobianParticleBlock(linalg::FactorizableBandMatrix::RowIterator& jac, const Indexer& idxr, double alpha, unsigned int parType) { // Mobile phase for (int comp = 0; comp < static_cast(_disc.nComp); ++comp, ++jac) diff --git a/src/libcadet/model/LumpedRateModelWithPores.cpp b/src/libcadet/model/LumpedRateModelWithPores.cpp index 31b552f55..a92857508 100644 --- a/src/libcadet/model/LumpedRateModelWithPores.cpp +++ b/src/libcadet/model/LumpedRateModelWithPores.cpp @@ -21,6 +21,7 @@ #include "model/BindingModel.hpp" #include "model/ReactionModel.hpp" #include "model/parts/BindingCellKernel.hpp" +#include "model/ParameterDependence.hpp" #include "SimulationTypes.hpp" #include "linalg/DenseMatrix.hpp" #include "linalg/BandMatrix.hpp" @@ -52,25 +53,30 @@ constexpr double SurfVolRatioSphere = 3.0; constexpr double SurfVolRatioCylinder = 2.0; constexpr double SurfVolRatioSlab = 1.0; +template int schurComplementMultiplierLRMPores(void* userData, double const* x, double* z) { - LumpedRateModelWithPores* const lrm = static_cast(userData); + typedef LumpedRateModelWithPores LRMP; + LRMP* const lrm = static_cast(userData); return lrm->schurComplementMatrixVector(x, z); } -LumpedRateModelWithPores::LumpedRateModelWithPores(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), - _dynReactionBulk(nullptr), _jacP(0), _jacPdisc(0), _jacPF(0), _jacFP(0), _jacInlet(), _analyticJac(true), +template +LumpedRateModelWithPores::LumpedRateModelWithPores(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), + _dynReactionBulk(nullptr), _filmDiffDep(nullptr), _jacP(0), _jacPdisc(0), _jacPF(0), _jacFP(0), _jacInlet(), _analyticJac(true), _jacobianAdDirs(0), _factorizeJacobian(false), _tempState(nullptr), _initC(0), _initCp(0), _initQ(0), _initState(0), _initStateDot(0) { } -LumpedRateModelWithPores::~LumpedRateModelWithPores() CADET_NOEXCEPT +template +LumpedRateModelWithPores::~LumpedRateModelWithPores() CADET_NOEXCEPT { delete[] _tempState; delete _dynReactionBulk; + delete _filmDiffDep; delete[] _disc.parTypeOffset; delete[] _disc.nBound; @@ -79,7 +85,8 @@ LumpedRateModelWithPores::~LumpedRateModelWithPores() CADET_NOEXCEPT delete[] _disc.nBoundBeforeType; } -unsigned int LumpedRateModelWithPores::numDofs() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::numDofs() const CADET_NOEXCEPT { // Column bulk DOFs: nCol * nComp // Particle DOFs: nCol * nParType particles each having nComp (liquid phase) + sum boundStates (solid phase) DOFs @@ -88,7 +95,8 @@ unsigned int LumpedRateModelWithPores::numDofs() const CADET_NOEXCEPT return _disc.nComp + _disc.nComp * _disc.nCol * (1 + _disc.nParType) + _disc.parTypeOffset[_disc.nParType]; } -unsigned int LumpedRateModelWithPores::numPureDofs() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::numPureDofs() const CADET_NOEXCEPT { // Column bulk DOFs: nCol * nComp // Particle DOFs: nCol * nParType particles each having nComp (liquid phase) + sum boundStates (solid phase) DOFs @@ -97,7 +105,8 @@ unsigned int LumpedRateModelWithPores::numPureDofs() const CADET_NOEXCEPT } -bool LumpedRateModelWithPores::usesAD() const CADET_NOEXCEPT +template +bool LumpedRateModelWithPores::usesAD() const CADET_NOEXCEPT { #ifdef CADET_CHECK_ANALYTIC_JACOBIAN // We always need AD if we want to check the analytical Jacobian @@ -108,7 +117,8 @@ bool LumpedRateModelWithPores::usesAD() const CADET_NOEXCEPT #endif } -bool LumpedRateModelWithPores::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +template +bool LumpedRateModelWithPores::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // ==== Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -214,7 +224,7 @@ bool LumpedRateModelWithPores::configureModelDiscretization(IParameterProvider& // Initialize and configure GMRES for solving the Schur-complement _gmres.initialize(_disc.nCol * _disc.nComp * _disc.nParType, paramProvider.getInt("MAX_KRYLOV"), linalg::toOrthogonalization(paramProvider.getInt("GS_TYPE")), paramProvider.getInt("MAX_RESTARTS")); - _gmres.matrixVectorMultiplier(&schurComplementMultiplierLRMPores, this); + _gmres.matrixVectorMultiplier(&schurComplementMultiplierLRMPores, this); _schurSafety = paramProvider.getDouble("SCHUR_SAFETY"); // Allocate space for initial conditions @@ -225,8 +235,6 @@ bool LumpedRateModelWithPores::configureModelDiscretization(IParameterProvider& // Create nonlinear solver for consistent initialization configureNonlinearSolver(paramProvider); - paramProvider.popScope(); - // Read particle geometry and default to "SPHERICAL" _parGeomSurfToVol = std::vector(_disc.nParType, SurfVolRatioSphere); if (paramProvider.exists("PAR_GEOM")) @@ -253,7 +261,21 @@ bool LumpedRateModelWithPores::configureModelDiscretization(IParameterProvider& } } - const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nCol); + paramProvider.popScope(); + + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nCol); + + if (paramProvider.exists("FILM_DIFFUSION_DEP")) + { + const std::string paramDepName = paramProvider.getString("FILM_DIFFUSION_DEP"); + _filmDiffDep = helper.createParameterParameterDependence(paramDepName); + if (!_filmDiffDep) + throw InvalidParameterException("Unknown parameter dependence " + paramDepName + " in FILM_DIFFUSION_DEP"); + + _filmDiffDep->configureModelDiscretization(paramProvider); + } + else + _filmDiffDep = helper.createParameterParameterDependence("CONSTANT_ONE"); // Allocate memory Indexer idxr(_disc); @@ -387,12 +409,19 @@ bool LumpedRateModelWithPores::configureModelDiscretization(IParameterProvider& return transportSuccess && bindingConfSuccess && reactionConfSuccess; } -bool LumpedRateModelWithPores::configure(IParameterProvider& paramProvider) +template +bool LumpedRateModelWithPores::configure(IParameterProvider& paramProvider) { _parameters.clear(); const bool transportSuccess = _convDispOp.configure(_unitOpIdx, paramProvider, _parameters); + if (_filmDiffDep) + { + if (!_filmDiffDep->configure(paramProvider, _unitOpIdx, ParTypeIndep, BoundStateIndep, "FILM_DIFFUSION_DEP")) + throw InvalidParameterException("Failed to configure film diffusion parameter dependency (FILM_DIFFUSION_DEP)"); + } + // Read geometry parameters _colPorosity = paramProvider.getDouble("COL_POROSITY"); _singleParRadius = readAndRegisterMultiplexTypeParam(paramProvider, _parameters, _parRadius, "PAR_RADIUS", _disc.nParType, _unitOpIdx); @@ -567,7 +596,8 @@ bool LumpedRateModelWithPores::configure(IParameterProvider& paramProvider) return transportSuccess && bindingConfSuccess && dynReactionConfSuccess; } -unsigned int LumpedRateModelWithPores::threadLocalMemorySize() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::threadLocalMemorySize() const CADET_NOEXCEPT { LinearMemorySizer lms; @@ -610,7 +640,8 @@ unsigned int LumpedRateModelWithPores::threadLocalMemorySize() const CADET_NOEXC return lms.bufferSize(); } -unsigned int LumpedRateModelWithPores::numAdDirsForJacobian() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::numAdDirsForJacobian() const CADET_NOEXCEPT { // We need as many directions as the highest bandwidth of the diagonal blocks: // The bandwidth of the column block depends on the size of the WENO stencil, whereas @@ -626,7 +657,8 @@ unsigned int LumpedRateModelWithPores::numAdDirsForJacobian() const CADET_NOEXCE return std::max(_convDispOp.requiredADdirs(), maxStride); } -void LumpedRateModelWithPores::useAnalyticJacobian(const bool analyticJac) +template +void LumpedRateModelWithPores::useAnalyticJacobian(const bool analyticJac) { #ifndef CADET_CHECK_ANALYTIC_JACOBIAN _analyticJac = analyticJac; @@ -641,7 +673,8 @@ void LumpedRateModelWithPores::useAnalyticJacobian(const bool analyticJac) #endif } -void LumpedRateModelWithPores::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) +template +void LumpedRateModelWithPores::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) { // Setup flux Jacobian blocks at the beginning of the simulation or in case of // section dependent film or particle diffusion coefficients @@ -650,22 +683,21 @@ void LumpedRateModelWithPores::notifyDiscontinuousSectionTransition(double t, un Indexer idxr(_disc); - // ConvectionDispersionOperator tells us whether flow direction has changed + // AxialConvectionDispersionOperator tells us whether flow direction has changed if (!_convDispOp.notifyDiscontinuousSectionTransition(t, secIdx, adJac)) return; // Setup the matrix connecting inlet DOFs to first column cells _jacInlet.clear(); - const double h = static_cast(_convDispOp.columnLength()) / static_cast(_disc.nCol); - const double u = static_cast(_convDispOp.currentVelocity()); + const double v = _convDispOp.inletJacobianFactor(); - if (u >= 0.0) + if (_convDispOp.forwardFlow()) { // Forwards flow // Place entries for inlet DOF to first column cell conversion for (unsigned int comp = 0; comp < _disc.nComp; ++comp) - _jacInlet.addElement(comp * idxr.strideColComp(), comp, -u / h); + _jacInlet.addElement(comp * idxr.strideColComp(), comp, -v); } else { @@ -674,30 +706,34 @@ void LumpedRateModelWithPores::notifyDiscontinuousSectionTransition(double t, un // Place entries for inlet DOF to last column cell conversion const unsigned int offset = (_disc.nCol - 1) * idxr.strideColCell(); for (unsigned int comp = 0; comp < _disc.nComp; ++comp) - _jacInlet.addElement(offset + comp * idxr.strideColComp(), comp, u / h); + _jacInlet.addElement(offset + comp * idxr.strideColComp(), comp, v); } } -void LumpedRateModelWithPores::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT +template +void LumpedRateModelWithPores::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT { _convDispOp.setFlowRates(in[0], out[0], _colPorosity); } -void LumpedRateModelWithPores::reportSolution(ISolutionRecorder& recorder, double const* const solution) const +template +void LumpedRateModelWithPores::reportSolution(ISolutionRecorder& recorder, double const* const solution) const { Exporter expr(_disc, *this, solution); recorder.beginUnitOperation(_unitOpIdx, *this, expr); recorder.endUnitOperation(); } -void LumpedRateModelWithPores::reportSolutionStructure(ISolutionRecorder& recorder) const +template +void LumpedRateModelWithPores::reportSolutionStructure(ISolutionRecorder& recorder) const { Exporter expr(_disc, *this, nullptr); recorder.unitOperationStructure(_unitOpIdx, *this, expr); } -unsigned int LumpedRateModelWithPores::requiredADdirs() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::requiredADdirs() const CADET_NOEXCEPT { #ifndef CADET_CHECK_ANALYTIC_JACOBIAN return _jacobianAdDirs; @@ -707,7 +743,8 @@ unsigned int LumpedRateModelWithPores::requiredADdirs() const CADET_NOEXCEPT #endif } -void LumpedRateModelWithPores::prepareADvectors(const AdJacobianParams& adJac) const +template +void LumpedRateModelWithPores::prepareADvectors(const AdJacobianParams& adJac) const { // Early out if AD is disabled if (!adJac.adY) @@ -733,7 +770,8 @@ void LumpedRateModelWithPores::prepareADvectors(const AdJacobianParams& adJac) c * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void LumpedRateModelWithPores::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) +template +void LumpedRateModelWithPores::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) { Indexer idxr(_disc); @@ -756,7 +794,8 @@ void LumpedRateModelWithPores::extractJacobianFromAD(active const* const adRes, * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void LumpedRateModelWithPores::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const +template +void LumpedRateModelWithPores::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const { Indexer idxr(_disc); @@ -778,7 +817,8 @@ void LumpedRateModelWithPores::checkAnalyticJacobianAgainstAd(active const* cons #endif -int LumpedRateModelWithPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidual); @@ -786,7 +826,8 @@ int LumpedRateModelWithPores::residual(const SimulationTime& simTime, const Cons return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); } -int LumpedRateModelWithPores::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithPores::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidual); @@ -794,7 +835,8 @@ int LumpedRateModelWithPores::residualWithJacobian(const SimulationTime& simTime return residual(simTime, simState, res, adJac, threadLocalMem, true, false); } -int LumpedRateModelWithPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, +template +int LumpedRateModelWithPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem, bool updateJacobian, bool paramSensitivity) { if (updateJacobian) @@ -896,8 +938,9 @@ int LumpedRateModelWithPores::residual(const SimulationTime& simTime, const Cons } } +template template -int LumpedRateModelWithPores::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) +int LumpedRateModelWithPores::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) { // Reset Jacobian if (wantJac) @@ -937,10 +980,11 @@ int LumpedRateModelWithPores::residualImpl(double t, unsigned int secIdx, StateT return 0; } +template template -int LumpedRateModelWithPores::residualBulk(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) +int LumpedRateModelWithPores::residualBulk(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) { - _convDispOp.residual(t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); + _convDispOp.residual(*this, t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); if (!_dynReactionBulk || (_dynReactionBulk->numReactionsLiquid() == 0)) return 0; @@ -965,8 +1009,9 @@ int LumpedRateModelWithPores::residualBulk(double t, unsigned int secIdx, StateT return 0; } +template template -int LumpedRateModelWithPores::residualParticle(double t, unsigned int parType, unsigned int colCell, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) +int LumpedRateModelWithPores::residualParticle(double t, unsigned int parType, unsigned int colCell, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) { Indexer idxr(_disc); @@ -979,7 +1024,7 @@ int LumpedRateModelWithPores::residualParticle(double t, unsigned int parType, u const ParamType radius = static_cast(_parRadius[parType]); // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = (0.5 + colCell) / static_cast(_disc.nCol); + const double z = _convDispOp.relativeCoordinate(colCell); const parts::cell::CellParameters cellResParams { @@ -1003,8 +1048,9 @@ int LumpedRateModelWithPores::residualParticle(double t, unsigned int parType, u return 0; } +template template -int LumpedRateModelWithPores::residualFlux(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase) +int LumpedRateModelWithPores::residualFlux(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase) { Indexer idxr(_disc); @@ -1042,7 +1088,12 @@ int LumpedRateModelWithPores::residualFlux(double t, unsigned int secIdx, StateT { const unsigned int colCell = i / _disc.nComp; const unsigned int comp = i % _disc.nComp; - resCol[i] += jacCF_val * static_cast(filmDiff[comp]) * static_cast(_parTypeVolFrac[type + _disc.nParType * colCell]) * yFluxType[i]; + + const double relPos = _convDispOp.relativeCoordinate(colCell); + const active curVelocity = _convDispOp.currentVelocity(relPos); + const active modifier = _filmDiffDep->getValue(*this, ColumnPosition{relPos, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, curVelocity); + + resCol[i] += jacCF_val * static_cast(filmDiff[comp]) * static_cast(modifier) * static_cast(_parTypeVolFrac[type + _disc.nParType * colCell]) * yFluxType[i]; } // J_{f,0} block, adds bulk volume state c_i to flux equation @@ -1058,10 +1109,15 @@ int LumpedRateModelWithPores::residualFlux(double t, unsigned int secIdx, StateT // J_{p,f} block, adds flux to particle / bead volume equations for (unsigned int pblk = 0; pblk < _disc.nCol; ++pblk) { + const double relPos = _convDispOp.relativeCoordinate(pblk); + const active curVelocity = _convDispOp.currentVelocity(relPos); + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) { + const active modifier = _filmDiffDep->getValue(*this, ColumnPosition{relPos, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, curVelocity); + const unsigned int eq = pblk * idxr.strideColCell() + comp * idxr.strideColComp(); - resParType[pblk * idxr.strideParBlock(type) + comp] += jacPF_val / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp]) * yFluxType[eq]; + resParType[pblk * idxr.strideParBlock(type) + comp] += jacPF_val / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp]) * static_cast(modifier) * yFluxType[eq]; } } @@ -1086,7 +1142,8 @@ int LumpedRateModelWithPores::residualFlux(double t, unsigned int secIdx, StateT * @param [in] t Current time * @param [in] secIdx Index of the current section */ -void LumpedRateModelWithPores::assembleOffdiagJac(double t, unsigned int secIdx) +template +void LumpedRateModelWithPores::assembleOffdiagJac(double t, unsigned int secIdx) { // Clear matrices for new assembly _jacCF.clear(); @@ -1121,8 +1178,12 @@ void LumpedRateModelWithPores::assembleOffdiagJac(double t, unsigned int secIdx) const unsigned int colCell = eq / _disc.nComp; const unsigned int comp = eq % _disc.nComp; + const double relPos = _convDispOp.relativeCoordinate(colCell); + const double curVelocity = static_cast(_convDispOp.currentVelocity(relPos)); + const double modifier = _filmDiffDep->getValue(*this, ColumnPosition{relPos, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, curVelocity); + // Main diagonal corresponds to j_{f,i} (flux) state variable - _jacCF.addElement(eq, eq + typeOffset, jacCF_val * static_cast(filmDiff[comp]) * static_cast(_parTypeVolFrac[type + _disc.nParType * colCell])); + _jacCF.addElement(eq, eq + typeOffset, jacCF_val * static_cast(filmDiff[comp]) * modifier * static_cast(_parTypeVolFrac[type + _disc.nParType * colCell])); } // J_{f,0} block, adds bulk volume state c_i to flux equation @@ -1134,11 +1195,16 @@ void LumpedRateModelWithPores::assembleOffdiagJac(double t, unsigned int secIdx) // J_{p,f} block, implements bead boundary condition in outer bead shell equation for (unsigned int pblk = 0; pblk < _disc.nCol; ++pblk) { + const double relPos = _convDispOp.relativeCoordinate(pblk); + const double curVelocity = static_cast(_convDispOp.currentVelocity(relPos)); + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) { const unsigned int eq = typeOffset + pblk * idxr.strideColCell() + comp * idxr.strideColComp(); const unsigned int col = pblk * idxr.strideParBlock(type) + comp; - _jacPF[type].addElement(col, eq, jacPF_val / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp])); + + const double modifier = _filmDiffDep->getValue(*this, ColumnPosition{relPos, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, curVelocity); + _jacPF[type].addElement(col, eq, jacPF_val / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp]) * modifier); } } @@ -1155,7 +1221,8 @@ void LumpedRateModelWithPores::assembleOffdiagJac(double t, unsigned int secIdx) } } -int LumpedRateModelWithPores::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, +template +int LumpedRateModelWithPores::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidualSens); @@ -1165,7 +1232,8 @@ int LumpedRateModelWithPores::residualSensFwdWithJacobian(const SimulationTime& return residual(simTime, simState, nullptr, adJac, threadLocalMem, true, true); } -int LumpedRateModelWithPores::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithPores::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidualSens); @@ -1173,7 +1241,8 @@ int LumpedRateModelWithPores::residualSensFwdAdOnly(const SimulationTime& simTim return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, adRes, threadLocalMem); } -int LumpedRateModelWithPores::residualSensFwdCombine(const SimulationTime& simTime, const ConstSimulationState& simState, +template +int LumpedRateModelWithPores::residualSensFwdCombine(const SimulationTime& simTime, const ConstSimulationState& simState, const std::vector& yS, const std::vector& ySdot, const std::vector& resS, active const* adRes, double* const tmp1, double* const tmp2, double* const tmp3) { @@ -1224,7 +1293,8 @@ int LumpedRateModelWithPores::residualSensFwdCombine(const SimulationTime& simTi * @param [in] beta Factor @f$ \beta @f$ in front of @f$ z @f$ * @param [in,out] ret Vector @f$ z @f$ which stores the result of the operation */ -void LumpedRateModelWithPores::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) +template +void LumpedRateModelWithPores::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) { Indexer idxr(_disc); @@ -1283,7 +1353,8 @@ void LumpedRateModelWithPores::multiplyWithJacobian(const SimulationTime& simTim * @param [in] sDot Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ * @param [out] ret Vector @f$ z @f$ which stores the result of the operation */ -void LumpedRateModelWithPores::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) +template +void LumpedRateModelWithPores::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) { Indexer idxr(_disc); @@ -1353,7 +1424,8 @@ void LumpedRateModelWithPores::multiplyWithDerivativeJacobian(const SimulationTi std::fill_n(ret, _disc.nComp, 0.0); } -void LumpedRateModelWithPores::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) +template +void LumpedRateModelWithPores::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) { for (IBindingModel* bm : _binding) { @@ -1362,10 +1434,11 @@ void LumpedRateModelWithPores::setExternalFunctions(IExternalFunction** extFuns, } } -unsigned int LumpedRateModelWithPores::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT { // Inlets are duplicated so need to be accounted for - if (static_cast(_convDispOp.currentVelocity()) >= 0.0) + if (_convDispOp.forwardFlow()) // Forward Flow: outlet is last cell return _disc.nComp + (_disc.nCol - 1) * _disc.nComp; else @@ -1373,27 +1446,32 @@ unsigned int LumpedRateModelWithPores::localOutletComponentIndex(unsigned int po return _disc.nComp; } -unsigned int LumpedRateModelWithPores::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT { return 0; } -unsigned int LumpedRateModelWithPores::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT { return 1; } -unsigned int LumpedRateModelWithPores::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithPores::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT { return 1; } -void LumpedRateModelWithPores::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) +template +void LumpedRateModelWithPores::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) { // @todo Write this function } -bool LumpedRateModelWithPores::setParameter(const ParameterId& pId, double value) +template +bool LumpedRateModelWithPores::setParameter(const ParameterId& pId, double value) { if (pId.unitOperation == _unitOpIdx) { @@ -1429,12 +1507,22 @@ bool LumpedRateModelWithPores::setParameter(const ParameterId& pId, double value if (_convDispOp.setParameter(pId, value)) return true; + + if (_filmDiffDep) + { + if (_filmDiffDep->hasParameter(pId)) + { + _filmDiffDep->setParameter(pId, value); + return true; + } + } } return UnitOperationBase::setParameter(pId, value); } -void LumpedRateModelWithPores::setSensitiveParameterValue(const ParameterId& pId, double value) +template +void LumpedRateModelWithPores::setSensitiveParameterValue(const ParameterId& pId, double value) { if (pId.unitOperation == _unitOpIdx) { @@ -1469,12 +1557,23 @@ void LumpedRateModelWithPores::setSensitiveParameterValue(const ParameterId& pId if (_convDispOp.setSensitiveParameterValue(_sensParams, pId, value)) return; + + if (_filmDiffDep) + { + active* const param = _filmDiffDep->getParameter(pId); + if (param) + { + param->setValue(value); + return; + } + } } UnitOperationBase::setSensitiveParameterValue(pId, value); } -bool LumpedRateModelWithPores::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) +template +bool LumpedRateModelWithPores::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) { if (pId.unitOperation == _unitOpIdx) { @@ -1534,20 +1633,33 @@ bool LumpedRateModelWithPores::setSensitiveParameter(const ParameterId& pId, uns LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; return true; } + + if (_filmDiffDep) + { + active* const param = _filmDiffDep->getParameter(pId); + if (param) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + param->setADValue(adDirection, adValue); + return true; + } + } } return UnitOperationBase::setSensitiveParameter(pId, adDirection, adValue); } -int LumpedRateModelWithPores::Exporter::writeMobilePhase(double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeMobilePhase(double* buffer) const { const int blockSize = _disc.nComp * _disc.nCol; std::copy_n(_idx.c(_data), blockSize, buffer); return blockSize; } -int LumpedRateModelWithPores::Exporter::writeSolidPhase(double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeSolidPhase(double* buffer) const { int numWritten = 0; for (unsigned int i = 0; i < _disc.nParType; ++i) @@ -1559,7 +1671,8 @@ int LumpedRateModelWithPores::Exporter::writeSolidPhase(double* buffer) const return numWritten; } -int LumpedRateModelWithPores::Exporter::writeParticleMobilePhase(double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeParticleMobilePhase(double* buffer) const { int numWritten = 0; for (unsigned int i = 0; i < _disc.nParType; ++i) @@ -1571,7 +1684,8 @@ int LumpedRateModelWithPores::Exporter::writeParticleMobilePhase(double* buffer) return numWritten; } -int LumpedRateModelWithPores::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const { cadet_assert(parType < _disc.nParType); @@ -1586,7 +1700,8 @@ int LumpedRateModelWithPores::Exporter::writeSolidPhase(unsigned int parType, do return _disc.nCol * _disc.strideBound[parType]; } -int LumpedRateModelWithPores::Exporter::writeParticleMobilePhase(unsigned int parType, double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeParticleMobilePhase(unsigned int parType, double* buffer) const { cadet_assert(parType < _disc.nParType); @@ -1601,38 +1716,43 @@ int LumpedRateModelWithPores::Exporter::writeParticleMobilePhase(unsigned int pa return _disc.nCol * _disc.nComp; } -int LumpedRateModelWithPores::Exporter::writeParticleFlux(double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeParticleFlux(double* buffer) const { const int blockSize = numParticleFluxDofs(); std::copy_n(_idx.jf(_data), blockSize, buffer); return blockSize; } -int LumpedRateModelWithPores::Exporter::writeParticleFlux(unsigned int parType, double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeParticleFlux(unsigned int parType, double* buffer) const { const unsigned int blockSize = _disc.nComp * _disc.nCol; std::copy_n(_idx.jf(_data) + blockSize * parType, blockSize, buffer); return blockSize; } -int LumpedRateModelWithPores::Exporter::writeInlet(unsigned int port, double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeInlet(unsigned int port, double* buffer) const { cadet_assert(port == 0); std::copy_n(_data, _disc.nComp, buffer); return _disc.nComp; } -int LumpedRateModelWithPores::Exporter::writeInlet(double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeInlet(double* buffer) const { std::copy_n(_data, _disc.nComp, buffer); return _disc.nComp; } -int LumpedRateModelWithPores::Exporter::writeOutlet(unsigned int port, double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeOutlet(unsigned int port, double* buffer) const { cadet_assert(port == 0); - if (_model._convDispOp.currentVelocity() >= 0) + if (_model._convDispOp.forwardFlow()) std::copy_n(&_idx.c(_data, _disc.nCol - 1, 0), _disc.nComp, buffer); else std::copy_n(&_idx.c(_data, 0, 0), _disc.nComp, buffer); @@ -1640,9 +1760,10 @@ int LumpedRateModelWithPores::Exporter::writeOutlet(unsigned int port, double* b return _disc.nComp; } -int LumpedRateModelWithPores::Exporter::writeOutlet(double* buffer) const +template +int LumpedRateModelWithPores::Exporter::writeOutlet(double* buffer) const { - if (_model._convDispOp.currentVelocity() >= 0) + if (_model._convDispOp.forwardFlow()) std::copy_n(&_idx.c(_data, _disc.nCol - 1, 0), _disc.nComp, buffer); else std::copy_n(&_idx.c(_data, 0, 0), _disc.nComp, buffer); @@ -1650,11 +1771,33 @@ int LumpedRateModelWithPores::Exporter::writeOutlet(double* buffer) const return _disc.nComp; } +} // namespace model + +} // namespace cadet + +#include "model/LumpedRateModelWithPores-InitialConditions.cpp" +#include "model/LumpedRateModelWithPores-LinearSolver.cpp" + +namespace cadet +{ + +namespace model +{ + +// Template instantiations +template class LumpedRateModelWithPores; +template class LumpedRateModelWithPores; void registerLumpedRateModelWithPores(std::unordered_map>& models) { - models[LumpedRateModelWithPores::identifier()] = [](UnitOpIdx uoId) { return new LumpedRateModelWithPores(uoId); }; - models["LRMP"] = [](UnitOpIdx uoId) { return new LumpedRateModelWithPores(uoId); }; + typedef LumpedRateModelWithPores AxialLRMP; + typedef LumpedRateModelWithPores RadialLRMP; + + models[AxialLRMP::identifier()] = [](UnitOpIdx uoId) { return new AxialLRMP(uoId); }; + models["LRMP"] = [](UnitOpIdx uoId) { return new AxialLRMP(uoId); }; + + models[RadialLRMP::identifier()] = [](UnitOpIdx uoId) { return new RadialLRMP(uoId); }; + models["RLRMP"] = [](UnitOpIdx uoId) { return new RadialLRMP(uoId); }; } } // namespace model diff --git a/src/libcadet/model/LumpedRateModelWithPores.hpp b/src/libcadet/model/LumpedRateModelWithPores.hpp index 687e71451..1fcc6fbd6 100644 --- a/src/libcadet/model/LumpedRateModelWithPores.hpp +++ b/src/libcadet/model/LumpedRateModelWithPores.hpp @@ -35,6 +35,24 @@ #include "Benchmark.hpp" +namespace +{ + template + struct LumpedRateModelWithPoresName { }; + + template <> + struct LumpedRateModelWithPoresName + { + static const char* identifier() CADET_NOEXCEPT { return "LUMPED_RATE_MODEL_WITH_PORES"; } + }; + + template <> + struct LumpedRateModelWithPoresName + { + static const char* identifier() CADET_NOEXCEPT { return "RADIAL_LUMPED_RATE_MODEL_WITH_PORES"; } + }; +} + namespace cadet { @@ -62,6 +80,7 @@ u c_{\text{in},i}(t) &= u c_i(t,0) - D_{\text{ax},i} \frac{\partial c_i}{\partia \end{align} @f] * Methods are described in @cite VonLieres2010a (WENO, linear solver), @cite Puttmann2013 @cite Puttmann2016 (forward sensitivities, AD, band compression) */ +template class LumpedRateModelWithPores : public UnitOperationBase { public: @@ -81,10 +100,10 @@ class LumpedRateModelWithPores : public UnitOperationBase virtual unsigned int numOutletPorts() const CADET_NOEXCEPT { return 1; } virtual bool canAccumulate() const CADET_NOEXCEPT { return false; } - static const char* identifier() { return "LUMPED_RATE_MODEL_WITH_PORES"; } + static const char* identifier() CADET_NOEXCEPT { return LumpedRateModelWithPoresName::identifier(); } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); @@ -243,8 +262,9 @@ class LumpedRateModelWithPores : public UnitOperationBase Discretization _disc; //!< Discretization info // IExternalFunction* _extFun; //!< External function (owned by library user) - parts::ConvectionDispersionOperator _convDispOp; //!< Convection dispersion operator for interstitial volume transport + ConvDispOperator _convDispOp; //!< Convection dispersion operator for interstitial volume transport IDynamicReactionModel* _dynReactionBulk; //!< Dynamic reactions in the bulk volume + IParameterParameterDependence* _filmDiffDep; //!< Film diffusion dependency on local velocity std::vector _jacP; //!< Particle jacobian diagonal blocks (all of them for each particle type) std::vector _jacPdisc; //!< Particle jacobian diagonal blocks (all of them for each particle type) with time derivatives from BDF method @@ -299,6 +319,7 @@ class LumpedRateModelWithPores : public UnitOperationBase BENCH_TIMER(_timerGmres) // Wrapper for calling the corresponding function in GeneralRateModel class + template friend int schurComplementMultiplierLRMPores(void* userData, double const* x, double* z); class Indexer @@ -400,9 +421,8 @@ class LumpedRateModelWithPores : public UnitOperationBase virtual int writePrimaryCoordinates(double* coords) const { - const double h = static_cast(_model._convDispOp.columnLength()) / static_cast(_disc.nCol); for (unsigned int i = 0; i < _disc.nCol; ++i) - coords[i] = (i + 0.5) * h; + coords[i] = _model._convDispOp.cellCenter(i); return _disc.nCol; } virtual int writeSecondaryCoordinates(double* coords) const { return 0; } @@ -420,6 +440,9 @@ class LumpedRateModelWithPores : public UnitOperationBase }; }; +extern template class LumpedRateModelWithPores; +extern template class LumpedRateModelWithPores; + } // namespace model } // namespace cadet diff --git a/src/libcadet/model/LumpedRateModelWithPoresDG.cpp b/src/libcadet/model/LumpedRateModelWithPoresDG.cpp index 2cd287e92..9a33bf003 100644 --- a/src/libcadet/model/LumpedRateModelWithPoresDG.cpp +++ b/src/libcadet/model/LumpedRateModelWithPoresDG.cpp @@ -100,7 +100,7 @@ bool LumpedRateModelWithPoresDG::usesAD() const CADET_NOEXCEPT #endif } -bool LumpedRateModelWithPoresDG::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool LumpedRateModelWithPoresDG::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // ==== Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -226,9 +226,9 @@ bool LumpedRateModelWithPoresDG::configureModelDiscretization(IParameterProvider paramProvider.popScope(); - const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nPoints, 0); // strideCell not needed for DG, so just set to zero _disc.dispersion = Eigen::VectorXd::Zero(_disc.nComp); // fill later on with convDispOp (section and component dependent) + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nPoints, 0); // strideCell not needed for DG, so just set to zero _disc.velocity = static_cast(_convDispOp.currentVelocity()); // updated later on with convDispOp (section dependent) _disc.curSection = -1; diff --git a/src/libcadet/model/LumpedRateModelWithPoresDG.hpp b/src/libcadet/model/LumpedRateModelWithPoresDG.hpp index 73830dca7..5d1a2fea0 100644 --- a/src/libcadet/model/LumpedRateModelWithPoresDG.hpp +++ b/src/libcadet/model/LumpedRateModelWithPoresDG.hpp @@ -91,7 +91,7 @@ class LumpedRateModelWithPoresDG : public UnitOperationBase static const char* identifier() { return "LUMPED_RATE_MODEL_WITH_PORES_DG"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); @@ -704,7 +704,7 @@ class LumpedRateModelWithPoresDG : public UnitOperationBase Discretization _disc; //!< Discretization info // IExternalFunction* _extFun; //!< External function (owned by library user) - parts::ConvectionDispersionOperatorBase _convDispOp; //!< Convection dispersion operator for interstitial volume transport + parts::AxialConvectionDispersionOperatorBase _convDispOp; //!< Convection dispersion operator for interstitial volume transport IDynamicReactionModel* _dynReactionBulk; //!< Dynamic reactions in the bulk volume Eigen::SparseLU> _globalSolver; //!< linear solver for the bulk concentration diff --git a/src/libcadet/model/LumpedRateModelWithoutPores.cpp b/src/libcadet/model/LumpedRateModelWithoutPores.cpp index 598caadcb..4e04bf1a0 100644 --- a/src/libcadet/model/LumpedRateModelWithoutPores.cpp +++ b/src/libcadet/model/LumpedRateModelWithoutPores.cpp @@ -42,44 +42,44 @@ namespace { - template + template class ConvOpResidual { public: - static inline void call(cadet::model::parts::ConvectionDispersionOperatorBase& op, double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) + static inline void call(cadet::IModel* const model, ConvDispOperator& op, double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) { // This should not be reached cadet_assert(false); } }; - template - class ConvOpResidual + template + class ConvOpResidual { public: - static inline void call(cadet::model::parts::ConvectionDispersionOperatorBase& op, double t, unsigned int secIdx, double const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) + static inline void call(cadet::IModel* const model, ConvDispOperator& op, double t, unsigned int secIdx, double const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) { - op.residual(t, secIdx, y, yDot, res, jac); + op.residual(*model, t, secIdx, y, yDot, res, jac); } }; - template - class ConvOpResidual + template + class ConvOpResidual { public: - static inline void call(cadet::model::parts::ConvectionDispersionOperatorBase& op, double t, unsigned int secIdx, double const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) + static inline void call(cadet::IModel* const model, ConvDispOperator& op, double t, unsigned int secIdx, double const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) { - op.residual(t, secIdx, y, yDot, res, typename cadet::ParamSens::enabled()); + op.residual(*model, t, secIdx, y, yDot, res, typename cadet::ParamSens::enabled()); } }; - template - class ConvOpResidual + template + class ConvOpResidual { public: - static inline void call(cadet::model::parts::ConvectionDispersionOperatorBase& op, double t, unsigned int secIdx, cadet::active const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) + static inline void call(cadet::IModel* const model, ConvDispOperator& op, double t, unsigned int secIdx, cadet::active const* const y, double const* const yDot, ResidualType* const res, cadet::linalg::BandMatrix& jac) { - op.residual(t, secIdx, y, yDot, res, typename cadet::ParamSens::enabled()); + op.residual(*model, t, secIdx, y, yDot, res, typename cadet::ParamSens::enabled()); } }; } @@ -90,7 +90,8 @@ namespace cadet namespace model { -LumpedRateModelWithoutPores::LumpedRateModelWithoutPores(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), +template +LumpedRateModelWithoutPores::LumpedRateModelWithoutPores(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), _jacInlet(), _analyticJac(true), _jacobianAdDirs(0), _factorizeJacobian(false), _tempState(nullptr), _initC(0), _initQ(0), _initState(0), _initStateDot(0) { @@ -99,7 +100,8 @@ LumpedRateModelWithoutPores::LumpedRateModelWithoutPores(UnitOpIdx unitOpIdx) : _singleDynReaction = true; } -LumpedRateModelWithoutPores::~LumpedRateModelWithoutPores() CADET_NOEXCEPT +template +LumpedRateModelWithoutPores::~LumpedRateModelWithoutPores() CADET_NOEXCEPT { delete[] _tempState; @@ -107,21 +109,24 @@ LumpedRateModelWithoutPores::~LumpedRateModelWithoutPores() CADET_NOEXCEPT delete[] _disc.boundOffset; } -unsigned int LumpedRateModelWithoutPores::numDofs() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::numDofs() const CADET_NOEXCEPT { // Column bulk DOFs: nCol * nComp mobile phase and nCol * (sum boundStates) solid phase // Inlet DOFs: nComp return _disc.nCol * (_disc.nComp + _disc.strideBound) + _disc.nComp; } -unsigned int LumpedRateModelWithoutPores::numPureDofs() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::numPureDofs() const CADET_NOEXCEPT { // Column bulk DOFs: nCol * nComp mobile phase and nCol * (sum boundStates) solid phase return _disc.nCol * (_disc.nComp + _disc.strideBound); } -bool LumpedRateModelWithoutPores::usesAD() const CADET_NOEXCEPT +template +bool LumpedRateModelWithoutPores::usesAD() const CADET_NOEXCEPT { #ifdef CADET_CHECK_ANALYTIC_JACOBIAN // We always need AD if we want to check the analytical Jacobian @@ -132,7 +137,8 @@ bool LumpedRateModelWithoutPores::usesAD() const CADET_NOEXCEPT #endif } -bool LumpedRateModelWithoutPores::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +template +bool LumpedRateModelWithoutPores::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // ==== Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -185,7 +191,7 @@ bool LumpedRateModelWithoutPores::configureModelDiscretization(IParameterProvide paramProvider.popScope(); const unsigned int strideCell = _disc.nComp + _disc.strideBound; - const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nCol, strideCell); + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nCol, strideCell); // Allocate memory Indexer idxr(_disc); @@ -252,7 +258,8 @@ bool LumpedRateModelWithoutPores::configureModelDiscretization(IParameterProvide return transportSuccess && bindingConfSuccess && reactionConfSuccess; } -bool LumpedRateModelWithoutPores::configure(IParameterProvider& paramProvider) +template +bool LumpedRateModelWithoutPores::configure(IParameterProvider& paramProvider) { _parameters.clear(); @@ -298,7 +305,8 @@ bool LumpedRateModelWithoutPores::configure(IParameterProvider& paramProvider) return transportSuccess && bindingConfSuccess && reactionConfSuccess; } -unsigned int LumpedRateModelWithoutPores::threadLocalMemorySize() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::threadLocalMemorySize() const CADET_NOEXCEPT { LinearMemorySizer lms; @@ -337,7 +345,8 @@ unsigned int LumpedRateModelWithoutPores::threadLocalMemorySize() const CADET_NO return lms.bufferSize(); } -void LumpedRateModelWithoutPores::useAnalyticJacobian(const bool analyticJac) +template +void LumpedRateModelWithoutPores::useAnalyticJacobian(const bool analyticJac) { #ifndef CADET_CHECK_ANALYTIC_JACOBIAN _analyticJac = analyticJac; @@ -351,7 +360,8 @@ void LumpedRateModelWithoutPores::useAnalyticJacobian(const bool analyticJac) #endif } -void LumpedRateModelWithoutPores::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) +template +void LumpedRateModelWithoutPores::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) { Indexer idxr(_disc); @@ -361,18 +371,17 @@ void LumpedRateModelWithoutPores::notifyDiscontinuousSectionTransition(double t, // Setup the matrix connecting inlet DOFs to first column cells _jacInlet.clear(); - const double h = static_cast(_convDispOp.columnLength()) / static_cast(_disc.nCol); - const double u = static_cast(_convDispOp.currentVelocity()); + const double v = _convDispOp.inletJacobianFactor(); const unsigned int lb = _convDispOp.jacobianLowerBandwidth(); const unsigned int ub = _convDispOp.jacobianUpperBandwidth(); - if (u >= 0.0) + if (_convDispOp.forwardFlow()) { // Forwards flow // Place entries for inlet DOF to first column cell conversion for (unsigned int comp = 0; comp < _disc.nComp; ++comp) - _jacInlet.addElement(comp * idxr.strideColComp(), comp, -u / h); + _jacInlet.addElement(comp * idxr.strideColComp(), comp, -v); // Repartition Jacobians _jac.repartition(lb, ub); @@ -385,7 +394,7 @@ void LumpedRateModelWithoutPores::notifyDiscontinuousSectionTransition(double t, // Place entries for inlet DOF to last column cell conversion const unsigned int offset = (_disc.nCol - 1) * idxr.strideColCell(); for (unsigned int comp = 0; comp < _disc.nComp; ++comp) - _jacInlet.addElement(offset + comp * idxr.strideColComp(), comp, u / h); + _jacInlet.addElement(offset + comp * idxr.strideColComp(), comp, v); // Repartition Jacobians _jac.repartition(ub, lb); @@ -395,26 +404,30 @@ void LumpedRateModelWithoutPores::notifyDiscontinuousSectionTransition(double t, prepareADvectors(adJac); } -void LumpedRateModelWithoutPores::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT +template +void LumpedRateModelWithoutPores::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT { _convDispOp.setFlowRates(in[0], out[0], _totalPorosity); } -void LumpedRateModelWithoutPores::reportSolution(ISolutionRecorder& recorder, double const* const solution) const +template +void LumpedRateModelWithoutPores::reportSolution(ISolutionRecorder& recorder, double const* const solution) const { Exporter expr(_disc, *this, solution); recorder.beginUnitOperation(_unitOpIdx, *this, expr); recorder.endUnitOperation(); } -void LumpedRateModelWithoutPores::reportSolutionStructure(ISolutionRecorder& recorder) const +template +void LumpedRateModelWithoutPores::reportSolutionStructure(ISolutionRecorder& recorder) const { Exporter expr(_disc, *this, nullptr); recorder.unitOperationStructure(_unitOpIdx, *this, expr); } -unsigned int LumpedRateModelWithoutPores::requiredADdirs() const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::requiredADdirs() const CADET_NOEXCEPT { #ifndef CADET_CHECK_ANALYTIC_JACOBIAN return _jacobianAdDirs; @@ -424,7 +437,8 @@ unsigned int LumpedRateModelWithoutPores::requiredADdirs() const CADET_NOEXCEPT #endif } -void LumpedRateModelWithoutPores::prepareADvectors(const AdJacobianParams& adJac) const +template +void LumpedRateModelWithoutPores::prepareADvectors(const AdJacobianParams& adJac) const { // Early out if AD is disabled if (!adJac.adY) @@ -444,7 +458,8 @@ void LumpedRateModelWithoutPores::prepareADvectors(const AdJacobianParams& adJac * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void LumpedRateModelWithoutPores::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) +template +void LumpedRateModelWithoutPores::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) { Indexer idxr(_disc); ad::extractBandedJacobianFromAd(adRes + idxr.offsetC(), adDirOffset, _jac.lowerBandwidth(), _jac); @@ -458,7 +473,8 @@ void LumpedRateModelWithoutPores::extractJacobianFromAD(active const* const adRe * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void LumpedRateModelWithoutPores::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const +template +void LumpedRateModelWithoutPores::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const { Indexer idxr(_disc); @@ -468,7 +484,8 @@ void LumpedRateModelWithoutPores::checkAnalyticJacobianAgainstAd(active const* c #endif -int LumpedRateModelWithoutPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithoutPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidual); @@ -476,7 +493,8 @@ int LumpedRateModelWithoutPores::residual(const SimulationTime& simTime, const C return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); } -int LumpedRateModelWithoutPores::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithoutPores::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidual); @@ -484,7 +502,8 @@ int LumpedRateModelWithoutPores::residualWithJacobian(const SimulationTime& simT return residual(simTime, simState, res, adJac, threadLocalMem, true, false); } -int LumpedRateModelWithoutPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, +template +int LumpedRateModelWithoutPores::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem, bool updateJacobian, bool paramSensitivity) { if (updateJacobian) @@ -586,10 +605,11 @@ int LumpedRateModelWithoutPores::residual(const SimulationTime& simTime, const C } } +template template -int LumpedRateModelWithoutPores::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) +int LumpedRateModelWithoutPores::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) { - ConvOpResidual::call(_convDispOp, t, secIdx, y, yDot, res, _jac); + ConvOpResidual::call(this, _convDispOp, t, secIdx, y, yDot, res, _jac); Indexer idxr(_disc); @@ -617,7 +637,7 @@ int LumpedRateModelWithoutPores::residualImpl(double t, unsigned int secIdx, Sta }; // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = 1.0 / static_cast(_disc.nCol) * (0.5 + col); + const double z = _convDispOp.relativeCoordinate(col); parts::cell::residualKernel( t, secIdx, ColumnPosition{z, 0.0, 0.0}, localY, localYdot, localRes, _jac.row(col * idxr.strideColCell()), cellResParams, threadLocalMem.get() @@ -636,7 +656,8 @@ int LumpedRateModelWithoutPores::residualImpl(double t, unsigned int secIdx, Sta return 0; } -int LumpedRateModelWithoutPores::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithoutPores::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidualSens); @@ -645,7 +666,8 @@ int LumpedRateModelWithoutPores::residualSensFwdWithJacobian(const SimulationTim return residual(simTime, simState, nullptr, adJac, threadLocalMem, true, true); } -int LumpedRateModelWithoutPores::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) +template +int LumpedRateModelWithoutPores::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerResidualSens); @@ -653,7 +675,8 @@ int LumpedRateModelWithoutPores::residualSensFwdAdOnly(const SimulationTime& sim return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, adRes, threadLocalMem); } -int LumpedRateModelWithoutPores::residualSensFwdCombine(const SimulationTime& simTime, const ConstSimulationState& simState, +template +int LumpedRateModelWithoutPores::residualSensFwdCombine(const SimulationTime& simTime, const ConstSimulationState& simState, const std::vector& yS, const std::vector& ySdot, const std::vector& resS, active const* adRes, double* const tmp1, double* const tmp2, double* const tmp3) { @@ -704,7 +727,8 @@ int LumpedRateModelWithoutPores::residualSensFwdCombine(const SimulationTime& si * @param [in] beta Factor @f$ \beta @f$ in front of @f$ z @f$ * @param [in,out] ret Vector @f$ z @f$ which stores the result of the operation */ -void LumpedRateModelWithoutPores::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) +template +void LumpedRateModelWithoutPores::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) { Indexer idxr(_disc); @@ -730,7 +754,8 @@ void LumpedRateModelWithoutPores::multiplyWithJacobian(const SimulationTime& sim * @param [in] sDot Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ * @param [out] ret Vector @f$ z @f$ which stores the result of the operation */ -void LumpedRateModelWithoutPores::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) +template +void LumpedRateModelWithoutPores::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) { Indexer idxr(_disc); const double invBeta = (1.0 / static_cast(_totalPorosity) - 1.0); @@ -749,16 +774,18 @@ void LumpedRateModelWithoutPores::multiplyWithDerivativeJacobian(const Simulatio std::fill_n(ret, _disc.nComp, 0.0); } -void LumpedRateModelWithoutPores::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) +template +void LumpedRateModelWithoutPores::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) { if (_binding[0]) _binding[0]->setExternalFunctions(extFuns, size); } -unsigned int LumpedRateModelWithoutPores::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT { // Inlets are duplicated so need to be accounted for - if (static_cast(_convDispOp.currentVelocity()) >= 0.0) + if (_convDispOp.forwardFlow()) // Forward Flow: outlet is last cell return _disc.nComp + (_disc.nCol - 1) * (_disc.nComp + _disc.strideBound); else @@ -766,22 +793,26 @@ unsigned int LumpedRateModelWithoutPores::localOutletComponentIndex(unsigned int return _disc.nComp; } -unsigned int LumpedRateModelWithoutPores::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT { return 0; } -unsigned int LumpedRateModelWithoutPores::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT { return 1; } -unsigned int LumpedRateModelWithoutPores::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT +template +unsigned int LumpedRateModelWithoutPores::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT { return 1; } -void LumpedRateModelWithoutPores::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) +template +void LumpedRateModelWithoutPores::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) { // @todo Write this function } @@ -801,7 +832,8 @@ void LumpedRateModelWithoutPores::expandErrorTol(double const* errorSpec, unsign * @param [in] simState State of the simulation (state vector and its time derivatives) at which the Jacobian is evaluated * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error */ -int LumpedRateModelWithoutPores::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, +template +int LumpedRateModelWithoutPores::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, const ConstSimulationState& simState) { BENCH_SCOPE(_timerLinearSolve); @@ -856,7 +888,8 @@ int LumpedRateModelWithoutPores::linearSolve(double t, double alpha, double oute * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) * @param [in] idxr Indexer */ -void LumpedRateModelWithoutPores::assembleDiscretizedJacobian(double alpha, const Indexer& idxr) +template +void LumpedRateModelWithoutPores::assembleDiscretizedJacobian(double alpha, const Indexer& idxr) { // Copy normal matrix over to factorizable matrix _jacDisc.copyOver(_jac); @@ -883,7 +916,8 @@ void LumpedRateModelWithoutPores::assembleDiscretizedJacobian(double alpha, cons * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) * @param [in] invBeta Inverse porosity term @f$\frac{1}{\beta}@f$ */ -void LumpedRateModelWithoutPores::addTimeDerivativeToJacobianCell(linalg::FactorizableBandMatrix::RowIterator& jac, const Indexer& idxr, double alpha, double invBeta) const +template +void LumpedRateModelWithoutPores::addTimeDerivativeToJacobianCell(linalg::FactorizableBandMatrix::RowIterator& jac, const Indexer& idxr, double alpha, double invBeta) const { // Mobile phase for (int comp = 0; comp < static_cast(_disc.nComp); ++comp, ++jac) @@ -915,7 +949,8 @@ void LumpedRateModelWithoutPores::addTimeDerivativeToJacobianCell(linalg::Factor } } -void LumpedRateModelWithoutPores::applyInitialCondition(const SimulationState& simState) const +template +void LumpedRateModelWithoutPores::applyInitialCondition(const SimulationState& simState) const { Indexer idxr(_disc); @@ -953,7 +988,8 @@ void LumpedRateModelWithoutPores::applyInitialCondition(const SimulationState& s } } -void LumpedRateModelWithoutPores::readInitialCondition(IParameterProvider& paramProvider) +template +void LumpedRateModelWithoutPores::readInitialCondition(IParameterProvider& paramProvider) { _initState.clear(); _initStateDot.clear(); @@ -1017,7 +1053,8 @@ void LumpedRateModelWithoutPores::readInitialCondition(IParameterProvider& param * @param [in] errorTol Error tolerance for algebraic equations * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void LumpedRateModelWithoutPores::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithoutPores::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1067,7 +1104,7 @@ void LumpedRateModelWithoutPores::consistentInitialState(const SimulationTime& s linalg::DenseMatrixView fullJacobianMatrix(_jacDisc.data() + col * _disc.strideBound * _disc.strideBound, nullptr, mask.len, mask.len); // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = (0.5 + static_cast(col)) / static_cast(_disc.nCol); + const double z = _convDispOp.relativeCoordinate(col); // Get workspace memory BufferedArray nonlinMemBuffer = tlmAlloc.array(_nonlinearSolver->workspaceSize(probSize)); @@ -1332,7 +1369,8 @@ void LumpedRateModelWithoutPores::consistentInitialState(const SimulationTime& s * @param [in] vecStateY Consistently initialized state vector * @param [in,out] vecStateYdot On entry, residual without taking time derivatives into account. On exit, consistent state time derivatives. */ -void LumpedRateModelWithoutPores::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithoutPores::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1365,7 +1403,7 @@ void LumpedRateModelWithoutPores::consistentInitialTimeDerivative(const Simulati continue; // Midpoint of current column cell (z coordinate) - needed in externally dependent adsorption kinetic - const double z = 1.0 / static_cast(_disc.nCol) * (0.5 + col); + const double z = _convDispOp.relativeCoordinate(col); // Get iterators to beginning of solid phase linalg::BandMatrix::RowIterator jacSolidOrig = _jac.row(idxr.strideColCell() * col + idxr.strideColLiquid()); @@ -1438,7 +1476,8 @@ void LumpedRateModelWithoutPores::consistentInitialTimeDerivative(const Simulati * @param [in,out] adJac Jacobian information for AD (AD vectors for residual and state, direction offset) * @param [in] errorTol Error tolerance for algebraic equations */ -void LumpedRateModelWithoutPores::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithoutPores::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) { } @@ -1468,7 +1507,8 @@ void LumpedRateModelWithoutPores::leanConsistentInitialState(const SimulationTim * @param [in,out] vecStateYdot On entry, inconsistent state time derivatives. On exit, partially consistent state time derivatives. * @param [in] res On entry, residual without taking time derivatives into account. The data is overwritten during execution of the function. */ -void LumpedRateModelWithoutPores::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) +template +void LumpedRateModelWithoutPores::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1496,7 +1536,8 @@ void LumpedRateModelWithoutPores::leanConsistentInitialTimeDerivative(double t, } } -void LumpedRateModelWithoutPores::initializeSensitivityStates(const std::vector& vecSensY) const +template +void LumpedRateModelWithoutPores::initializeSensitivityStates(const std::vector& vecSensY) const { Indexer idxr(_disc); for (std::size_t param = 0; param < vecSensY.size(); ++param) @@ -1553,7 +1594,8 @@ void LumpedRateModelWithoutPores::initializeSensitivityStates(const std::vector< * @param [in] adRes Pointer to residual vector of AD datatypes with parameter sensitivities * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void LumpedRateModelWithoutPores::consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, +template +void LumpedRateModelWithoutPores::consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1725,7 +1767,8 @@ void LumpedRateModelWithoutPores::consistentInitialSensitivity(const SimulationT * @param [in] adRes Pointer to residual vector of AD datatypes with parameter sensitivities * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) */ -void LumpedRateModelWithoutPores::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, +template +void LumpedRateModelWithoutPores::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) { BENCH_SCOPE(_timerConsistentInit); @@ -1773,7 +1816,8 @@ void LumpedRateModelWithoutPores::leanConsistentInitialSensitivity(const Simulat } } -bool LumpedRateModelWithoutPores::setParameter(const ParameterId& pId, double value) +template +bool LumpedRateModelWithoutPores::setParameter(const ParameterId& pId, double value) { if (_convDispOp.setParameter(pId, value)) return true; @@ -1781,7 +1825,8 @@ bool LumpedRateModelWithoutPores::setParameter(const ParameterId& pId, double va return UnitOperationBase::setParameter(pId, value); } -void LumpedRateModelWithoutPores::setSensitiveParameterValue(const ParameterId& pId, double value) +template +void LumpedRateModelWithoutPores::setSensitiveParameterValue(const ParameterId& pId, double value) { if (_convDispOp.setSensitiveParameterValue(_sensParams, pId, value)) return; @@ -1789,7 +1834,8 @@ void LumpedRateModelWithoutPores::setSensitiveParameterValue(const ParameterId& UnitOperationBase::setSensitiveParameterValue(pId, value); } -bool LumpedRateModelWithoutPores::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) +template +bool LumpedRateModelWithoutPores::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) { if (_convDispOp.setSensitiveParameter(_sensParams, pId, adDirection, adValue)) { @@ -1801,7 +1847,8 @@ bool LumpedRateModelWithoutPores::setSensitiveParameter(const ParameterId& pId, } -int LumpedRateModelWithoutPores::Exporter::writeMobilePhase(double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeMobilePhase(double* buffer) const { const int stride = _idx.strideColCell(); double const* ptr = _data + _idx.offsetC(); @@ -1814,7 +1861,8 @@ int LumpedRateModelWithoutPores::Exporter::writeMobilePhase(double* buffer) cons return _disc.nCol * _disc.nComp; } -int LumpedRateModelWithoutPores::Exporter::writeSolidPhase(double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeSolidPhase(double* buffer) const { const int stride = _idx.strideColCell(); double const* ptr = _data + _idx.offsetC() + _idx.strideColLiquid(); @@ -1827,30 +1875,34 @@ int LumpedRateModelWithoutPores::Exporter::writeSolidPhase(double* buffer) const return _disc.nCol * _disc.strideBound; } -int LumpedRateModelWithoutPores::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const { cadet_assert(parType == 0); return writeSolidPhase(buffer); } -int LumpedRateModelWithoutPores::Exporter::writeInlet(unsigned int port, double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeInlet(unsigned int port, double* buffer) const { cadet_assert(port == 0); std::copy_n(_data, _disc.nComp, buffer); return _disc.nComp; } -int LumpedRateModelWithoutPores::Exporter::writeInlet(double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeInlet(double* buffer) const { std::copy_n(_data, _disc.nComp, buffer); return _disc.nComp; } -int LumpedRateModelWithoutPores::Exporter::writeOutlet(unsigned int port, double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeOutlet(unsigned int port, double* buffer) const { cadet_assert(port == 0); - if (_model._convDispOp.currentVelocity() >= 0) + if (_model._convDispOp.forwardFlow()) std::copy_n(&_idx.c(_data, _disc.nCol - 1, 0), _disc.nComp, buffer); else std::copy_n(&_idx.c(_data, 0, 0), _disc.nComp, buffer); @@ -1858,9 +1910,10 @@ int LumpedRateModelWithoutPores::Exporter::writeOutlet(unsigned int port, double return _disc.nComp; } -int LumpedRateModelWithoutPores::Exporter::writeOutlet(double* buffer) const +template +int LumpedRateModelWithoutPores::Exporter::writeOutlet(double* buffer) const { - if (_model._convDispOp.currentVelocity() >= 0) + if (_model._convDispOp.forwardFlow()) std::copy_n(&_idx.c(_data, _disc.nCol - 1, 0), _disc.nComp, buffer); else std::copy_n(&_idx.c(_data, 0, 0), _disc.nComp, buffer); @@ -1871,9 +1924,15 @@ int LumpedRateModelWithoutPores::Exporter::writeOutlet(double* buffer) const void registerLumpedRateModelWithoutPores(std::unordered_map>& models) { - models[LumpedRateModelWithoutPores::identifier()] = [](UnitOpIdx uoId) { return new LumpedRateModelWithoutPores(uoId); }; - models["LRM"] = [](UnitOpIdx uoId) { return new LumpedRateModelWithoutPores(uoId); }; - models["DPFR"] = [](UnitOpIdx uoId) { return new LumpedRateModelWithoutPores(uoId); }; + typedef LumpedRateModelWithoutPores AxialLRM; + typedef LumpedRateModelWithoutPores RadialLRM; + + models[AxialLRM::identifier()] = [](UnitOpIdx uoId) { return new AxialLRM(uoId); }; + models["LRM"] = [](UnitOpIdx uoId) { return new AxialLRM(uoId); }; + models["DPFR"] = [](UnitOpIdx uoId) { return new AxialLRM(uoId); }; + + models[RadialLRM::identifier()] = [](UnitOpIdx uoId) { return new RadialLRM(uoId); }; + models["RLRM"] = [](UnitOpIdx uoId) { return new RadialLRM(uoId); }; } } // namespace model diff --git a/src/libcadet/model/LumpedRateModelWithoutPores.hpp b/src/libcadet/model/LumpedRateModelWithoutPores.hpp index 73761e7cf..c4423fcae 100644 --- a/src/libcadet/model/LumpedRateModelWithoutPores.hpp +++ b/src/libcadet/model/LumpedRateModelWithoutPores.hpp @@ -33,6 +33,24 @@ #include "Benchmark.hpp" +namespace +{ + template + struct LumpedRateModelWithoutPoresName { }; + + template <> + struct LumpedRateModelWithoutPoresName + { + static const char* identifier() CADET_NOEXCEPT { return "LUMPED_RATE_MODEL_WITHOUT_PORES"; } + }; + + template <> + struct LumpedRateModelWithoutPoresName + { + static const char* identifier() CADET_NOEXCEPT { return "RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES"; } + }; +} + namespace cadet { @@ -54,6 +72,7 @@ u c_{\text{in},i}(t) &= u c_i(t,0) - D_{\text{ax},i} \frac{\partial c_i}{\partia \end{align} @f] * Methods are described in @cite VonLieres2010a (WENO, linear solver), @cite Puttmann2013 @cite Puttmann2016 (forward sensitivities, AD, band compression) */ +template class LumpedRateModelWithoutPores : public UnitOperationBase { public: @@ -73,10 +92,10 @@ class LumpedRateModelWithoutPores : public UnitOperationBase virtual unsigned int numOutletPorts() const CADET_NOEXCEPT { return 1; } virtual bool canAccumulate() const CADET_NOEXCEPT { return false; } - static const char* identifier() { return "LUMPED_RATE_MODEL_WITHOUT_PORES"; } + static const char* identifier() CADET_NOEXCEPT { return LumpedRateModelWithoutPoresName::identifier(); } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); @@ -204,7 +223,7 @@ class LumpedRateModelWithoutPores : public UnitOperationBase Discretization _disc; //!< Discretization info // IExternalFunction* _extFun; //!< External function (owned by library user) - parts::ConvectionDispersionOperatorBase _convDispOp; //!< Convection dispersion operator for interstitial volume transport + ConvDispOperator _convDispOp; //!< Convection dispersion operator for interstitial volume transport linalg::BandMatrix _jac; //!< Jacobian linalg::FactorizableBandMatrix _jacDisc; //!< Jacobian with time derivatives from BDF method @@ -306,30 +325,11 @@ class LumpedRateModelWithoutPores : public UnitOperationBase virtual int writeOutlet(unsigned int port, double* buffer) const; virtual int writeOutlet(double* buffer) const; - virtual double const* concentration() const { return _idx.c(_data); } - virtual double const* flux() const { return nullptr; } - virtual double const* particleMobilePhase(unsigned int parType) const { return nullptr; } - virtual double const* solidPhase(unsigned int parType) const { return _idx.q(_data); } - virtual double const* volume() const { return nullptr; } - virtual double const* inlet(unsigned int port, unsigned int& stride) const - { - stride = _idx.strideColComp(); - return _data; - } - virtual double const* outlet(unsigned int port, unsigned int& stride) const - { - stride = _idx.strideColComp(); - if (_model._convDispOp.currentVelocity() >= 0) - return &_idx.c(_data, _disc.nCol - 1, 0); - else - return &_idx.c(_data, 0, 0); - } virtual int writePrimaryCoordinates(double* coords) const { - const double h = static_cast(_model._convDispOp.columnLength()) / static_cast(_disc.nCol); for (unsigned int i = 0; i < _disc.nCol; ++i) - coords[i] = (i + 0.5) * h; + coords[i] = _model._convDispOp.cellCenter(i); return _disc.nCol; } virtual int writeSecondaryCoordinates(double* coords) const { return 0; } diff --git a/src/libcadet/model/LumpedRateModelWithoutPoresDG.cpp b/src/libcadet/model/LumpedRateModelWithoutPoresDG.cpp index 78e7fc70b..00c291aad 100644 --- a/src/libcadet/model/LumpedRateModelWithoutPoresDG.cpp +++ b/src/libcadet/model/LumpedRateModelWithoutPoresDG.cpp @@ -90,7 +90,7 @@ namespace cadet #endif } - bool LumpedRateModelWithoutPoresDG::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) + bool LumpedRateModelWithoutPoresDG::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // Read discretization _disc.nComp = paramProvider.getInt("NCOMP"); @@ -143,9 +143,7 @@ namespace cadet paramProvider.popScope(); - const unsigned int strideCell = _disc.nNodes; - - const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, _disc.nComp, _disc.nCol, strideCell); + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, _disc.nPoints, 0); // strideCell not needed for DG, so just set to zero _disc.dispersion = Eigen::VectorXd::Zero(_disc.nComp); // fill later on with convDispOp (section and component dependent) diff --git a/src/libcadet/model/LumpedRateModelWithoutPoresDG.hpp b/src/libcadet/model/LumpedRateModelWithoutPoresDG.hpp index 5a76428bd..b3417aba9 100644 --- a/src/libcadet/model/LumpedRateModelWithoutPoresDG.hpp +++ b/src/libcadet/model/LumpedRateModelWithoutPoresDG.hpp @@ -84,7 +84,7 @@ namespace cadet static const char* identifier() { return "LUMPED_RATE_MODEL_WITHOUT_PORES_DG"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); @@ -670,7 +670,7 @@ namespace cadet // IExternalFunction* _extFun; //!< External function (owned by library user) // used as auxiliary supplier - parts::ConvectionDispersionOperatorBase _convDispOp; //!< Convection dispersion operator for interstitial volume transport + parts::AxialConvectionDispersionOperatorBase _convDispOp; //!< Convection dispersion operator for interstitial volume transport // linear solver (Eigen lib) Eigen::SparseLU> _linSolver; diff --git a/src/libcadet/model/ModelSystemImpl.cpp b/src/libcadet/model/ModelSystemImpl.cpp index 973de421d..548b18be7 100644 --- a/src/libcadet/model/ModelSystemImpl.cpp +++ b/src/libcadet/model/ModelSystemImpl.cpp @@ -444,7 +444,7 @@ void ModelSystem::allocateSuperStructMatrices() } } -bool ModelSystem::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool ModelSystem::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { // Discretizations of unit operation models are already configured rebuildInternalDataStructures(); diff --git a/src/libcadet/model/ModelSystemImpl.hpp b/src/libcadet/model/ModelSystemImpl.hpp index 10c7b8989..14cdb7427 100644 --- a/src/libcadet/model/ModelSystemImpl.hpp +++ b/src/libcadet/model/ModelSystemImpl.hpp @@ -82,7 +82,7 @@ class ModelSystem : public ISimulatableModel virtual bool usesAD() const CADET_NOEXCEPT; virtual unsigned int requiredADdirs() const CADET_NOEXCEPT; - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); virtual bool configureModel(IParameterProvider& paramProvider, unsigned int unitOpIdx); diff --git a/src/libcadet/model/OutletModel.cpp b/src/libcadet/model/OutletModel.cpp index b486e313d..7354148cf 100644 --- a/src/libcadet/model/OutletModel.cpp +++ b/src/libcadet/model/OutletModel.cpp @@ -64,7 +64,7 @@ bool OutletModel::usesAD() const CADET_NOEXCEPT return false; } -bool OutletModel::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool OutletModel::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { _nComp = paramProvider.getInt("NCOMP"); return true; diff --git a/src/libcadet/model/OutletModel.hpp b/src/libcadet/model/OutletModel.hpp index 828fa1620..00a2fea2c 100644 --- a/src/libcadet/model/OutletModel.hpp +++ b/src/libcadet/model/OutletModel.hpp @@ -63,7 +63,7 @@ class OutletModel : public IUnitOperation static const char* identifier() { return "OUTLET"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); diff --git a/src/libcadet/model/ParameterDependence.hpp b/src/libcadet/model/ParameterDependence.hpp index 14d934ee4..a463ca02a 100644 --- a/src/libcadet/model/ParameterDependence.hpp +++ b/src/libcadet/model/ParameterDependence.hpp @@ -33,23 +33,24 @@ namespace cadet class IParameterProvider; struct ColumnPosition; +class IModel; namespace model { /** - * @brief Defines a parameter dependence relation + * @brief Defines how a parameter depends on a state * @details Represents the dependence of a parameter on the state (i.e., concentrations * inside a cell). Scalar as well as vector-valued parameters are considered. * Both simple and extended cells are supported. A simple cell contains only * liquid phase components. An extended cell contains liquid and solid phase * components. */ -class IParameterDependence +class IParameterStateDependence { public: - virtual ~IParameterDependence() CADET_NOEXCEPT { } + virtual ~IParameterStateDependence() CADET_NOEXCEPT { } /** * @brief Returns the name of the parameter dependence @@ -60,7 +61,7 @@ class IParameterDependence /** * @brief Returns whether the parameter dependence requires additional parameters supplied by configure() - * @details After construction of an IParameterDependence object, configureModelDiscretization() is called. + * @details After construction of an IParameterStateDependence object, configureModelDiscretization() is called. * The parameter dependence may require to read additional parameters from the reaction group * of the parameter provider. This opportunity is given by a call to configure(). However, a * parameter dependence may not require this. This function communicates to the outside @@ -352,6 +353,161 @@ class IParameterDependence protected: }; +/** + * @brief Defines how a parameter depends on another parameter + * @details Represents the dependence of a parameter on another parameter. + */ +class IParameterParameterDependence +{ +public: + + virtual ~IParameterParameterDependence() CADET_NOEXCEPT { } + + /** + * @brief Returns the name of the parameter dependence + * @details This name is also used to identify and create the dependence in the factory. + * @return Name of the parameter dependence + */ + virtual const char* name() const CADET_NOEXCEPT = 0; + + /** + * @brief Returns whether the parameter dependence requires additional parameters supplied by configure() + * @details After construction of an IParameterParameterDependence object, configureModelDiscretization() is called. + * The parameter dependence may require to read additional parameters from the reaction group + * of the parameter provider. This opportunity is given by a call to configure(). However, a + * parameter dependence may not require this. This function communicates to the outside + * whether a call to configure() is necessary. + * @return @c true if configure() has to be called, otherwise @c false + */ + virtual bool requiresConfiguration() const CADET_NOEXCEPT = 0; + + /** + * @brief Returns whether the parameter dependence uses the IParameterProvider in configureModelDiscretization() + * @details If the IParameterProvider is used in configureModelDiscretization(), it has to be in the correct scope. + * @return @c true if the IParameterProvider is used in configureModelDiscretization(), otherwise @c false + */ + virtual bool usesParamProviderInDiscretizationConfig() const CADET_NOEXCEPT = 0; + + /** + * @brief Configures the parameter dependence + * @details This function is called prior to configure() by the underlying model. + * It can only be called once. Model parameters are configured by configure(). + * + * @param [in] paramProvider Parameter provider + */ + virtual bool configureModelDiscretization(IParameterProvider& paramProvider) = 0; + + /** + * @brief Configures the model by extracting all non-structural parameters (e.g., model parameters) from the given @p paramProvider + * @details The scope of the cadet::IParameterProvider is left unchanged on return. + * + * The structure of the model is left unchanged, that is, the number of degrees of + * freedom stays the same (e.g., number of bound states is left unchanged). Only + * true (non-structural) model parameters are read and changed. + * + * This function may only be called if configureModelDiscretization() has been called + * in the past. Contrary to configureModelDiscretization(), it can be called multiple + * times. + * + * @param [in] paramProvider Parameter provider + * @param [in] unitOpIdx Index of the unit operation this parameter dependence belongs to + * @param [in] parTypeIdx Index of the particle type this parameter dependence belongs to + * @param [in] bndIdx Index of the bound state this parameter dependence belongs to + * @param [in] name Name of the parameter + * @return @c true if the configuration was successful, otherwise @c false + */ + virtual bool configure(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) = 0; + + /** + * @brief Checks whether a given parameter exists + * @param [in] pId ParameterId that identifies the parameter uniquely + * @return @c true if the parameter exists, otherwise @c false + */ + virtual bool hasParameter(const ParameterId& pId) const = 0; + + /** + * @brief Returns all parameters with their current values that can be made sensitive + * @return Map with all parameters that can be made sensitive along with their current value + */ + virtual std::unordered_map getAllParameterValues() const = 0; + + /** + * @brief Sets a parameter value + * @details The parameter identified by its unique parameter is set to the given value. + * + * @param [in] pId ParameterId that identifies the parameter uniquely + * @param [in] value Value of the parameter + * @return @c true if the parameter has been successfully set to the given value, + * otherwise @c false (e.g., if the parameter is not available in this model) + */ + virtual bool setParameter(const ParameterId& pId, int value) = 0; + virtual bool setParameter(const ParameterId& pId, double value) = 0; + virtual bool setParameter(const ParameterId& pId, bool value) = 0; + + /** + * @brief Returns a pointer to the parameter identified by the given id + * @param [in] pId Parameter Id of the sensitive parameter + * @return a pointer to the parameter if the parameter dependence contains the parameter, otherwise @c nullptr + */ + virtual active* getParameter(const ParameterId& pId) = 0; + + /** + * @brief Evaluates the parameter + * @details This function is called simultaneously from multiple threads. + * + * @param [in] model Model that owns this parameter dependence + * @param [in] colPos Position in normalized coordinates (column inlet = 0, column outlet = 1; outer shell = 1, inner center = 0) + * @param [in] comp Index of the component the parameter belongs to (or @c -1 if independent of components) + * @param [in] parType Index of the particle type the parameter belongs to (or @c -1 if independent of particle types) + * @param [in] bnd Index of the bound state the parameter belongs to (or @c -1 if independent of bound states) + * @return Actual parameter value + */ + virtual double getValue(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const = 0; + + /** + * @brief Evaluates the parameter + * @details This function is called simultaneously from multiple threads. + * + * @param [in] model Model that owns this parameter dependence + * @param [in] colPos Position in normalized coordinates (column inlet = 0, column outlet = 1; outer shell = 1, inner center = 0) + * @param [in] comp Index of the component the parameter belongs to (or @c -1 if independent of components) + * @param [in] parType Index of the particle type the parameter belongs to (or @c -1 if independent of particle types) + * @param [in] bnd Index of the bound state the parameter belongs to (or @c -1 if independent of bound states) + * @return Actual parameter value + */ + virtual active getValueActive(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const = 0; + + /** + * @brief Evaluates the parameter + * @details This function is called simultaneously from multiple threads. + * + * @param [in] model Model that owns this parameter dependence + * @param [in] colPos Position in normalized coordinates (column inlet = 0, column outlet = 1; outer shell = 1, inner center = 0) + * @param [in] comp Index of the component the parameter belongs to (or @c -1 if independent of components) + * @param [in] parType Index of the particle type the parameter belongs to (or @c -1 if independent of particle types) + * @param [in] bnd Index of the bound state the parameter belongs to (or @c -1 if independent of bound states) + * @param [in] val Additional parameter-dependent value + * @return Actual parameter value + */ + virtual double getValue(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, double val) const = 0; + + /** + * @brief Evaluates the parameter + * @details This function is called simultaneously from multiple threads. + * + * @param [in] model Model that owns this parameter dependence + * @param [in] colPos Position in normalized coordinates (column inlet = 0, column outlet = 1; outer shell = 1, inner center = 0) + * @param [in] comp Index of the component the parameter belongs to (or @c -1 if independent of components) + * @param [in] parType Index of the particle type the parameter belongs to (or @c -1 if independent of particle types) + * @param [in] bnd Index of the bound state the parameter belongs to (or @c -1 if independent of bound states) + * @param [in] val Additional parameter-dependent value + * @return Actual parameter value + */ + virtual active getValue(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, const active& val) const = 0; + +protected: +}; + } // namespace model } // namespace cadet diff --git a/src/libcadet/model/StirredTankModel.cpp b/src/libcadet/model/StirredTankModel.cpp index 3e6cf83a8..b73ae97b8 100644 --- a/src/libcadet/model/StirredTankModel.cpp +++ b/src/libcadet/model/StirredTankModel.cpp @@ -106,7 +106,7 @@ void CSTRModel::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT _flowRateOut = out[0]; } -bool CSTRModel::configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) +bool CSTRModel::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) { _nComp = paramProvider.getInt("NCOMP"); diff --git a/src/libcadet/model/StirredTankModel.hpp b/src/libcadet/model/StirredTankModel.hpp index cb1f31fab..401c90e6a 100644 --- a/src/libcadet/model/StirredTankModel.hpp +++ b/src/libcadet/model/StirredTankModel.hpp @@ -66,7 +66,7 @@ class CSTRModel : public UnitOperationBase static const char* identifier() { return "CSTR"; } virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper); virtual bool configure(IParameterProvider& paramProvider); virtual void notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac); diff --git a/src/libcadet/model/UnitOperation.hpp b/src/libcadet/model/UnitOperation.hpp index 8b991df74..23a14ffb2 100644 --- a/src/libcadet/model/UnitOperation.hpp +++ b/src/libcadet/model/UnitOperation.hpp @@ -94,7 +94,7 @@ class IUnitOperation : public IModel * @param [in] helper Used to inject or create required objects * @return @c true if the configuration was successful, otherwise @c false */ - virtual bool configureModelDiscretization(IParameterProvider& paramProvider, IConfigHelper& helper) = 0; + virtual bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) = 0; /** * @brief (Re-)configures the model by extracting all non-structural parameters (e.g., model parameters) from the given @p paramProvider diff --git a/src/libcadet/model/paramdep/DummyParameterDependence.cpp b/src/libcadet/model/paramdep/DummyParameterDependence.cpp index c292d9b48..c73c92753 100644 --- a/src/libcadet/model/paramdep/DummyParameterDependence.cpp +++ b/src/libcadet/model/paramdep/DummyParameterDependence.cpp @@ -27,17 +27,17 @@ namespace model { /** - * @brief Defines a dummy parameter dependence + * @brief Defines a parameter dependence that outputs constant 0.0 */ -class DummyParameterDependence : public ParameterDependenceBase +class ConstantZeroParameterStateDependence : public ParameterStateDependenceBase { public: - DummyParameterDependence() { } - virtual ~DummyParameterDependence() CADET_NOEXCEPT { } + ConstantZeroParameterStateDependence() { } + virtual ~ConstantZeroParameterStateDependence() CADET_NOEXCEPT { } - static const char* identifier() { return "DUMMY"; } - virtual const char* name() const CADET_NOEXCEPT { return DummyParameterDependence::identifier(); } + static const char* identifier() { return "CONSTANT_ZERO"; } + virtual const char* name() const CADET_NOEXCEPT { return ConstantZeroParameterStateDependence::identifier(); } virtual int jacobianElementsPerRowLiquid() const CADET_NOEXCEPT { return 0; } virtual int jacobianElementsPerRowCombined() const CADET_NOEXCEPT { return 0; } @@ -46,7 +46,7 @@ class DummyParameterDependence : public ParameterDependenceBase virtual void analyticJacobianCombinedAddLiquid(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int comp, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } virtual void analyticJacobianCombinedAddSolid(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int bnd, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } - CADET_PARAMETERDEPENDENCE_BOILERPLATE + CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE protected: @@ -84,14 +84,154 @@ class DummyParameterDependence : public ParameterDependenceBase }; +/** + * @brief Defines a parameter dependence that outputs constant 1.0 + */ +class ConstantOneParameterStateDependence : public ParameterStateDependenceBase +{ +public: + + ConstantOneParameterStateDependence() { } + virtual ~ConstantOneParameterStateDependence() CADET_NOEXCEPT { } + + static const char* identifier() { return "CONSTANT_ONE"; } + virtual const char* name() const CADET_NOEXCEPT { return ConstantOneParameterStateDependence::identifier(); } + + virtual int jacobianElementsPerRowLiquid() const CADET_NOEXCEPT { return 0; } + virtual int jacobianElementsPerRowCombined() const CADET_NOEXCEPT { return 0; } + + virtual void analyticJacobianLiquidAdd(const ColumnPosition& colPos, double param, double const* y, int comp, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } + virtual void analyticJacobianCombinedAddLiquid(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int comp, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } + virtual void analyticJacobianCombinedAddSolid(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int bnd, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } + + CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE + +protected: + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, const std::string& name) + { + return true; + } + + template + typename DoubleActivePromoter::type liquidParameterImpl(const ColumnPosition& colPos, const ParamType& param, StateType const* y, int comp) const + { + return 1.0; + } + + template + void analyticJacobianLiquidAddImpl(const ColumnPosition& colPos, double param, double const* y, int comp, double factor, int offset, RowIterator jac) const { } + + template + typename DoubleActivePromoter::type combinedParameterLiquidImpl(const ColumnPosition& colPos, const ParamType& param, StateType const* yLiquid, StateType const* ySolid, int comp) const + { + return 1.0; + } + + template + void analyticJacobianCombinedAddLiquidImpl(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int comp, double factor, int offset, RowIterator jac) const { } + + template + typename DoubleActivePromoter::type combinedParameterSolidImpl(const ColumnPosition& colPos, const ParamType& param, StateType const* yLiquid, StateType const* ySolid, int bnd) const + { + return 1.0; + } + + template + void analyticJacobianCombinedAddSolidImpl(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int bnd, double factor, int offset, RowIterator jac) const { } +}; + + +/** + * @brief Defines a parameter dependence that outputs constant 0.0 + */ +class ConstantZeroParameterParameterDependence : public ParameterParameterDependenceBase +{ +public: + + ConstantZeroParameterParameterDependence() { } + virtual ~ConstantZeroParameterParameterDependence() CADET_NOEXCEPT { } + + static const char* identifier() { return "CONSTANT_ZERO"; } + virtual const char* name() const CADET_NOEXCEPT { return ConstantZeroParameterParameterDependence::identifier(); } + + CADET_PARAMETERPARAMETERDEPENDENCE_BOILERPLATE + +protected: + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) + { + return true; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const + { + return 0.0; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, const ParamType& val) const + { + return 0.0; + } + +}; + + +/** + * @brief Defines a parameter dependence that outputs constant 1.0 + */ +class ConstantOneParameterParameterDependence : public ParameterParameterDependenceBase +{ +public: + + ConstantOneParameterParameterDependence() { } + virtual ~ConstantOneParameterParameterDependence() CADET_NOEXCEPT { } + + static const char* identifier() { return "CONSTANT_ONE"; } + virtual const char* name() const CADET_NOEXCEPT { return ConstantOneParameterParameterDependence::identifier(); } + + CADET_PARAMETERPARAMETERDEPENDENCE_BOILERPLATE + +protected: + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) + { + return true; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const + { + return 1.0; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, const ParamType& val) const + { + return 1.0; + } + +}; + + namespace paramdep { - void registerDummyParamDependence(std::unordered_map>& paramDeps) + void registerDummyParamDependence(std::unordered_map>& paramDeps) + { + paramDeps[ConstantOneParameterStateDependence::identifier()] = []() { return new ConstantOneParameterStateDependence(); }; + paramDeps[ConstantZeroParameterStateDependence::identifier()] = []() { return new ConstantZeroParameterStateDependence(); }; + paramDeps["NONE"] = []() { return new ConstantOneParameterStateDependence(); }; + } + + void registerDummyParamDependence(std::unordered_map>& paramDeps) { - paramDeps[DummyParameterDependence::identifier()] = []() { return new DummyParameterDependence(); }; - paramDeps["NONE"] = []() { return new DummyParameterDependence(); }; + paramDeps[ConstantOneParameterParameterDependence::identifier()] = []() { return new ConstantOneParameterParameterDependence(); }; + paramDeps[ConstantZeroParameterParameterDependence::identifier()] = []() { return new ConstantZeroParameterParameterDependence(); }; + paramDeps["NONE"] = []() { return new ConstantOneParameterParameterDependence(); }; } -} // namespace reaction +} // namespace paramdep } // namespace model diff --git a/src/libcadet/model/paramdep/IdentityParameterDependence.cpp b/src/libcadet/model/paramdep/IdentityParameterDependence.cpp new file mode 100644 index 000000000..60092ed6b --- /dev/null +++ b/src/libcadet/model/paramdep/IdentityParameterDependence.cpp @@ -0,0 +1,141 @@ +// ============================================================================= +// CADET +// +// Copyright ┬® 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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/paramdep/ParameterDependenceBase.hpp" +#include "ParamReaderHelper.hpp" +#include "cadet/Exceptions.hpp" +#include "SimulationTypes.hpp" +#include "cadet/ParameterId.hpp" + +#include +#include +#include + +namespace cadet +{ + +namespace model +{ + +/** + * @brief Defines an identity parameter dependence + */ +class IdentityParameterStateDependence : public ParameterStateDependenceBase +{ +public: + + IdentityParameterStateDependence() { } + virtual ~IdentityParameterStateDependence() CADET_NOEXCEPT { } + + static const char* identifier() { return "IDENTITY"; } + virtual const char* name() const CADET_NOEXCEPT { return IdentityParameterStateDependence::identifier(); } + + virtual int jacobianElementsPerRowLiquid() const CADET_NOEXCEPT { return 0; } + virtual int jacobianElementsPerRowCombined() const CADET_NOEXCEPT { return 0; } + + virtual void analyticJacobianLiquidAdd(const ColumnPosition& colPos, double param, double const* y, int comp, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } + virtual void analyticJacobianCombinedAddLiquid(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int comp, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } + virtual void analyticJacobianCombinedAddSolid(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int bnd, double factor, int offset, int row, linalg::DoubleSparseMatrix& jac) const { } + + CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE + +protected: + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, const std::string& name) + { + return true; + } + + template + typename DoubleActivePromoter::type liquidParameterImpl(const ColumnPosition& colPos, const ParamType& param, StateType const* y, int comp) const + { + return param; + } + + template + void analyticJacobianLiquidAddImpl(const ColumnPosition& colPos, double param, double const* y, int comp, double factor, int offset, RowIterator jac) const { } + + template + typename DoubleActivePromoter::type combinedParameterLiquidImpl(const ColumnPosition& colPos, const ParamType& param, StateType const* yLiquid, StateType const* ySolid, int comp) const + { + return param; + } + + template + void analyticJacobianCombinedAddLiquidImpl(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int comp, double factor, int offset, RowIterator jac) const { } + + template + typename DoubleActivePromoter::type combinedParameterSolidImpl(const ColumnPosition& colPos, const ParamType& param, StateType const* yLiquid, StateType const* ySolid, int bnd) const + { + return param; + } + + template + void analyticJacobianCombinedAddSolidImpl(const ColumnPosition& colPos, double param, double const* yLiquid, double const* ySolid, int bnd, double factor, int offset, RowIterator jac) const { } +}; + + +/** + * @brief Defines an identity parameter dependence + */ +class IdentityParameterParameterDependence : public ParameterParameterDependenceBase +{ +public: + + IdentityParameterParameterDependence() { } + virtual ~IdentityParameterParameterDependence() CADET_NOEXCEPT { } + + static const char* identifier() { return "IDENTITY"; } + virtual const char* name() const CADET_NOEXCEPT { return IdentityParameterParameterDependence::identifier(); } + + CADET_PARAMETERPARAMETERDEPENDENCE_BOILERPLATE + +protected: + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) + { + return true; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const + { + return 0.0; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, const ParamType& val) const + { + return val; + } + +}; + + +namespace paramdep +{ + void registerIdentityParamDependence(std::unordered_map>& paramDeps) + { + paramDeps[IdentityParameterStateDependence::identifier()] = []() { return new IdentityParameterStateDependence(); }; + paramDeps["NONE"] = []() { return new IdentityParameterStateDependence(); }; + } + + void registerIdentityParamDependence(std::unordered_map>& paramDeps) + { + paramDeps[IdentityParameterParameterDependence::identifier()] = []() { return new IdentityParameterParameterDependence(); }; + paramDeps["NONE"] = []() { return new IdentityParameterParameterDependence(); }; + } +} // namespace paramdep + +} // namespace model + +} // namespace cadet diff --git a/src/libcadet/model/paramdep/LiquidSaltSolidParameterDependence.cpp b/src/libcadet/model/paramdep/LiquidSaltSolidParameterDependence.cpp index fe12024ad..15bb729cc 100644 --- a/src/libcadet/model/paramdep/LiquidSaltSolidParameterDependence.cpp +++ b/src/libcadet/model/paramdep/LiquidSaltSolidParameterDependence.cpp @@ -71,15 +71,15 @@ namespace /** * @brief Defines an exponential parameter dependence for the solid phase of a combined cell based on the liquid phase salt concentration */ -class ExpLiquidSaltSolidParameterDependence : public ParameterDependenceBase +class ExpLiquidSaltSolidParameterStateDependence : public ParameterStateDependenceBase { public: - ExpLiquidSaltSolidParameterDependence() : _factor(0), _multiplicator(0) { } - virtual ~ExpLiquidSaltSolidParameterDependence() CADET_NOEXCEPT { } + ExpLiquidSaltSolidParameterStateDependence() : _factor(0), _multiplicator(0) { } + virtual ~ExpLiquidSaltSolidParameterStateDependence() CADET_NOEXCEPT { } static const char* identifier() { return "LIQUID_SALT_EXPONENTIAL"; } - virtual const char* name() const CADET_NOEXCEPT { return ExpLiquidSaltSolidParameterDependence::identifier(); } + virtual const char* name() const CADET_NOEXCEPT { return ExpLiquidSaltSolidParameterStateDependence::identifier(); } virtual int jacobianElementsPerRowLiquid() const CADET_NOEXCEPT { return 0; } virtual int jacobianElementsPerRowCombined() const CADET_NOEXCEPT { return 1; } @@ -92,7 +92,7 @@ class ExpLiquidSaltSolidParameterDependence : public ParameterDependenceBase jac(row, offset) += factor * param * static_cast(_factor[bnd]) * exp(yLiquid[0] * static_cast(_multiplicator[bnd])) * static_cast(_multiplicator[bnd]); } - CADET_PARAMETERDEPENDENCE_BOILERPLATE + CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE protected: std::vector _factor; @@ -141,15 +141,15 @@ class ExpLiquidSaltSolidParameterDependence : public ParameterDependenceBase /** * @brief Defines a power law parameter dependence for the solid phase of a combined cell based on the liquid phase salt concentration */ -class PowerLiquidSaltSolidParameterDependence : public ParameterDependenceBase +class PowerLiquidSaltSolidParameterStateDependence : public ParameterStateDependenceBase { public: - PowerLiquidSaltSolidParameterDependence() : _factor(0), _exponent(0) { } - virtual ~PowerLiquidSaltSolidParameterDependence() CADET_NOEXCEPT { } + PowerLiquidSaltSolidParameterStateDependence() : _factor(0), _exponent(0) { } + virtual ~PowerLiquidSaltSolidParameterStateDependence() CADET_NOEXCEPT { } static const char* identifier() { return "LIQUID_SALT_POWER"; } - virtual const char* name() const CADET_NOEXCEPT { return PowerLiquidSaltSolidParameterDependence::identifier(); } + virtual const char* name() const CADET_NOEXCEPT { return PowerLiquidSaltSolidParameterStateDependence::identifier(); } virtual int jacobianElementsPerRowLiquid() const CADET_NOEXCEPT { return 0; } virtual int jacobianElementsPerRowCombined() const CADET_NOEXCEPT { return 1; } @@ -162,7 +162,7 @@ class PowerLiquidSaltSolidParameterDependence : public ParameterDependenceBase jac(row, offset) += factor * param * static_cast(_factor[bnd]) * pow(yLiquid[0], static_cast(_exponent[bnd]) - 1.0) * static_cast(_exponent[bnd]); } - CADET_PARAMETERDEPENDENCE_BOILERPLATE + CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE protected: std::vector _factor; @@ -211,15 +211,15 @@ class PowerLiquidSaltSolidParameterDependence : public ParameterDependenceBase /** * @brief Defines a parameter dependence for the solid phase of a combined cell based on the liquid phase salt concentration using the binding affinity of a colloidal binding model */ -class ColloidalAffinityLiquidSaltSolidParameterDependence : public ParameterDependenceBase +class ColloidalAffinityLiquidSaltSolidParameterStateDependence : public ParameterStateDependenceBase { public: - ColloidalAffinityLiquidSaltSolidParameterDependence() : _lnKeqExp(0), _lnKeqFactor(0), _lnKeqConst(0), _powFactor(0), _powExponent(0), _expFactor(0), _expExponent(0) { } - virtual ~ColloidalAffinityLiquidSaltSolidParameterDependence() CADET_NOEXCEPT { } + ColloidalAffinityLiquidSaltSolidParameterStateDependence() : _lnKeqExp(0), _lnKeqFactor(0), _lnKeqConst(0), _powFactor(0), _powExponent(0), _expFactor(0), _expExponent(0) { } + virtual ~ColloidalAffinityLiquidSaltSolidParameterStateDependence() CADET_NOEXCEPT { } static const char* identifier() { return "LIQUID_SALT_COLLOIDAL_AFFINITY"; } - virtual const char* name() const CADET_NOEXCEPT { return ColloidalAffinityLiquidSaltSolidParameterDependence::identifier(); } + virtual const char* name() const CADET_NOEXCEPT { return ColloidalAffinityLiquidSaltSolidParameterStateDependence::identifier(); } virtual int jacobianElementsPerRowLiquid() const CADET_NOEXCEPT { return 0; } virtual int jacobianElementsPerRowCombined() const CADET_NOEXCEPT { return 1; } @@ -236,7 +236,7 @@ class ColloidalAffinityLiquidSaltSolidParameterDependence : public ParameterDepe jac(row, offset) += factor * param * (dPow_dy + dExp_dy) * dLogKeq_dy; } - CADET_PARAMETERDEPENDENCE_BOILERPLATE + CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE protected: std::vector _lnKeqExp; @@ -301,13 +301,13 @@ class ColloidalAffinityLiquidSaltSolidParameterDependence : public ParameterDepe namespace paramdep { - void registerLiquidSaltSolidParamDependence(std::unordered_map>& paramDeps) + void registerLiquidSaltSolidParamDependence(std::unordered_map>& paramDeps) { - paramDeps[ExpLiquidSaltSolidParameterDependence::identifier()] = []() { return new ExpLiquidSaltSolidParameterDependence(); }; - paramDeps[PowerLiquidSaltSolidParameterDependence::identifier()] = []() { return new PowerLiquidSaltSolidParameterDependence(); }; - paramDeps[ColloidalAffinityLiquidSaltSolidParameterDependence::identifier()] = []() { return new ColloidalAffinityLiquidSaltSolidParameterDependence(); }; + paramDeps[ExpLiquidSaltSolidParameterStateDependence::identifier()] = []() { return new ExpLiquidSaltSolidParameterStateDependence(); }; + paramDeps[PowerLiquidSaltSolidParameterStateDependence::identifier()] = []() { return new PowerLiquidSaltSolidParameterStateDependence(); }; + paramDeps[ColloidalAffinityLiquidSaltSolidParameterStateDependence::identifier()] = []() { return new ColloidalAffinityLiquidSaltSolidParameterStateDependence(); }; } -} // namespace reaction +} // namespace paramdep } // namespace model diff --git a/src/libcadet/model/paramdep/ParameterDependenceBase.cpp b/src/libcadet/model/paramdep/ParameterDependenceBase.cpp index b1640d07c..9a77a2a21 100644 --- a/src/libcadet/model/paramdep/ParameterDependenceBase.cpp +++ b/src/libcadet/model/paramdep/ParameterDependenceBase.cpp @@ -26,12 +26,12 @@ namespace cadet namespace model { -ParameterDependenceBase::ParameterDependenceBase() : _nComp(0), _nBoundStates(nullptr) { } -ParameterDependenceBase::~ParameterDependenceBase() CADET_NOEXCEPT +ParameterStateDependenceBase::ParameterStateDependenceBase() : _nComp(0), _nBoundStates(nullptr) { } +ParameterStateDependenceBase::~ParameterStateDependenceBase() CADET_NOEXCEPT { } -bool ParameterDependenceBase::configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) +bool ParameterStateDependenceBase::configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) { _nComp = nComp; _nBoundStates = nBound; @@ -45,14 +45,14 @@ bool ParameterDependenceBase::configureModelDiscretization(IParameterProvider& p return true; } -bool ParameterDependenceBase::configure(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, const std::string& name) +bool ParameterStateDependenceBase::configure(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, const std::string& name) { // Clear all parameters and reconfigure _parameters.clear(); return configureImpl(paramProvider, unitOpIdx, parTypeIdx, name); } -std::unordered_map ParameterDependenceBase::getAllParameterValues() const +std::unordered_map ParameterStateDependenceBase::getAllParameterValues() const { std::unordered_map data; std::transform(_parameters.begin(), _parameters.end(), std::inserter(data, data.end()), @@ -60,17 +60,17 @@ std::unordered_map ParameterDependenceBase::getAllParameter return data; } -bool ParameterDependenceBase::hasParameter(const ParameterId& pId) const +bool ParameterStateDependenceBase::hasParameter(const ParameterId& pId) const { return _parameters.find(pId) != _parameters.end(); } -bool ParameterDependenceBase::setParameter(const ParameterId& pId, int value) +bool ParameterStateDependenceBase::setParameter(const ParameterId& pId, int value) { return false; } -bool ParameterDependenceBase::setParameter(const ParameterId& pId, double value) +bool ParameterStateDependenceBase::setParameter(const ParameterId& pId, double value) { auto paramHandle = _parameters.find(pId); if (paramHandle != _parameters.end()) @@ -82,12 +82,76 @@ bool ParameterDependenceBase::setParameter(const ParameterId& pId, double value) return false; } -bool ParameterDependenceBase::setParameter(const ParameterId& pId, bool value) +bool ParameterStateDependenceBase::setParameter(const ParameterId& pId, bool value) { return false; } -active* ParameterDependenceBase::getParameter(const ParameterId& pId) +active* ParameterStateDependenceBase::getParameter(const ParameterId& pId) +{ + auto paramHandle = _parameters.find(pId); + if (paramHandle != _parameters.end()) + { + return paramHandle->second; + } + + return nullptr; +} + + +ParameterParameterDependenceBase::ParameterParameterDependenceBase() { } +ParameterParameterDependenceBase::~ParameterParameterDependenceBase() CADET_NOEXCEPT +{ +} + +bool ParameterParameterDependenceBase::configureModelDiscretization(IParameterProvider& paramProvider) +{ + return true; +} + +bool ParameterParameterDependenceBase::configure(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) +{ + // Clear all parameters and reconfigure + _parameters.clear(); + return configureImpl(paramProvider, unitOpIdx, parTypeIdx, bndIdx, name); +} + +std::unordered_map ParameterParameterDependenceBase::getAllParameterValues() const +{ + std::unordered_map data; + std::transform(_parameters.begin(), _parameters.end(), std::inserter(data, data.end()), + [](const std::pair& p) { return std::make_pair(p.first, static_cast(*p.second)); }); + return data; +} + +bool ParameterParameterDependenceBase::hasParameter(const ParameterId& pId) const +{ + return _parameters.find(pId) != _parameters.end(); +} + +bool ParameterParameterDependenceBase::setParameter(const ParameterId& pId, int value) +{ + return false; +} + +bool ParameterParameterDependenceBase::setParameter(const ParameterId& pId, double value) +{ + auto paramHandle = _parameters.find(pId); + if (paramHandle != _parameters.end()) + { + paramHandle->second->setValue(value); + return true; + } + + return false; +} + +bool ParameterParameterDependenceBase::setParameter(const ParameterId& pId, bool value) +{ + return false; +} + +active* ParameterParameterDependenceBase::getParameter(const ParameterId& pId) { auto paramHandle = _parameters.find(pId); if (paramHandle != _parameters.end()) diff --git a/src/libcadet/model/paramdep/ParameterDependenceBase.hpp b/src/libcadet/model/paramdep/ParameterDependenceBase.hpp index 7235968e9..1f72db332 100644 --- a/src/libcadet/model/paramdep/ParameterDependenceBase.hpp +++ b/src/libcadet/model/paramdep/ParameterDependenceBase.hpp @@ -12,7 +12,7 @@ /** * @file - * Defines an IParameterDependence base class. + * Defines an IParameterStateDependence base class. */ #ifndef LIBCADET_PARAMETERDEPENDENCEBASE_HPP_ @@ -30,17 +30,17 @@ namespace model { /** - * @brief Defines a ParameterDependence base class that can be used to implement other parameter dependences + * @brief Defines a ParameterStateDependence base class that can be used to implement other parameter dependences * @details This base class can be used as a starting point for new parameter dependences. * Some common parameter handling is provided using a hash map (std::unordered_map). */ -class ParameterDependenceBase : public IParameterDependence +class ParameterStateDependenceBase : public IParameterStateDependence { public: - ParameterDependenceBase(); + ParameterStateDependenceBase(); - virtual ~ParameterDependenceBase() CADET_NOEXCEPT; + virtual ~ParameterStateDependenceBase() CADET_NOEXCEPT; virtual bool requiresConfiguration() const CADET_NOEXCEPT { return true; } virtual bool usesParamProviderInDiscretizationConfig() const CADET_NOEXCEPT { return true; } @@ -81,7 +81,7 @@ class ParameterDependenceBase : public IParameterDependence /** * @brief Inserts implementations of all parameter() and analyticJacobian() method variants - * @details An IParameterDependence implementation has to provide liquidParameter(), combinedParameterLiquid(), + * @details An IParameterStateDependence implementation has to provide liquidParameter(), combinedParameterLiquid(), * combinedParameterSolid(), analyticJacobianLiquidAdd(), analyticJacobianCombinedAddLiquid(), and * analyticJacobianCombinedAddSolid() methods for different variants of state and parameter type. * This macro saves some time by providing those implementations. It assumes that the implementation @@ -91,7 +91,7 @@ class ParameterDependenceBase : public IParameterDependence * * The implementation is inserted inline in the class declaration. */ -#define CADET_PARAMETERDEPENDENCE_BOILERPLATE \ +#define CADET_PARAMETERSTATEDEPENDENCE_BOILERPLATE \ virtual active liquidParameter(const ColumnPosition& colPos, const active& param, active const* y, int comp) const \ { \ return liquidParameterImpl(colPos, param, y, comp); \ @@ -182,6 +182,87 @@ class ParameterDependenceBase : public IParameterDependence analyticJacobianCombinedAddSolidImpl(colPos, param, yLiquid, ySolid, bnd, factor, offset, jac); \ } + + +/** + * @brief Defines a ParameterParameterDependence base class that can be used to implement other parameter dependences + * @details This base class can be used as a starting point for new parameter dependences. + * Some common parameter handling is provided using a hash map (std::unordered_map). + */ +class ParameterParameterDependenceBase : public IParameterParameterDependence +{ +public: + + ParameterParameterDependenceBase(); + + virtual ~ParameterParameterDependenceBase() CADET_NOEXCEPT; + + virtual bool requiresConfiguration() const CADET_NOEXCEPT { return true; } + virtual bool usesParamProviderInDiscretizationConfig() const CADET_NOEXCEPT { return true; } + virtual bool configure(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name); + virtual bool configureModelDiscretization(IParameterProvider& paramProvider); + + virtual std::unordered_map getAllParameterValues() const; + virtual bool hasParameter(const ParameterId& pId) const; + + virtual bool setParameter(const ParameterId& pId, int value); + virtual bool setParameter(const ParameterId& pId, double value); + virtual bool setParameter(const ParameterId& pId, bool value); + + virtual active* getParameter(const ParameterId& pId); + +protected: + + std::unordered_map _parameters; //!< Map used to translate ParameterIds to actual variables + + /** + * @brief Configures the reaction model + * @details This function implements the (re-)configuration of a reaction model. It is called when + * the reaction model is configured or reconfigured. On call the _parameters map will always + * be empty. + * @param [in] paramProvider Parameter provider + * @param [in] unitOpIdx Unit operation index + * @param [in] parTypeIdx Particle type index + * @param [in] bndIdx Bound state index + * @param [in] name Name of the parameter + * @return @c true if the configuration was successful, otherwise @c false + */ + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) = 0; +}; + + + +/** + * @brief Inserts implementations of all getValue() method variants + * @details An IParameterStateDependence implementation has to provide getValue(), and getValueActive() + * methods for different variants of state and parameter type. + * This macro saves some time by providing those implementations. It assumes that the implementation + * provides templatized getValue() functions that realize all required variants. + * + * The implementation is inserted inline in the class declaration. + */ +#define CADET_PARAMETERPARAMETERDEPENDENCE_BOILERPLATE \ + virtual double getValue(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, double val) const \ + { \ + return getValueImpl(model, colPos, comp, parType, bnd, val); \ + } \ + \ + virtual active getValue(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, const active& val) const \ + { \ + return getValueImpl(model, colPos, comp, parType, bnd, val); \ + } \ + \ + virtual double getValue(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const \ + { \ + return getValueImpl(model, colPos, comp, parType, bnd); \ + } \ + \ + virtual active getValueActive(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const \ + { \ + return getValueImpl(model, colPos, comp, parType, bnd); \ + } + + } // namespace model } // namespace cadet diff --git a/src/libcadet/model/paramdep/PowerLawParameterDependence.cpp b/src/libcadet/model/paramdep/PowerLawParameterDependence.cpp new file mode 100644 index 000000000..99d9e0d2e --- /dev/null +++ b/src/libcadet/model/paramdep/PowerLawParameterDependence.cpp @@ -0,0 +1,104 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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/paramdep/ParameterDependenceBase.hpp" +#include "SimulationTypes.hpp" +#include "cadet/ParameterId.hpp" + +#include + +namespace cadet +{ +namespace model +{ + +/** + * @brief Defines a power law parameter parameter dependence + * @details The parameter dependence is defined as + * @f[\begin{align} + p_{new}(p) = \alpha p^k, + \end{align} @f] + where @f$ \alpha @f$ and @f$ k @f$ are (meta-)parameters. + */ +class PowerLawParameterParameterDependence : public ParameterParameterDependenceBase +{ +public: + + PowerLawParameterParameterDependence() { } + virtual ~PowerLawParameterParameterDependence() CADET_NOEXCEPT { } + + static const char* identifier() { return "POWER_LAW"; } + virtual const char* name() const CADET_NOEXCEPT { return PowerLawParameterParameterDependence::identifier(); } + + CADET_PARAMETERPARAMETERDEPENDENCE_BOILERPLATE + +protected: + active _base; + active _exponent; + bool _useAbs; + + virtual bool configureImpl(IParameterProvider& paramProvider, UnitOpIdx unitOpIdx, ParticleTypeIdx parTypeIdx, BoundStateIdx bndIdx, const std::string& name) + { + const std::string baseName = name + "_BASE"; + const std::string expName = name + "_EXPONENT"; + const std::string flagName = name + "_ABS"; + + if (paramProvider.exists(baseName)) + _base = paramProvider.getDouble(baseName); + else + _base = 1.0; + + _exponent = paramProvider.getDouble(expName); + + if (paramProvider.exists(flagName)) + _useAbs = paramProvider.getBool(flagName); + else + _useAbs = true; + + _parameters[makeParamId(hashStringRuntime(baseName), unitOpIdx, CompIndep, parTypeIdx, bndIdx, ReactionIndep, SectionIndep)] = &_base; + _parameters[makeParamId(hashStringRuntime(expName), unitOpIdx, CompIndep, parTypeIdx, bndIdx, ReactionIndep, SectionIndep)] = &_exponent; + + return true; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd) const + { + return 0.0; + } + + template + ParamType getValueImpl(const IModel& model, const ColumnPosition& colPos, int comp, int parType, int bnd, ParamType val) const + { + using std::pow; + using std::abs; + + if (_useAbs) + return static_cast(_base) * pow(abs(val), static_cast(_exponent)); + else + return static_cast(_base) * pow(val, static_cast(_exponent)); + } + +}; + + +namespace paramdep +{ + void registerPowerLawParamDependence(std::unordered_map>& paramDeps) + { + paramDeps[PowerLawParameterParameterDependence::identifier()] = []() { return new PowerLawParameterParameterDependence(); }; + } +} // namespace paramdep + +} // namespace model + +} // namespace cadet diff --git a/src/libcadet/model/parts/ConvectionDispersionKernel.cpp b/src/libcadet/model/parts/AxialConvectionDispersionKernel.cpp similarity index 93% rename from src/libcadet/model/parts/ConvectionDispersionKernel.cpp rename to src/libcadet/model/parts/AxialConvectionDispersionKernel.cpp index ac5092e62..1915b3e10 100644 --- a/src/libcadet/model/parts/ConvectionDispersionKernel.cpp +++ b/src/libcadet/model/parts/AxialConvectionDispersionKernel.cpp @@ -12,10 +12,10 @@ /** * @file - * Implements the kernel of the convection dispersion transport operator. + * Implements the kernel of the axial convection dispersion transport operator. */ -#include "model/parts/ConvectionDispersionKernel.hpp" +#include "model/parts/AxialConvectionDispersionKernel.hpp" #include "Weno.hpp" #include "linalg/CompressedSparseMatrix.hpp" @@ -43,7 +43,7 @@ namespace impl } // namespace impl -void sparsityPattern(linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, Weno& weno) +void sparsityPatternAxial(linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, Weno& weno) { impl::DummyStencil stencil; diff --git a/src/libcadet/model/parts/ConvectionDispersionKernel.hpp b/src/libcadet/model/parts/AxialConvectionDispersionKernel.hpp similarity index 77% rename from src/libcadet/model/parts/ConvectionDispersionKernel.hpp rename to src/libcadet/model/parts/AxialConvectionDispersionKernel.hpp index 5e839d76b..49d9fb31b 100644 --- a/src/libcadet/model/parts/ConvectionDispersionKernel.hpp +++ b/src/libcadet/model/parts/AxialConvectionDispersionKernel.hpp @@ -12,11 +12,11 @@ /** * @file - * Implements the kernel of the convection dispersion transport operator. + * Implements the kernel of the axial convection dispersion transport operator. */ -#ifndef LIBCADET_CONVECTIONDISPERSIONKERNEL_HPP_ -#define LIBCADET_CONVECTIONDISPERSIONKERNEL_HPP_ +#ifndef LIBCADET_AXIALCONVECTIONDISPERSIONKERNEL_HPP_ +#define LIBCADET_AXIALCONVECTIONDISPERSIONKERNEL_HPP_ #include "AutoDiff.hpp" #include "Memory.hpp" @@ -24,6 +24,8 @@ #include "Stencil.hpp" #include "linalg/CompressedSparseMatrix.hpp" #include "SimulationTypes.hpp" +#include "model/ParameterDependence.hpp" +#include "model/UnitOperation.hpp" namespace cadet { @@ -38,7 +40,7 @@ namespace convdisp { template -struct FlowParameters +struct AxialFlowParameters { T u; active const* d_ax; @@ -52,13 +54,15 @@ struct FlowParameters unsigned int nCol; unsigned int offsetToInlet; //!< Offset to the first component of the inlet DOFs in the local state vector unsigned int offsetToBulk; //!< Offset to the first component of the first bulk cell in the local state vector + IParameterParameterDependence* parDep; + const IModel& model; }; namespace impl { template - int residualForwardsFlow(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const FlowParameters& p) + int residualForwardsAxialFlow(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const AxialFlowParameters& p) { const ParamType h2 = p.h * p.h; @@ -119,24 +123,28 @@ namespace impl // Right side, leave out if we're in the last cell (boundary condition) if (cadet_likely(col < p.nCol - 1)) { - resBulkComp[col * p.strideCell] -= d_ax / h2 * (stencil[1] - stencil[0]); + const double relCoord = static_cast(col+1) / p.nCol; + const ParamType d_ax_right = d_ax * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u)); + resBulkComp[col * p.strideCell] -= d_ax_right / h2 * (stencil[1] - stencil[0]); // Jacobian entries if (wantJac) { - jac[0] += static_cast(d_ax) / static_cast(h2); - jac[p.strideCell] -= static_cast(d_ax) / static_cast(h2); + jac[0] += static_cast(d_ax_right) / static_cast(h2); + jac[p.strideCell] -= static_cast(d_ax_right) / static_cast(h2); } } // Left side, leave out if we're in the first cell (boundary condition) if (cadet_likely(col > 0)) { - resBulkComp[col * p.strideCell] -= d_ax / h2 * (stencil[-1] - stencil[0]); + const double relCoord = static_cast(col) / p.nCol; + const ParamType d_ax_left = d_ax * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u)); + resBulkComp[col * p.strideCell] -= d_ax_left / h2 * (stencil[-1] - stencil[0]); // Jacobian entries if (wantJac) { - jac[0] += static_cast(d_ax) / static_cast(h2); - jac[-p.strideCell] -= static_cast(d_ax) / static_cast(h2); + jac[0] += static_cast(d_ax_left) / static_cast(h2); + jac[-p.strideCell] -= static_cast(d_ax_left) / static_cast(h2); } } @@ -200,7 +208,7 @@ namespace impl } template - int residualBackwardsFlow(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const FlowParameters& p) + int residualBackwardsAxialFlow(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const AxialFlowParameters& p) { const ParamType h2 = p.h * p.h; @@ -262,24 +270,28 @@ namespace impl // Right side, leave out if we're in the first cell (boundary condition) if (cadet_likely(col < p.nCol - 1)) { - resBulkComp[col * p.strideCell] -= d_ax / h2 * (stencil[-1] - stencil[0]); + const double relCoord = static_cast(col+1) / p.nCol; + const ParamType d_ax_right = d_ax * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u)); + resBulkComp[col * p.strideCell] -= d_ax_right / h2 * (stencil[-1] - stencil[0]); // Jacobian entries if (wantJac) { - jac[0] += static_cast(d_ax) / static_cast(h2); - jac[p.strideCell] -= static_cast(d_ax) / static_cast(h2); + jac[0] += static_cast(d_ax_right) / static_cast(h2); + jac[p.strideCell] -= static_cast(d_ax_right) / static_cast(h2); } } // Left side, leave out if we're in the last cell (boundary condition) if (cadet_likely(col > 0)) { - resBulkComp[col * p.strideCell] -= d_ax / h2 * (stencil[1] - stencil[0]); + const double relCoord = static_cast(col) / p.nCol; + const ParamType d_ax_left = d_ax * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u)); + resBulkComp[col * p.strideCell] -= d_ax_left / h2 * (stencil[1] - stencil[0]); // Jacobian entries if (wantJac) { - jac[0] += static_cast(d_ax) / static_cast(h2); - jac[-p.strideCell] -= static_cast(d_ax) / static_cast(h2); + jac[0] += static_cast(d_ax_left) / static_cast(h2); + jac[-p.strideCell] -= static_cast(d_ax_left) / static_cast(h2); } } @@ -346,19 +358,19 @@ namespace impl template -int residualKernel(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const FlowParameters& p) +int residualKernelAxial(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const AxialFlowParameters& p) { if (p.u >= 0.0) - return impl::residualForwardsFlow(simTime, y, yDot, res, jacBegin, p); + return impl::residualForwardsAxialFlow(simTime, y, yDot, res, jacBegin, p); else - return impl::residualBackwardsFlow(simTime, y, yDot, res, jacBegin, p); + return impl::residualBackwardsAxialFlow(simTime, y, yDot, res, jacBegin, p); } -void sparsityPattern(linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, Weno& weno); +void sparsityPatternAxial(linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, Weno& weno); } // namespace convdisp } // namespace parts } // namespace model } // namespace cadet -#endif // LIBCADET_CONVECTIONDISPERSIONKERNEL_HPP_ +#endif // LIBCADET_AXIALCONVECTIONDISPERSIONKERNEL_HPP_ diff --git a/src/libcadet/model/parts/ConvectionDispersionOperator.cpp b/src/libcadet/model/parts/ConvectionDispersionOperator.cpp index a17f1ca4a..0e508127d 100644 --- a/src/libcadet/model/parts/ConvectionDispersionOperator.cpp +++ b/src/libcadet/model/parts/ConvectionDispersionOperator.cpp @@ -17,13 +17,17 @@ #include "ParamReaderHelper.hpp" #include "AdUtils.hpp" #include "SimulationTypes.hpp" -#include "model/parts/ConvectionDispersionKernel.hpp" +#include "model/parts/AxialConvectionDispersionKernel.hpp" +#include "model/parts/RadialConvectionDispersionKernel.hpp" +#include "model/ParameterDependence.hpp" #include "SensParamUtil.hpp" +#include "ConfigurationHelper.hpp" #include "LoggingUtils.hpp" #include "Logging.hpp" #include +#include namespace cadet { @@ -35,15 +39,18 @@ namespace parts { /** - * @brief Creates a ConvectionDispersionOperatorBase + * @brief Creates an AxialConvectionDispersionOperatorBase */ -ConvectionDispersionOperatorBase::ConvectionDispersionOperatorBase() : _stencilMemory(sizeof(active) * Weno::maxStencilSize()), - _wenoDerivatives(new double[Weno::maxStencilSize()]), _weno() +AxialConvectionDispersionOperatorBase::AxialConvectionDispersionOperatorBase() : _stencilMemory(sizeof(active) * Weno::maxStencilSize()), + _wenoDerivatives(new double[Weno::maxStencilSize()]), _weno(), _dispersionDep(nullptr) { } -ConvectionDispersionOperatorBase::~ConvectionDispersionOperatorBase() CADET_NOEXCEPT +AxialConvectionDispersionOperatorBase::~AxialConvectionDispersionOperatorBase() CADET_NOEXCEPT { + if (_dispersionDep) + delete _dispersionDep; + delete[] _wenoDerivatives; } @@ -55,12 +62,24 @@ ConvectionDispersionOperatorBase::~ConvectionDispersionOperatorBase() CADET_NOEX * @param [in] nCol Number of axial cells * @return @c true if configuration went fine, @c false otherwise */ -bool ConvectionDispersionOperatorBase::configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int nCol, unsigned int strideCell) +bool AxialConvectionDispersionOperatorBase::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol, unsigned int strideCell) { _nComp = nComp; _nCol = nCol; _strideCell = strideCell; + if (paramProvider.exists("COL_DISPERSION_DEP")) + { + const std::string paramDepName = paramProvider.getString("COL_DISPERSION_DEP"); + _dispersionDep = helper.createParameterParameterDependence(paramDepName); + if (!_dispersionDep) + throw InvalidParameterException("Unknown parameter dependence " + paramDepName + " in COL_DISPERSION_DEP"); + + _dispersionDep->configureModelDiscretization(paramProvider); + } + else + _dispersionDep = helper.createParameterParameterDependence("CONSTANT_ONE"); + paramProvider.pushScope("discretization"); // Read WENO settings and apply them @@ -83,7 +102,7 @@ bool ConvectionDispersionOperatorBase::configureModelDiscretization(IParameterPr * @param [out] parameters Map in which local parameters are inserted * @return @c true if configuration went fine, @c false otherwise */ -bool ConvectionDispersionOperatorBase::configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters) +bool AxialConvectionDispersionOperatorBase::configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters) { // Read geometry parameters _colLength = paramProvider.getDouble("COL_LENGTH"); @@ -146,6 +165,12 @@ bool ConvectionDispersionOperatorBase::configure(UnitOpIdx unitOpIdx, IParameter _colDispersion = std::move(expanded); } + if (_dispersionDep) + { + if (!_dispersionDep->configure(paramProvider, unitOpIdx, ParTypeIndep, BoundStateIndep, "COL_DISPERSION_DEP")) + throw InvalidParameterException("Failed to configure dispersion parameter dependency (COL_DISPERSION_DEP)"); + } + if (_velocity.empty() && (_crossSection <= 0.0)) { throw InvalidParameterException("At least one of CROSS_SECTION_AREA and VELOCITY has to be set"); @@ -184,7 +209,7 @@ bool ConvectionDispersionOperatorBase::configure(UnitOpIdx unitOpIdx, IParameter * @param [in] secIdx Index of the new section that is about to be integrated * @return @c true if flow direction has changed, otherwise @c false */ -bool ConvectionDispersionOperatorBase::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx) +bool AxialConvectionDispersionOperatorBase::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx) { // setFlowRates() was called before, so _curVelocity has direction dirOld const int dirOld = _dir; @@ -221,7 +246,7 @@ bool ConvectionDispersionOperatorBase::notifyDiscontinuousSectionTransition(doub * @param [in] out Total volumetric outlet flow rate * @param [in] colPorosity Porosity used for computing interstitial velocity from volumetric flow rate */ -void ConvectionDispersionOperatorBase::setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT +void AxialConvectionDispersionOperatorBase::setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT { // If we have cross section area, interstitial velocity is given by network flow rates if (_crossSection > 0.0) @@ -230,6 +255,7 @@ void ConvectionDispersionOperatorBase::setFlowRates(const active& in, const acti /** * @brief Computes the residual of the transport equations + * @param [in] model Model that owns the operator * @param [in] t Current time point * @param [in] secIdx Index of the current section * @param [in] y Pointer to unit operation's state vector @@ -238,51 +264,51 @@ void ConvectionDispersionOperatorBase::setFlowRates(const active& in, const acti * @param [in] jac Matrix that holds the Jacobian * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error */ -int ConvectionDispersionOperatorBase::residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, linalg::BandMatrix& jac) +int AxialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, linalg::BandMatrix& jac) { // Reset Jacobian jac.setAll(0.0); - return residualImpl(t, secIdx, y, yDot, res, jac.row(0)); + return residualImpl(model, t, secIdx, y, yDot, res, jac.row(0)); } -int ConvectionDispersionOperatorBase::residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, WithoutParamSensitivity) +int AxialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, WithoutParamSensitivity) { - return residualImpl(t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); } -int ConvectionDispersionOperatorBase::residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithoutParamSensitivity) +int AxialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithoutParamSensitivity) { - return residualImpl(t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); } -int ConvectionDispersionOperatorBase::residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, linalg::BandMatrix& jac) +int AxialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, linalg::BandMatrix& jac) { // Reset Jacobian jac.setAll(0.0); - return residualImpl(t, secIdx, y, yDot, res, jac.row(0)); + return residualImpl(model, t, secIdx, y, yDot, res, jac.row(0)); } -int ConvectionDispersionOperatorBase::residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, WithParamSensitivity) +int AxialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, WithParamSensitivity) { - return residualImpl(t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); } -int ConvectionDispersionOperatorBase::residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithParamSensitivity) +int AxialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithParamSensitivity) { - return residualImpl(t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); } template -int ConvectionDispersionOperatorBase::residualImpl(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin) +int AxialConvectionDispersionOperatorBase::residualImpl(const IModel& model, double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin) { const ParamType u = static_cast(_curVelocity); active const* const d_c = getSectionDependentSlice(_colDispersion, _nComp, secIdx); const ParamType h = static_cast(_colLength) / static_cast(_nCol); // const int strideCell = strideColCell(); - convdisp::FlowParameters fp{ + convdisp::AxialFlowParameters fp{ u, d_c, h, @@ -294,10 +320,12 @@ int ConvectionDispersionOperatorBase::residualImpl(double t, unsigned int secIdx _nComp, _nCol, 0u, - _nComp + _nComp, + _dispersionDep, + model }; - return convdisp::residualKernel(SimulationTime{t, secIdx}, y, yDot, res, jacBegin, fp); + return convdisp::residualKernelAxial(SimulationTime{t, secIdx}, y, yDot, res, jacBegin, fp); } /** @@ -311,7 +339,7 @@ int ConvectionDispersionOperatorBase::residualImpl(double t, unsigned int secIdx * @param [in] sDot Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ * @param [out] ret Vector @f$ z @f$ which stores the result of the operation */ -void ConvectionDispersionOperatorBase::multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const +void AxialConvectionDispersionOperatorBase::multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const { double* localRet = ret + offsetC(); double const* localSdot = sDot + offsetC(); @@ -333,7 +361,7 @@ void ConvectionDispersionOperatorBase::multiplyWithDerivativeJacobian(const Simu * The factor @f$ \alpha @f$ is useful when constructing the linear system in the time integration process. * @param [in] alpha Factor in front of @f$ \frac{\partial F}{\partial \dot{y}} @f$ */ -void ConvectionDispersionOperatorBase::addTimeDerivativeToJacobian(double alpha, linalg::FactorizableBandMatrix& jacDisc) +void AxialConvectionDispersionOperatorBase::addTimeDerivativeToJacobian(double alpha, linalg::FactorizableBandMatrix& jacDisc) { const int gapCell = strideColCell() - static_cast(_nComp) * strideColComp(); linalg::FactorizableBandMatrix::RowIterator jac = jacDisc.row(0); @@ -347,7 +375,7 @@ void ConvectionDispersionOperatorBase::addTimeDerivativeToJacobian(double alpha, } } -unsigned int ConvectionDispersionOperatorBase::jacobianLowerBandwidth() const CADET_NOEXCEPT +unsigned int AxialConvectionDispersionOperatorBase::jacobianLowerBandwidth() const CADET_NOEXCEPT { // Note that we have to increase the lower bandwidth by 1 because the WENO stencil is applied to the // right cell face (lower + 1 + upper) and to the left cell face (shift the stencil by -1 because influx of cell i @@ -356,21 +384,38 @@ unsigned int ConvectionDispersionOperatorBase::jacobianLowerBandwidth() const CA return std::max(_weno.lowerBandwidth() + 1u, 1u) * strideColCell(); } -unsigned int ConvectionDispersionOperatorBase::jacobianUpperBandwidth() const CADET_NOEXCEPT +unsigned int AxialConvectionDispersionOperatorBase::jacobianUpperBandwidth() const CADET_NOEXCEPT { // We have to make sure that there's at least one sub and super diagonal for the dispersion term return std::max(_weno.upperBandwidth(), 1u) * strideColCell(); } -unsigned int ConvectionDispersionOperatorBase::jacobianDiscretizedBandwidth() const CADET_NOEXCEPT +unsigned int AxialConvectionDispersionOperatorBase::jacobianDiscretizedBandwidth() const CADET_NOEXCEPT { // When flow direction is changed, the bandwidths of the Jacobian swap. // Hence, we have to reserve memory such that the swapped Jacobian can fit into the matrix. return std::max(jacobianLowerBandwidth(), jacobianUpperBandwidth()); } -bool ConvectionDispersionOperatorBase::setParameter(const ParameterId& pId, double value) +double AxialConvectionDispersionOperatorBase::inletJacobianFactor() const CADET_NOEXCEPT { + const double h = static_cast(_colLength) / static_cast(_nCol); + const double u = static_cast(_curVelocity); + return u / h; +} + +bool AxialConvectionDispersionOperatorBase::setParameter(const ParameterId& pId, double value) +{ + // Check if parameter is in parameter dependence of column dispersion coefficient + if (_dispersionDep) + { + if (_dispersionDep->hasParameter(pId)) + { + _dispersionDep->setParameter(pId, value); + return true; + } + } + // We only need to do something if COL_DISPERSION is component independent if (!_dispersionCompIndep) return false; @@ -400,8 +445,19 @@ bool ConvectionDispersionOperatorBase::setParameter(const ParameterId& pId, doub return true; } -bool ConvectionDispersionOperatorBase::setSensitiveParameterValue(const std::unordered_set& sensParams, const ParameterId& pId, double value) +bool AxialConvectionDispersionOperatorBase::setSensitiveParameterValue(const std::unordered_set& sensParams, const ParameterId& pId, double value) { + // Check if parameter is in parameter dependence of column dispersion coefficient + if (_dispersionDep) + { + active* const param = _dispersionDep->getParameter(pId); + if (param) + { + param->setValue(value); + return true; + } + } + // We only need to do something if COL_DISPERSION is component independent if (!_dispersionCompIndep) return false; @@ -437,8 +493,19 @@ bool ConvectionDispersionOperatorBase::setSensitiveParameterValue(const std::uno return true; } -bool ConvectionDispersionOperatorBase::setSensitiveParameter(std::unordered_set& sensParams, const ParameterId& pId, unsigned int adDirection, double adValue) +bool AxialConvectionDispersionOperatorBase::setSensitiveParameter(std::unordered_set& sensParams, const ParameterId& pId, unsigned int adDirection, double adValue) { + // Check if parameter is in parameter dependence of column dispersion coefficient + if (_dispersionDep) + { + active* const param = _dispersionDep->getParameter(pId); + if (param) + { + param->setADValue(adDirection, adValue); + return true; + } + } + // We only need to do something if COL_DISPERSION is component independent if (!_dispersionCompIndep) return false; @@ -472,14 +539,587 @@ bool ConvectionDispersionOperatorBase::setSensitiveParameter(std::unordered_set< + /** - * @brief Creates a ConvectionDispersionOperator + * @brief Creates a RadialConvectionDispersionOperatorBase */ -ConvectionDispersionOperator::ConvectionDispersionOperator() +RadialConvectionDispersionOperatorBase::RadialConvectionDispersionOperatorBase() : _stencilMemory(sizeof(active) * Weno::maxStencilSize()), _dispersionDep(nullptr) { } -ConvectionDispersionOperator::~ConvectionDispersionOperator() CADET_NOEXCEPT +RadialConvectionDispersionOperatorBase::~RadialConvectionDispersionOperatorBase() CADET_NOEXCEPT +{ + if (_dispersionDep) + delete _dispersionDep; +} + +/** + * @brief Reads parameters and allocates memory + * @details Has to be called once before the operator is used. + * @param [in] paramProvider Parameter provider for reading parameters + * @param [in] nComp Number of components + * @param [in] nCol Number of axial cells + * @return @c true if configuration went fine, @c false otherwise + */ +bool RadialConvectionDispersionOperatorBase::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol, unsigned int strideCell) +{ + _nComp = nComp; + _nCol = nCol; + _strideCell = strideCell; + + if (paramProvider.exists("COL_DISPERSION_DEP")) + { + const std::string paramDepName = paramProvider.getString("COL_DISPERSION_DEP"); + _dispersionDep = helper.createParameterParameterDependence(paramDepName); + if (!_dispersionDep) + throw InvalidParameterException("Unknown parameter dependence " + paramDepName + " in COL_DISPERSION_DEP"); + + _dispersionDep->configureModelDiscretization(paramProvider); + } + else + _dispersionDep = helper.createParameterParameterDependence("CONSTANT_ONE"); + + paramProvider.pushScope("discretization"); + + // Read WENO settings and apply them +/* + paramProvider.pushScope("weno"); + _weno.order(paramProvider.getInt("WENO_ORDER")); + _weno.boundaryTreatment(paramProvider.getInt("BOUNDARY_MODEL")); + _wenoEpsilon = paramProvider.getDouble("WENO_EPS"); + paramProvider.popScope(); +*/ + + paramProvider.popScope(); + + return true; +} + +/** + * @brief Reads model parameters + * @details Only reads parameters that do not affect model structure (e.g., discretization). + * @param [in] unitOpIdx Unit operation id of the owning unit operation model + * @param [in] paramProvider Parameter provider for reading parameters + * @param [out] parameters Map in which local parameters are inserted + * @return @c true if configuration went fine, @c false otherwise + */ +bool RadialConvectionDispersionOperatorBase::configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters) +{ + // Read geometry parameters + _innerRadius = paramProvider.getDouble("COL_RADIUS_INNER"); + _outerRadius = paramProvider.getDouble("COL_RADIUS_OUTER"); + + // Read length or set to -1 + _colLength = -1.0; + if (paramProvider.exists("COL_LENGTH")) + { + _colLength = paramProvider.getDouble("COL_LENGTH"); + } + + // Read section dependent parameters (transport) + + // Read VELOCITY coefficient + _velocity.clear(); + if (paramProvider.exists("VELOCITY_COEFF")) + { + readScalarParameterOrArray(_velocity, paramProvider, "VELOCITY_COEFF", 1); + } + _dir = 1; + + readScalarParameterOrArray(_colDispersion, paramProvider, "COL_DISPERSION", 1); + if (paramProvider.exists("COL_DISPERSION_MULTIPLEX")) + { + const int mode = paramProvider.getInt("COL_DISPERSION_MULTIPLEX"); + if (mode == 0) + // Comp-indep, sec-indep + _dispersionCompIndep = true; + else if (mode == 1) + // Comp-dep, sec-indep + _dispersionCompIndep = false; + else if (mode == 2) + // Comp-indep, sec-dep + _dispersionCompIndep = true; + else if (mode == 3) + // Comp-dep, sec-dep + _dispersionCompIndep = false; + + if (!_dispersionCompIndep && (_colDispersion.size() % _nComp != 0)) + throw InvalidParameterException("Number of elements in field COL_DISPERSION is not a positive multiple of NCOMP (" + std::to_string(_nComp) + ")"); + if ((mode == 0) && (_colDispersion.size() != 1)) + throw InvalidParameterException("Number of elements in field COL_DISPERSION inconsistent with COL_DISPERSION_MULTIPLEX (should be 1)"); + if ((mode == 1) && (_colDispersion.size() != _nComp)) + throw InvalidParameterException("Number of elements in field COL_DISPERSION inconsistent with COL_DISPERSION_MULTIPLEX (should be " + std::to_string(_nComp) + ")"); + } + else + { + // Infer component dependence of COL_DISPERSION: + // size not divisible by NCOMP -> component independent + _dispersionCompIndep = ((_colDispersion.size() % _nComp) != 0); + } + + // Expand _colDispersion to make it component dependent + if (_dispersionCompIndep) + { + std::vector expanded(_colDispersion.size() * _nComp); + for (std::size_t i = 0; i < _colDispersion.size(); ++i) + std::fill(expanded.begin() + i * _nComp, expanded.begin() + (i + 1) * _nComp, _colDispersion[i]); + + _colDispersion = std::move(expanded); + } + + if (_dispersionDep) + { + if (!_dispersionDep->configure(paramProvider, unitOpIdx, ParTypeIndep, BoundStateIndep, "COL_DISPERSION_DEP")) + throw InvalidParameterException("Failed to configure dispersion parameter dependency (COL_DISPERSION_DEP)"); + } + + if (_velocity.empty() && (_colLength <= 0.0)) + { + throw InvalidParameterException("At least one of COL_LENGTH and VELOCITY_COEFF has to be set"); + } + + // Add parameters to map + if (_dispersionCompIndep) + { + if (_colDispersion.size() > _nComp) + { + // Register only the first item in each section + for (std::size_t i = 0; i < _colDispersion.size() / _nComp; ++i) + parameters[makeParamId(hashString("COL_DISPERSION"), unitOpIdx, CompIndep, ParTypeIndep, BoundStateIndep, ReactionIndep, i)] = &_colDispersion[i * _nComp]; + } + else + { + // We have only one parameter + parameters[makeParamId(hashString("COL_DISPERSION"), unitOpIdx, CompIndep, ParTypeIndep, BoundStateIndep, ReactionIndep, SectionIndep)] = &_colDispersion[0]; + } + } + else + registerParam2DArray(parameters, _colDispersion, [=](bool multi, unsigned int sec, unsigned int comp) { return makeParamId(hashString("COL_DISPERSION"), unitOpIdx, comp, ParTypeIndep, BoundStateIndep, ReactionIndep, multi ? sec : SectionIndep); }, _nComp); + + registerScalarSectionDependentParam(hashString("VELOCITY_COEFF"), parameters, _velocity, unitOpIdx, ParTypeIndep); + parameters[makeParamId(hashString("COL_LENGTH"), unitOpIdx, CompIndep, ParTypeIndep, BoundStateIndep, ReactionIndep, SectionIndep)] = &_colLength; + parameters[makeParamId(hashString("COL_RADIUS_INNER"), unitOpIdx, CompIndep, ParTypeIndep, BoundStateIndep, ReactionIndep, SectionIndep)] = &_innerRadius; + parameters[makeParamId(hashString("COL_RADIUS_OUTER"), unitOpIdx, CompIndep, ParTypeIndep, BoundStateIndep, ReactionIndep, SectionIndep)] = &_outerRadius; + + equidistantCells(); + + return true; +} + +/** + * @brief Notifies the operator that a discontinuous section transition is in progress + * @details In addition to changing flow direction internally, if necessary, the function returns whether + * the flow direction has changed. + * @param [in] t Current time point + * @param [in] secIdx Index of the new section that is about to be integrated + * @return @c true if flow direction has changed, otherwise @c false + */ +bool RadialConvectionDispersionOperatorBase::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx) +{ + // setFlowRates() was called before, so _curVelocity has direction dirOld + const int dirOld = _dir; + + if (_colLength <= 0.0) + { + // Use the provided _velocity (direction is also set), only update _dir + _curVelocity = getSectionDependentScalar(_velocity, secIdx); + _dir = (_curVelocity >= 0.0) ? 1 : -1; + } + else if (!_velocity.empty()) + { + // Use network flow rate but take direction from _velocity + _dir = (getSectionDependentScalar(_velocity, secIdx) >= 0.0) ? 1 : -1; + + // _curVelocity has correct magnitude but previous direction, so flip it if necessary + if (dirOld * _dir < 0) + _curVelocity *= -1.0; + } + + // No remaining case, exception was thrown if neither column length nor velocity coefficient was specified + + // Detect change in flow direction + return (dirOld * _dir < 0); +} + +/** + * @brief Sets the flow rates for the current time section + * @details The flow rates may change due to valve switches. + * @param [in] in Total volumetric inlet flow rate + * @param [in] out Total volumetric outlet flow rate + * @param [in] colPorosity Porosity used for computing interstitial velocity from volumetric flow rate + */ +void RadialConvectionDispersionOperatorBase::setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT +{ + const double pi = 3.1415926535897932384626434; + + // If we have column length, interstitial velocity is given by network flow rates + if (_colLength > 0.0) + _curVelocity = _dir * in / (2.0 * pi * _colLength * colPorosity); +} + +active RadialConvectionDispersionOperatorBase::currentVelocity(double pos) const CADET_NOEXCEPT +{ + const active radius = pos * (_outerRadius - _innerRadius) + _innerRadius; + return _curVelocity / radius; +} + +/** + * @brief Computes the residual of the transport equations + * @param [in] model Model that owns the operator + * @param [in] t Current time point + * @param [in] secIdx Index of the current section + * @param [in] y Pointer to unit operation's state vector + * @param [in] yDot Pointer to unit operation's time derivative state vector + * @param [out] res Pointer to unit operation's residual vector + * @param [in] jac Matrix that holds the Jacobian + * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error + */ +int RadialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, linalg::BandMatrix& jac) +{ + // Reset Jacobian + jac.setAll(0.0); + + return residualImpl(model, t, secIdx, y, yDot, res, jac.row(0)); +} + +int RadialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, WithoutParamSensitivity) +{ + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); +} + +int RadialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithoutParamSensitivity) +{ + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); +} + +int RadialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, linalg::BandMatrix& jac) +{ + // Reset Jacobian + jac.setAll(0.0); + + return residualImpl(model, t, secIdx, y, yDot, res, jac.row(0)); +} + +int RadialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, WithParamSensitivity) +{ + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); +} + +int RadialConvectionDispersionOperatorBase::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithParamSensitivity) +{ + return residualImpl(model, t, secIdx, y, yDot, res, linalg::BandMatrix::RowIterator()); +} + +template +int RadialConvectionDispersionOperatorBase::residualImpl(const IModel& model, double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin) +{ + const ParamType u = static_cast(_curVelocity); + active const* const d_rad = getSectionDependentSlice(_colDispersion, _nComp, secIdx); + + convdisp::RadialFlowParameters fp{ + u, + d_rad, + _cellCenters.data(), + _cellSizes.data(), + _cellBounds.data(), + &_stencilMemory, + strideColCell(), + _nComp, + _nCol, + 0u, + _nComp, + _dispersionDep, + model + }; + + return convdisp::residualKernelRadial(SimulationTime{t, secIdx}, y, yDot, res, jacBegin, fp); +} + +/** + * @brief Multiplies the time derivative Jacobian @f$ \frac{\partial F}{\partial \dot{y}}\left(t, y, \dot{y}\right) @f$ with a given vector + * @details The operation @f$ z = \frac{\partial F}{\partial \dot{y}} x @f$ is performed. + * The matrix-vector multiplication is performed matrix-free (i.e., no matrix is explicitly formed). + * + * Note that this function only performs multiplication with the Jacobian of the (axial) transport equations. + * The input vectors are assumed to point to the beginning (including inlet DOFs) of the respective unit operation's arrays. + * @param [in] simTime Simulation time information (time point, section index, pre-factor of time derivatives) + * @param [in] sDot Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ + * @param [out] ret Vector @f$ z @f$ which stores the result of the operation + */ +void RadialConvectionDispersionOperatorBase::multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const +{ + double* localRet = ret + offsetC(); + double const* localSdot = sDot + offsetC(); + const int gapCell = strideColCell() - static_cast(_nComp) * strideColComp(); + + for (unsigned int i = 0; i < _nCol; ++i, localRet += gapCell, localSdot += gapCell) + { + for (unsigned int j = 0; j < _nComp; ++j, ++localRet, ++localSdot) + { + *localRet = (*localSdot); + } + } +} + +/** + * @brief Adds the derivatives with respect to @f$ \dot{y} @f$ of @f$ F(t, y, \dot{y}) @f$ to the Jacobian + * @details This functions computes + * @f[ \begin{align*} \text{_jacCdisc} = \text{_jacCdisc} + \alpha \frac{\partial F}{\partial \dot{y}}. \end{align*} @f] + * The factor @f$ \alpha @f$ is useful when constructing the linear system in the time integration process. + * @param [in] alpha Factor in front of @f$ \frac{\partial F}{\partial \dot{y}} @f$ + */ +void RadialConvectionDispersionOperatorBase::addTimeDerivativeToJacobian(double alpha, linalg::FactorizableBandMatrix& jacDisc) +{ + const int gapCell = strideColCell() - static_cast(_nComp) * strideColComp(); + linalg::FactorizableBandMatrix::RowIterator jac = jacDisc.row(0); + for (unsigned int i = 0; i < _nCol; ++i, jac += gapCell) + { + for (unsigned int j = 0; j < _nComp; ++j, ++jac) + { + // Add time derivative to main diagonal + jac[0] += alpha; + } + } +} + +unsigned int RadialConvectionDispersionOperatorBase::jacobianLowerBandwidth() const CADET_NOEXCEPT +{ + // Note that we have to increase the lower bandwidth by 1 because the WENO stencil is applied to the + // right cell face (lower + 1 + upper) and to the left cell face (shift the stencil by -1 because influx of cell i + // is outflux of cell i-1) + // We also have to make sure that there's at least one sub and super diagonal for the dispersion term +// return std::max(_weno.lowerBandwidth() + 1u, 1u) * strideColCell(); + return strideColCell(); +} + +unsigned int RadialConvectionDispersionOperatorBase::jacobianUpperBandwidth() const CADET_NOEXCEPT +{ + // We have to make sure that there's at least one sub and super diagonal for the dispersion term +// return std::max(_weno.upperBandwidth(), 1u) * strideColCell(); + return strideColCell(); +} + +unsigned int RadialConvectionDispersionOperatorBase::jacobianDiscretizedBandwidth() const CADET_NOEXCEPT +{ + // When flow direction is changed, the bandwidths of the Jacobian swap. + // Hence, we have to reserve memory such that the swapped Jacobian can fit into the matrix. + return std::max(jacobianLowerBandwidth(), jacobianUpperBandwidth()); +} + +double RadialConvectionDispersionOperatorBase::inletJacobianFactor() const CADET_NOEXCEPT +{ + const double denom = static_cast(_cellCenters[0]) * static_cast(_cellSizes[0]); + const double u = static_cast(_curVelocity); + return u / denom; +} + +bool RadialConvectionDispersionOperatorBase::setParameter(const ParameterId& pId, double value) +{ + // Check if parameter is in parameter dependence of column dispersion coefficient + if (_dispersionDep) + { + if (_dispersionDep->hasParameter(pId)) + { + _dispersionDep->setParameter(pId, value); + return true; + } + } + + // Check whether column radius has changed and update discretization if necessary + if (pId.name == hashString("COL_RADIUS_INNER")) + { + _innerRadius.setValue(value); + equidistantCells(); + return true; + } + + if (pId.name == hashString("COL_RADIUS_OUTER")) + { + _outerRadius.setValue(value); + equidistantCells(); + return true; + } + + // We only need to do something if COL_DISPERSION is component independent + if (!_dispersionCompIndep) + return false; + + if ((pId.name != hashString("COL_DISPERSION")) || (pId.component != CompIndep) || (pId.boundState != BoundStateIndep) || (pId.reaction != ReactionIndep) || (pId.particleType != ParTypeIndep)) + return false; + + if (_colDispersion.size() > _nComp) + { + // Section dependent + if (pId.section == SectionIndep) + return false; + + for (unsigned int i = 0; i < _nComp; ++i) + _colDispersion[pId.section * _nComp + i].setValue(value); + } + else + { + // Section independent + if (pId.section != SectionIndep) + return false; + + for (unsigned int i = 0; i < _nComp; ++i) + _colDispersion[i].setValue(value); + } + + return true; +} + +bool RadialConvectionDispersionOperatorBase::setSensitiveParameterValue(const std::unordered_set& sensParams, const ParameterId& pId, double value) +{ + // Check if parameter is in parameter dependence of column dispersion coefficient + if (_dispersionDep) + { + active* const param = _dispersionDep->getParameter(pId); + if (param) + { + param->setValue(value); + return true; + } + } + + // Check whether column radius has changed and update discretization if necessary + if (pId.name == hashString("COL_RADIUS_INNER")) + { + _innerRadius.setValue(value); + equidistantCells(); + return true; + } + + if (pId.name == hashString("COL_RADIUS_OUTER")) + { + _outerRadius.setValue(value); + equidistantCells(); + return true; + } + + // We only need to do something if COL_DISPERSION is component independent + if (!_dispersionCompIndep) + return false; + + if ((pId.name != hashString("COL_DISPERSION")) || (pId.component != CompIndep) || (pId.boundState != BoundStateIndep) || (pId.reaction != ReactionIndep) || (pId.particleType != ParTypeIndep)) + return false; + + if (_colDispersion.size() > _nComp) + { + // Section dependent + if (pId.section == SectionIndep) + return false; + + if (!contains(sensParams, &_colDispersion[pId.section * _nComp])) + return false; + + for (unsigned int i = 0; i < _nComp; ++i) + _colDispersion[pId.section * _nComp + i].setValue(value); + } + else + { + // Section independent + if (pId.section != SectionIndep) + return false; + + if (!contains(sensParams, &_colDispersion[0])) + return false; + + for (unsigned int i = 0; i < _nComp; ++i) + _colDispersion[i].setValue(value); + } + + return true; +} + +bool RadialConvectionDispersionOperatorBase::setSensitiveParameter(std::unordered_set& sensParams, const ParameterId& pId, unsigned int adDirection, double adValue) +{ + // Check if parameter is in parameter dependence of column dispersion coefficient + if (_dispersionDep) + { + active* const param = _dispersionDep->getParameter(pId); + if (param) + { + param->setADValue(adDirection, adValue); + return true; + } + } + + // Check whether column radius has changed and update discretization if necessary + if (pId.name == hashString("COL_RADIUS_INNER")) + { + _innerRadius.setADValue(adDirection, adValue); + equidistantCells(); + return true; + } + + if (pId.name == hashString("COL_RADIUS_OUTER")) + { + _outerRadius.setADValue(adDirection, adValue); + equidistantCells(); + return true; + } + + // We only need to do something if COL_DISPERSION is component independent + if (!_dispersionCompIndep) + return false; + + if ((pId.name != hashString("COL_DISPERSION")) || (pId.component != CompIndep) || (pId.boundState != BoundStateIndep) || (pId.reaction != ReactionIndep) || (pId.particleType != ParTypeIndep)) + return false; + + if (_colDispersion.size() > _nComp) + { + // Section dependent + if (pId.section == SectionIndep) + return false; + + sensParams.insert(&_colDispersion[pId.section * _nComp]); + for (unsigned int i = 0; i < _nComp; ++i) + _colDispersion[pId.section * _nComp + i].setADValue(adDirection, adValue); + } + else + { + // Section independent + if (pId.section != SectionIndep) + return false; + + sensParams.insert(&_colDispersion[0]); + for (unsigned int i = 0; i < _nComp; ++i) + _colDispersion[i].setADValue(adDirection, adValue); + } + + return true; +} + +void RadialConvectionDispersionOperatorBase::equidistantCells() +{ + const active dr = (_outerRadius - _innerRadius) / _nCol; + std::vector centers(_nCol, 0.0); + _cellSizes = std::vector(_nCol, dr); + std::vector bounds(_nCol + 1, 0.0); + + for (unsigned int i = 0; i < _nCol; ++i) + { + centers[i] = (i + 0.5) * dr + _innerRadius; + bounds[i] = i * dr + _innerRadius; + } + bounds[_nCol] = _outerRadius; + + _cellCenters = std::move(centers); + _cellBounds = std::move(bounds); +} + + +/** + * @brief Creates an ConvectionDispersionOperator + */ +template +ConvectionDispersionOperator::ConvectionDispersionOperator() +{ +} + +template +ConvectionDispersionOperator::~ConvectionDispersionOperator() CADET_NOEXCEPT { } @@ -488,7 +1128,8 @@ ConvectionDispersionOperator::~ConvectionDispersionOperator() CADET_NOEXCEPT * @details Band compression is used to minimize the amount of AD directions. * @return Number of required AD directions */ -int ConvectionDispersionOperator::requiredADdirs() const CADET_NOEXCEPT +template +int ConvectionDispersionOperator::requiredADdirs() const CADET_NOEXCEPT { return _jacC.stride(); } @@ -503,9 +1144,10 @@ int ConvectionDispersionOperator::requiredADdirs() const CADET_NOEXCEPT * @param [in] nCol Number of axial cells * @return @c true if configuration went fine, @c false otherwise */ -bool ConvectionDispersionOperator::configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int nCol) +template +bool ConvectionDispersionOperator::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol) { - const bool retVal = _baseOp.configureModelDiscretization(paramProvider, nComp, nCol, nComp); + const bool retVal = _baseOp.configureModelDiscretization(paramProvider, helper, nComp, nCol, nComp); // Allocate memory const unsigned int lb = _baseOp.jacobianLowerBandwidth(); @@ -528,7 +1170,8 @@ bool ConvectionDispersionOperator::configureModelDiscretization(IParameterProvid * @param [out] parameters Map in which local parameters are inserted * @return @c true if configuration went fine, @c false otherwise */ -bool ConvectionDispersionOperator::configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters) +template +bool ConvectionDispersionOperator::configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters) { return _baseOp.configure(unitOpIdx, paramProvider, parameters); } @@ -542,7 +1185,8 @@ bool ConvectionDispersionOperator::configure(UnitOpIdx unitOpIdx, IParameterProv * @param [in,out] adJac Jacobian information for AD (AD vectors for residual and state, direction offset) * @return @c true if flow direction has changed, otherwise @c false */ -bool ConvectionDispersionOperator::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const AdJacobianParams& adJac) +template +bool ConvectionDispersionOperator::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const AdJacobianParams& adJac) { const bool hasChanged = _baseOp.notifyDiscontinuousSectionTransition(t, secIdx); @@ -553,8 +1197,7 @@ bool ConvectionDispersionOperator::notifyDiscontinuousSectionTransition(double t const unsigned int lb = _baseOp.jacobianLowerBandwidth(); const unsigned int ub = _baseOp.jacobianUpperBandwidth(); - const double u = static_cast(_baseOp.currentVelocity()); - if (u >= 0.0) + if (_baseOp.forwardFlow()) { // Forwards flow @@ -581,7 +1224,8 @@ bool ConvectionDispersionOperator::notifyDiscontinuousSectionTransition(double t * @brief Sets the AD seed vectors for the bulk transport variables * @param [in,out] adJac Jacobian information for AD (AD vectors for residual and state, direction offset) */ -void ConvectionDispersionOperator::prepareADvectors(const AdJacobianParams& adJac) const +template +void ConvectionDispersionOperator::prepareADvectors(const AdJacobianParams& adJac) const { // Early out if AD is disabled if (!adJac.adY) @@ -602,13 +1246,15 @@ void ConvectionDispersionOperator::prepareADvectors(const AdJacobianParams& adJa * @param [in] out Total volumetric outlet flow rate * @param [in] colPorosity Porosity used for computing interstitial velocity from volumetric flow rate */ -void ConvectionDispersionOperator::setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT +template +void ConvectionDispersionOperator::setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT { _baseOp.setFlowRates(in, out, colPorosity); } /** * @brief Computes the residual of the transport equations + * @param [in] model Model that owns the operator * @param [in] t Current time point * @param [in] secIdx Index of the current section * @param [in] y Pointer to unit operation's state vector @@ -617,30 +1263,34 @@ void ConvectionDispersionOperator::setFlowRates(const active& in, const active& * @param [in] wantJac Determines whether analytic Jacobian is computed * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error */ -int ConvectionDispersionOperator::residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity) +template +int ConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity) { if (wantJac) - return _baseOp.residual(t, secIdx, y, yDot, res, _jacC); + return _baseOp.residual(model, t, secIdx, y, yDot, res, _jacC); else - return _baseOp.residual(t, secIdx, y, yDot, res, WithoutParamSensitivity()); + return _baseOp.residual(model, t, secIdx, y, yDot, res, WithoutParamSensitivity()); } -int ConvectionDispersionOperator::residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity) +template +int ConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity) { - return _baseOp.residual(t, secIdx, y, yDot, res, WithoutParamSensitivity()); + return _baseOp.residual(model, t, secIdx, y, yDot, res, WithoutParamSensitivity()); } -int ConvectionDispersionOperator::residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) +template +int ConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) { - return _baseOp.residual(t, secIdx, y, yDot, res, WithParamSensitivity()); + return _baseOp.residual(model, t, secIdx, y, yDot, res, WithParamSensitivity()); } -int ConvectionDispersionOperator::residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) +template +int ConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) { if (wantJac) - return _baseOp.residual(t, secIdx, y, yDot, res, _jacC); + return _baseOp.residual(model, t, secIdx, y, yDot, res, _jacC); else - return _baseOp.residual(t, secIdx, y, yDot, res, WithParamSensitivity()); + return _baseOp.residual(model, t, secIdx, y, yDot, res, WithParamSensitivity()); } /** @@ -654,7 +1304,8 @@ int ConvectionDispersionOperator::residual(double t, unsigned int secIdx, double * @param [in] sDot Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ * @param [out] ret Vector @f$ z @f$ which stores the result of the operation */ -void ConvectionDispersionOperator::multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const +template +void ConvectionDispersionOperator::multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const { _baseOp.multiplyWithDerivativeJacobian(simTime, sDot, ret); } @@ -665,7 +1316,8 @@ void ConvectionDispersionOperator::multiplyWithDerivativeJacobian(const Simulati * @param [in] adRes Residual vector of AD datatypes with band compressed seed vectors * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) */ -void ConvectionDispersionOperator::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) +template +void ConvectionDispersionOperator::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) { ad::extractBandedJacobianFromAd(adRes + offsetC(), adDirOffset, _jacC.lowerBandwidth(), _jacC); } @@ -680,7 +1332,8 @@ void ConvectionDispersionOperator::extractJacobianFromAD(active const* const adR * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) * @return Maximum elementwise absolute difference between analytic and AD Jacobian */ -double ConvectionDispersionOperator::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const +template +double ConvectionDispersionOperator::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const { // Column const double maxDiffCol = ad::compareBandedJacobianWithAd(adRes + offsetC(), adDirOffset, _jacC.lowerBandwidth(), _jacC); @@ -706,7 +1359,8 @@ double ConvectionDispersionOperator::checkAnalyticJacobianAgainstAd(active const * * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) */ -void ConvectionDispersionOperator::assembleDiscretizedJacobian(double alpha) +template +void ConvectionDispersionOperator::assembleDiscretizedJacobian(double alpha) { // Copy normal matrix over to factorizable matrix _jacCdisc.copyOver(_jacC); @@ -723,7 +1377,8 @@ void ConvectionDispersionOperator::assembleDiscretizedJacobian(double alpha) * The factor @f$ \alpha @f$ is useful when constructing the linear system in the time integration process. * @param [in] alpha Factor in front of @f$ \frac{\partial F}{\partial \dot{y}} @f$ */ -void ConvectionDispersionOperator::addTimeDerivativeToJacobian(double alpha) +template +void ConvectionDispersionOperator::addTimeDerivativeToJacobian(double alpha) { _baseOp.addTimeDerivativeToJacobian(alpha, _jacCdisc); } @@ -734,7 +1389,8 @@ void ConvectionDispersionOperator::addTimeDerivativeToJacobian(double alpha) * @param [in] alpha Factor in front of @f$ \frac{\partial F}{\partial \dot{y}} @f$ * @return @c true if factorization went fine, otherwise @c false */ -bool ConvectionDispersionOperator::assembleAndFactorizeDiscretizedJacobian(double alpha) +template +bool ConvectionDispersionOperator::assembleAndFactorizeDiscretizedJacobian(double alpha) { assembleDiscretizedJacobian(alpha); return _jacCdisc.factorize(); @@ -749,7 +1405,8 @@ bool ConvectionDispersionOperator::assembleAndFactorizeDiscretizedJacobian(doubl * @param [in,out] rhs On entry, right hand side of the equation system. On exit, solution of the system. * @return @c true if the system was solved correctly, otherwise @c false */ -bool ConvectionDispersionOperator::solveDiscretizedJacobian(double* rhs) const +template +bool ConvectionDispersionOperator::solveDiscretizedJacobian(double* rhs) const { return _jacCdisc.solve(rhs); } @@ -762,7 +1419,8 @@ bool ConvectionDispersionOperator::solveDiscretizedJacobian(double* rhs) const * @param [in,out] rhs On entry, right hand side. On exit, solution of the system. * @return @c true if the system was solved correctly, @c false otherwise */ -bool ConvectionDispersionOperator::solveTimeDerivativeSystem(const SimulationTime& simTime, double* const rhs) +template +bool ConvectionDispersionOperator::solveTimeDerivativeSystem(const SimulationTime& simTime, double* const rhs) { // Assemble _jacCdisc.setAll(0.0); @@ -787,6 +1445,10 @@ bool ConvectionDispersionOperator::solveTimeDerivativeSystem(const SimulationTim return true; } +// Template instantiations +template class ConvectionDispersionOperator; +template class ConvectionDispersionOperator; + } // namespace parts } // namespace model diff --git a/src/libcadet/model/parts/ConvectionDispersionOperator.hpp b/src/libcadet/model/parts/ConvectionDispersionOperator.hpp index 76d0c50ae..41bd14860 100644 --- a/src/libcadet/model/parts/ConvectionDispersionOperator.hpp +++ b/src/libcadet/model/parts/ConvectionDispersionOperator.hpp @@ -34,12 +34,16 @@ namespace cadet { class IParameterProvider; +class IConfigHelper; struct AdJacobianParams; struct SimulationTime; +class IModel; namespace model { +class IParameterParameterDependence; + namespace parts { @@ -61,32 +65,36 @@ u c_{\text{in},i}(t) &= u c_i(t,0) - D_{\text{ax},i} \frac{\partial c_i}{\partia * It assumes that there is no offset to the inlet in the local state vector and that the firsts cell is placed * directly after the inlet DOFs. */ -class ConvectionDispersionOperatorBase +class AxialConvectionDispersionOperatorBase { public: - ConvectionDispersionOperatorBase(); - ~ConvectionDispersionOperatorBase() CADET_NOEXCEPT; + AxialConvectionDispersionOperatorBase(); + ~AxialConvectionDispersionOperatorBase() CADET_NOEXCEPT; void setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT; - bool configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int nCol, unsigned int strideCell); + bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol, unsigned int strideCell); bool configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters); bool notifyDiscontinuousSectionTransition(double t, unsigned int secIdx); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, linalg::BandMatrix& jac); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, linalg::BandMatrix& jac); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, WithoutParamSensitivity); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, WithParamSensitivity); - int residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithParamSensitivity); - int residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, linalg::BandMatrix& jac); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, linalg::BandMatrix& jac); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithoutParamSensitivity); void multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const; void addTimeDerivativeToJacobian(double alpha, linalg::FactorizableBandMatrix& jacDisc); inline const active& columnLength() const CADET_NOEXCEPT { return _colLength; } inline const active& crossSectionArea() const CADET_NOEXCEPT { return _crossSection; } + inline const active& currentVelocity(double pos) const CADET_NOEXCEPT { return _curVelocity; } + inline bool forwardFlow() const CADET_NOEXCEPT { return _curVelocity >= 0.0; } + inline double cellCenter(unsigned int idx) const CADET_NOEXCEPT { return static_cast(_colLength) / _nCol * (idx + 0.5); } + inline double relativeCoordinate(unsigned int idx) const CADET_NOEXCEPT { return (0.5 + idx) / _nCol; } inline const active& currentVelocity() const CADET_NOEXCEPT { return _curVelocity; } inline const active* currentDispersion(const int secIdx) const CADET_NOEXCEPT { return getSectionDependentSlice(_colDispersion, _nComp, secIdx); } // todo delete once DG has its own operator inline const bool dispersionCompIndep() const CADET_NOEXCEPT { return _dispersionCompIndep; } // todo delete once DG has its own operator @@ -98,6 +106,7 @@ class ConvectionDispersionOperatorBase unsigned int jacobianLowerBandwidth() const CADET_NOEXCEPT; unsigned int jacobianUpperBandwidth() const CADET_NOEXCEPT; unsigned int jacobianDiscretizedBandwidth() const CADET_NOEXCEPT; + double inletJacobianFactor() const CADET_NOEXCEPT; bool setParameter(const ParameterId& pId, double value); bool setSensitiveParameter(std::unordered_set& sensParams, const ParameterId& pId, unsigned int adDirection, double adValue); @@ -106,13 +115,7 @@ class ConvectionDispersionOperatorBase protected: template - int residualImpl(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin); - - template - int residualForwardsFlow(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin); - - template - int residualBackwardsFlow(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin); + int residualImpl(const IModel& model, double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin); unsigned int _nComp; //!< Number of components unsigned int _nCol; //!< Number of axial cells @@ -134,6 +137,8 @@ class ConvectionDispersionOperatorBase bool _dispersionCompIndep; //!< Determines whether dispersion is component independent + IParameterParameterDependence* _dispersionDep; + // Indexer functionality // Strides @@ -159,11 +164,110 @@ u c_{\text{in},i}(t) &= u c_i(t,0) - D_{\text{ax},i} \frac{\partial c_i}{\partia \end{align} @f] * Methods are described in @cite VonLieres2010a (WENO, linear solver), and @cite Puttmann2013, @cite Puttmann2016 (forward sensitivities, AD, band compression) * - * This class wraps ConvectionDispersionOperatorBase and provides all the functionality it does. In addition, - * the Jacobian is stored and corresponding functions are provided (assembly, factorization, solution, retrieval). + * This class does not store the Jacobian. It only fills existing matrices given to its residual() functions. + * It assumes that there is no offset to the inlet in the local state vector and that the firsts cell is placed + * directly after the inlet DOFs. + */ +class RadialConvectionDispersionOperatorBase +{ +public: + + RadialConvectionDispersionOperatorBase(); + ~RadialConvectionDispersionOperatorBase() CADET_NOEXCEPT; + + void setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT; + + bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol, unsigned int strideCell); + bool configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters); + bool notifyDiscontinuousSectionTransition(double t, unsigned int secIdx); + + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, linalg::BandMatrix& jac); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, linalg::BandMatrix& jac); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, WithoutParamSensitivity); + + void multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const; + void addTimeDerivativeToJacobian(double alpha, linalg::FactorizableBandMatrix& jacDisc); + + inline const active& columnLength() const CADET_NOEXCEPT { return _colLength; } + inline const active& innerRadius() const CADET_NOEXCEPT { return _innerRadius; } + inline const active& outerRadius() const CADET_NOEXCEPT { return _outerRadius; } + active currentVelocity(double pos) const CADET_NOEXCEPT; + inline bool forwardFlow() const CADET_NOEXCEPT { return _curVelocity >= 0.0; } + + inline double cellCenter(unsigned int idx) const CADET_NOEXCEPT { return static_cast(_cellCenters[idx]); } + inline double relativeCoordinate(unsigned int idx) const CADET_NOEXCEPT { return (0.5 + idx) / _nCol; } + + inline unsigned int nComp() const CADET_NOEXCEPT { return _nComp; } + inline unsigned int nCol() const CADET_NOEXCEPT { return _nCol; } +// inline const Weno& weno() const CADET_NOEXCEPT { return _weno; } + + unsigned int jacobianLowerBandwidth() const CADET_NOEXCEPT; + unsigned int jacobianUpperBandwidth() const CADET_NOEXCEPT; + unsigned int jacobianDiscretizedBandwidth() const CADET_NOEXCEPT; + double inletJacobianFactor() const CADET_NOEXCEPT; + + bool setParameter(const ParameterId& pId, double value); + bool setSensitiveParameter(std::unordered_set& sensParams, const ParameterId& pId, unsigned int adDirection, double adValue); + bool setSensitiveParameterValue(const std::unordered_set& sensParams, const ParameterId& id, double value); + +protected: + + template + int residualImpl(const IModel& model, double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin); + + void equidistantCells(); + + unsigned int _nComp; //!< Number of components + unsigned int _nCol; //!< Number of axial cells + unsigned int _strideCell; //!< Number of elements between the same item in two adjacent cells + + active _colLength; //!< Column length \f$ L \f$ + active _innerRadius; //!< Inner radius + active _outerRadius; //!< Outer radius + + // Section dependent parameters + std::vector _colDispersion; //!< Column dispersion (may be section dependent) \f$ D_{\text{rad}} \f$ + std::vector _velocity; //!< Radial velocity (may be section dependent) \f$ v \f$ + active _curVelocity; //!< Current interstitial velocity \f$ u \f$ in this time section + int _dir; //!< Current flow direction in this time section + + ArrayPool _stencilMemory; //!< Provides memory for the stencil +// double* _wenoDerivatives; //!< Holds derivatives of the WENO scheme +// Weno _weno; //!< The WENO scheme implementation +// double _wenoEpsilon; //!< The @f$ \varepsilon @f$ of the WENO scheme (prevents division by zero) + + bool _dispersionCompIndep; //!< Determines whether dispersion is component independent + + IParameterParameterDependence* _dispersionDep; + + // Grid info + std::vector _cellCenters; + std::vector _cellSizes; + std::vector _cellBounds; + + // Indexer functionality + + // Strides + inline int strideColCell() const CADET_NOEXCEPT { return static_cast(_strideCell); } + inline int strideColComp() const CADET_NOEXCEPT { return 1; } + + // Offsets + inline int offsetC() const CADET_NOEXCEPT { return _nComp; } +}; + + +/** + * @brief Convection dispersion transport operator + * @details This class wraps AxialConvectionDispersionOperatorBase or RadialConvectionDispersionOperatorBase + * and provides all the functionality it does. In addition, the Jacobian is stored and corresponding functions + * are provided (assembly, factorization, solution, retrieval). * * This class assumes that the first cell is offset by the number of components (inlet DOFs) in the global state vector. */ +template class ConvectionDispersionOperator { public: @@ -175,14 +279,14 @@ class ConvectionDispersionOperator void setFlowRates(const active& in, const active& out, const active& colPorosity) CADET_NOEXCEPT; - bool configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int nCol); + bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol); bool configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters); bool notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const AdJacobianParams& adJac); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity); - int residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); - int residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); void prepareADvectors(const AdJacobianParams& adJac) const; void extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset); @@ -198,8 +302,12 @@ class ConvectionDispersionOperator #endif inline const active& columnLength() const CADET_NOEXCEPT { return _baseOp.columnLength(); } - inline const active& crossSectionArea() const CADET_NOEXCEPT { return _baseOp.crossSectionArea(); } - inline const active& currentVelocity() const CADET_NOEXCEPT { return _baseOp.currentVelocity(); } + inline active currentVelocity(double pos) const CADET_NOEXCEPT { return _baseOp.currentVelocity(pos); } + inline bool forwardFlow() const CADET_NOEXCEPT { return _baseOp.forwardFlow(); } + inline double inletJacobianFactor() const CADET_NOEXCEPT { return _baseOp.inletJacobianFactor(); } + + inline double cellCenter(unsigned int idx) const CADET_NOEXCEPT { return _baseOp.cellCenter(idx); } + inline double relativeCoordinate(unsigned int idx) const CADET_NOEXCEPT { return _baseOp.relativeCoordinate(idx); } inline linalg::BandMatrix& jacobian() CADET_NOEXCEPT { return _jacC; } inline const linalg::BandMatrix& jacobian() const CADET_NOEXCEPT { return _jacC; } @@ -225,7 +333,7 @@ class ConvectionDispersionOperator void addTimeDerivativeToJacobian(double alpha); void assembleDiscretizedJacobian(double alpha); - ConvectionDispersionOperatorBase _baseOp; + BaseOperator _baseOp; linalg::BandMatrix _jacC; //!< Jacobian linalg::FactorizableBandMatrix _jacCdisc; //!< Jacobian with time derivatives from BDF method @@ -236,6 +344,12 @@ class ConvectionDispersionOperator inline int offsetC() const CADET_NOEXCEPT { return _baseOp.nComp(); } }; +extern template class ConvectionDispersionOperator; +extern template class ConvectionDispersionOperator; + +typedef ConvectionDispersionOperator AxialConvectionDispersionOperator; +typedef ConvectionDispersionOperator RadialConvectionDispersionOperator; + } // namespace parts } // namespace model } // namespace cadet diff --git a/src/libcadet/model/parts/RadialConvectionDispersionKernel.cpp b/src/libcadet/model/parts/RadialConvectionDispersionKernel.cpp new file mode 100644 index 000000000..1d01e7812 --- /dev/null +++ b/src/libcadet/model/parts/RadialConvectionDispersionKernel.cpp @@ -0,0 +1,168 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 +// ============================================================================= + +/** + * @file + * Implements the kernel of the radial convection dispersion transport operator. + */ + +#include "model/parts/RadialConvectionDispersionKernel.hpp" +#include "Weno.hpp" +#include "linalg/CompressedSparseMatrix.hpp" + +namespace cadet +{ + +namespace model +{ + +namespace parts +{ + +namespace convdisp +{ + +namespace impl +{ + + class DummyStencil + { + public: + DummyStencil() { } + inline double operator[](const int idx) const { return 0.0; } + }; + +} // namespace impl + +void sparsityPatternRadial(linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, Weno& weno) +{ + impl::DummyStencil stencil; + + if (u >= 0.0) + { + for (unsigned int comp = 0; comp < nComp; ++comp) + { + linalg::SparsityPatternRowIterator jac = itBegin + comp; + + // Time derivative + jac.centered(0); + + // Reset WENO output + double vm = 0.0; // reconstructed value + + int wenoOrder = 0; + + // Iterate over all cells + for (unsigned int col = 0; col < nCol; ++col) + { + // ------------------- Dispersion ------------------- + + // Right side, leave out if we're in the last cell (boundary condition) + if (cadet_likely(col < nCol - 1)) + { + // jac.centered(0); + jac.centered(strideCell); + } + + // Left side, leave out if we're in the first cell (boundary condition) + if (cadet_likely(col > 0)) + { + // jac.centered(0); + jac.centered(-strideCell); + } + + // ------------------- Convection ------------------- + + // Add convection through this cell's left face + if (cadet_likely(col > 0)) + { + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + // Note that we have an offset of -1 here (compared to the right cell face below), since + // the reconstructed value depends on the previous stencil (which has now been moved by one cell) + jac.centered((i - wenoOrder) * strideCell); + } + + // Reconstruct concentration on this cell's right face -- we only need the WENO order here + wenoOrder = weno.reconstruct(1e-12, col, nCol, stencil, vm); + + // Right side + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + jac.centered((i - wenoOrder + 1) * strideCell); + + if (cadet_likely(col < nCol - 1)) + jac += strideCell; + } + } + } + else + { + for (unsigned int comp = 0; comp < nComp; ++comp) + { + linalg::SparsityPatternRowIterator jac = itBegin + strideCell * (nCol - 1) + comp; + + // Time derivative + jac.centered(0); + + // Reset WENO output + double vm = 0.0; // reconstructed value + + int wenoOrder = 0; + + // Iterate over all cells (backwards) + // Note that col wraps around to unsigned int's maximum value after 0 + for (unsigned int col = nCol - 1; col < nCol; --col) + { + // ------------------- Dispersion ------------------- + + // Right side, leave out if we're in the first cell (boundary condition) + if (cadet_likely(col < nCol - 1)) + { + // jac.centered(0); + jac.centered(strideCell); + } + + // Left side, leave out if we're in the last cell (boundary condition) + if (cadet_likely(col > 0)) + { + // jac.centered(0); + jac.centered(-strideCell); + } + + // ------------------- Convection ------------------- + + // Add convection through this cell's right face + if (cadet_likely(col < nCol - 1)) + { + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + // Note that we have an offset of +1 here (compared to the left cell face below), since + // the reconstructed value depends on the previous stencil (which has now been moved by one cell) + jac.centered((wenoOrder - i) * strideCell); + } + + // Reconstruct concentration on this cell's left face -- we only need the WENO order here + wenoOrder = weno.reconstruct(1e-12, col, nCol, stencil, vm); + + // Left face + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + jac.centered((wenoOrder - i - 1) * strideCell); + + if (cadet_likely(col > 0)) + jac -= strideCell; + } + } + } +} + +} // namespace convdisp +} // namespace parts +} // namespace model +} // namespace cadet diff --git a/src/libcadet/model/parts/RadialConvectionDispersionKernel.hpp b/src/libcadet/model/parts/RadialConvectionDispersionKernel.hpp new file mode 100644 index 000000000..33e8b5f23 --- /dev/null +++ b/src/libcadet/model/parts/RadialConvectionDispersionKernel.hpp @@ -0,0 +1,399 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 +// ============================================================================= + +/** + * @file + * Implements the kernel of the radial convection dispersion transport operator. + */ + +#ifndef LIBCADET_RADIALCONVECTIONDISPERSIONKERNEL_HPP_ +#define LIBCADET_RADIALCONVECTIONDISPERSIONKERNEL_HPP_ + +#include "AutoDiff.hpp" +#include "Memory.hpp" +#include "Weno.hpp" +#include "Stencil.hpp" +#include "linalg/CompressedSparseMatrix.hpp" +#include "SimulationTypes.hpp" +#include "model/ParameterDependence.hpp" +#include "model/UnitOperation.hpp" + +namespace cadet +{ + +class IModel; + +namespace model +{ + +namespace parts +{ + +namespace convdisp +{ + +template +struct RadialFlowParameters +{ + T u; + active const* d_rad; + active const* cellCenters; //!< Midpoints of the cells + active const* cellSizes; //!< Cell sizes + active const* cellBounds; //!< Cell boundaries + ArrayPool* stencilMemory; //!< Provides memory for the stencil + int strideCell; + unsigned int nComp; + unsigned int nCol; + unsigned int offsetToInlet; //!< Offset to the first component of the inlet DOFs in the local state vector + unsigned int offsetToBulk; //!< Offset to the first component of the first bulk cell in the local state vector + IParameterParameterDependence* parDep; + const IModel& model; +}; + + +namespace impl +{ + template + int residualForwardsRadialFlow(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const RadialFlowParameters& p) + { + // The stencil caches parts of the state vector for better spatial coherence + typedef CachingStencil StencilType; +// StencilType stencil(std::max(p.weno->stencilSize(), 3u), *p.stencilMemory, std::max(p.weno->order() - 1, 1)); + StencilType stencil(std::max(1u, 3u), *p.stencilMemory, std::max(1 - 1, 1)); + + // The RowIterator is always centered on the main diagonal. + // This means that jac[0] is the main diagonal, jac[-1] is the first lower diagonal, + // and jac[1] is the first upper diagonal. We can also access the rows from left to + // right beginning with the last lower diagonal moving towards the main diagonal and + // continuing to the last upper diagonal by using the native() method. + RowIteratorType jac; + + ResidualType* const resBulk = res + p.offsetToBulk; + StateType const* const yBulk = y + p.offsetToBulk; + + for (unsigned int comp = 0; comp < p.nComp; ++comp) + { + if (wantJac) + jac = jacBegin + comp; + + ResidualType* const resBulkComp = resBulk + comp; + StateType const* const yBulkComp = yBulk + comp; + + // Add time derivative to each cell + if (yDot) + { + double const* const yDotBulkComp = yDot + p.offsetToBulk + comp; + for (unsigned int col = 0; col < p.nCol; ++col) + resBulkComp[col * p.strideCell] = yDotBulkComp[col * p.strideCell]; + } + else + { + for (unsigned int col = 0; col < p.nCol; ++col) + resBulkComp[col * p.strideCell] = 0.0; + } + + // Fill stencil (left side with zeros, right side with states) + for (int i = -std::max(1, 2) + 1; i < 0; ++i) + stencil[i] = 0.0; + for (int i = 0; i < std::max(1, 2); ++i) + stencil[i] = yBulkComp[i * p.strideCell]; + + // Reset WENO output + StateType vm(0.0); // reconstructed value +// if (wantJac) +// std::fill(p.wenoDerivatives, p.wenoDerivatives + p.weno->stencilSize(), 0.0); + + int wenoOrder = 1; + const ParamType d_rad = static_cast(p.d_rad[comp]); + + // Iterate over all cells + for (unsigned int col = 0; col < p.nCol; ++col) + { + const ParamType denom = static_cast(p.cellCenters[col]) * static_cast(p.cellSizes[col]); + + // ------------------- Dispersion ------------------- + + // Right side, leave out if we're in the last cell (boundary condition) + if (cadet_likely(col < p.nCol - 1)) + { + const double relCoord = (static_cast(p.cellBounds[col+1]) - static_cast(p.cellBounds[0])) / (static_cast(p.cellBounds[p.nCol - 1]) - static_cast(p.cellBounds[0])); + const ParamType d_rad_right = d_rad * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u) / static_cast(p.cellBounds[col+1])); + resBulkComp[col * p.strideCell] -= d_rad_right * static_cast(p.cellBounds[col+1]) / denom * (stencil[1] - stencil[0]) / (static_cast(p.cellCenters[col+1]) - static_cast(p.cellCenters[col])); + // Jacobian entries + if (wantJac) + { + const double val = static_cast(d_rad_right) * static_cast(p.cellBounds[col+1]) / static_cast(denom) / (static_cast(p.cellCenters[col+1]) - static_cast(p.cellCenters[col])); + jac[0] += val; + jac[p.strideCell] -= val; + } + } + + // Left side, leave out if we're in the first cell (boundary condition) + if (cadet_likely(col > 0)) + { + const double relCoord = (static_cast(p.cellBounds[col]) - static_cast(p.cellBounds[0])) / (static_cast(p.cellBounds[p.nCol - 1]) - static_cast(p.cellBounds[0])); + const ParamType d_rad_left = d_rad * p.parDep->getValue(p.model, ColumnPosition{ relCoord, 0.0, 0.0 }, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u) / static_cast(p.cellBounds[col])); + resBulkComp[col * p.strideCell] -= d_rad_left * static_cast(p.cellBounds[col]) / denom * (stencil[-1] - stencil[0]) / (static_cast(p.cellCenters[col]) - static_cast(p.cellCenters[col-1])); + // Jacobian entries + if (wantJac) + { + const double val = static_cast(d_rad_left) * static_cast(p.cellBounds[col]) / static_cast(denom) / (static_cast(p.cellCenters[col]) - static_cast(p.cellCenters[col-1])); + jac[0] += val; + jac[-p.strideCell] -= val; + } + } + + // ------------------- Convection ------------------- + + // Add convection through this cell's left face + if (cadet_likely(col > 0)) + { + // Remember that vm still contains the reconstructed value of the previous + // cell's *right* face, which is identical to this cell's *left* face! + resBulkComp[col * p.strideCell] -= p.u / denom * vm; + + // Jacobian entries + if (wantJac) + { + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + // Note that we have an offset of -1 here (compared to the right cell face below), since + // the reconstructed value depends on the previous stencil (which has now been moved by one cell) + jac[(i - wenoOrder) * p.strideCell] -= static_cast(p.u) / static_cast(denom); + } + } + else + { + // In the first cell we need to apply the boundary condition: inflow concentration + resBulkComp[col * p.strideCell] -= p.u / denom * y[p.offsetToInlet + comp]; + } + + // Reconstruct concentration on this cell's right face + if (wantJac) + { + wenoOrder = 1; + vm = stencil[0]; + // wenoOrder = p.weno->template reconstruct(p.wenoEpsilon, col, p.nCol, stencil, vm, p.wenoDerivatives); + } + else + { + wenoOrder = 1; + vm = stencil[0]; + // wenoOrder = p.weno->template reconstruct(p.wenoEpsilon, col, p.nCol, stencil, vm); + } + + // Right side + resBulkComp[col * p.strideCell] += p.u / denom * vm; + // Jacobian entries + if (wantJac) + { + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + jac[(i - wenoOrder + 1) * p.strideCell] += static_cast(p.u) / static_cast(denom); + } + + // Update stencil + const unsigned int shift = std::max(1, 2); + if (cadet_likely(col + shift < p.nCol)) + stencil.advance(yBulkComp[(col + shift) * p.strideCell]); + else + stencil.advance(0.0); + + if (wantJac) + { + if (cadet_likely(col < p.nCol - 1)) + jac += p.strideCell; + } + } + } + + // Film diffusion with flux into beads is added in residualFlux() function + + return 0; + } + + template + int residualBackwardsRadialFlow(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const RadialFlowParameters& p) + { + // The stencil caches parts of the state vector for better spatial coherence + typedef CachingStencil StencilType; +// StencilType stencil(std::max(p.weno->stencilSize(), 3u), *p.stencilMemory, std::max(p.weno->order() - 1, 1)); + StencilType stencil(std::max(1u, 3u), *p.stencilMemory, std::max(1 - 1, 1)); + + // The RowIterator is always centered on the main diagonal. + // This means that jac[0] is the main diagonal, jac[-1] is the first lower diagonal, + // and jac[1] is the first upper diagonal. We can also access the rows from left to + // right beginning with the last lower diagonal moving towards the main diagonal and + // continuing to the last upper diagonal by using the native() method. + RowIteratorType jac; + + ResidualType* const resBulk = res + p.offsetToBulk; + StateType const* const yBulk = y + p.offsetToBulk; + + for (unsigned int comp = 0; comp < p.nComp; ++comp) + { + if (wantJac) + jac = jacBegin + p.strideCell * (p.nCol - 1) + comp; + + ResidualType* const resBulkComp = resBulk + comp; + StateType const* const yBulkComp = yBulk + comp; + + // Add time derivative to each cell + if (yDot) + { + double const* const yDotBulkComp = yDot + p.offsetToBulk + comp; + for (unsigned int col = 0; col < p.nCol; ++col) + resBulkComp[col * p.strideCell] = yDotBulkComp[col * p.strideCell]; + } + else + { + for (unsigned int col = 0; col < p.nCol; ++col) + resBulkComp[col * p.strideCell] = 0.0; + } + + // Fill stencil (left side with zeros, right side with states) + for (int i = -std::max(1, 2) + 1; i < 0; ++i) + stencil[i] = 0.0; + for (int i = 0; i < std::max(1, 2); ++i) + stencil[i] = yBulkComp[(p.nCol - static_cast(i) - 1) * p.strideCell]; + + // Reset WENO output + StateType vm(0.0); // reconstructed value +// if (wantJac) +// std::fill(p.wenoDerivatives, p.wenoDerivatives + p.weno->stencilSize(), 0.0); + + int wenoOrder = 1; + const ParamType d_rad = static_cast(p.d_rad[comp]); + + // Iterate over all cells (backwards) + // Note that col wraps around to unsigned int's maximum value after 0 + for (unsigned int col = p.nCol - 1; col < p.nCol; --col) + { + const ParamType denom = static_cast(p.cellCenters[col]) * static_cast(p.cellSizes[col]); + + // ------------------- Dispersion ------------------- + + // Right side, leave out if we're in the first cell (boundary condition) + if (cadet_likely(col < p.nCol - 1)) + { + const double relCoord = (static_cast(p.cellBounds[col+1]) - static_cast(p.cellBounds[0])) / (static_cast(p.cellBounds[p.nCol - 1]) - static_cast(p.cellBounds[0])); + const ParamType d_rad_right = d_rad * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u) / static_cast(p.cellBounds[col+1])); + resBulkComp[col * p.strideCell] -= d_rad_right * static_cast(p.cellBounds[col+1]) / denom * (stencil[-1] - stencil[0]) / (static_cast(p.cellCenters[col+1]) - static_cast(p.cellCenters[col])); + // Jacobian entries + if (wantJac) + { + const double val = static_cast(d_rad_right) * static_cast(p.cellBounds[col+1]) / static_cast(denom) / (static_cast(p.cellCenters[col+1]) - static_cast(p.cellCenters[col])); + jac[0] += val; + jac[p.strideCell] -= val; + } + } + + // Left side, leave out if we're in the last cell (boundary condition) + if (cadet_likely(col > 0)) + { + const double relCoord = (static_cast(p.cellBounds[col]) - static_cast(p.cellBounds[0])) / (static_cast(p.cellBounds[p.nCol - 1]) - static_cast(p.cellBounds[0])); + const ParamType d_rad_left = d_rad * p.parDep->getValue(p.model, ColumnPosition{relCoord, 0.0, 0.0}, comp, ParTypeIndep, BoundStateIndep, static_cast(p.u) / static_cast(p.cellBounds[col])); + resBulkComp[col * p.strideCell] -= d_rad_left * static_cast(p.cellBounds[col]) / denom * (stencil[1] - stencil[0]) / (static_cast(p.cellCenters[col-1]) - static_cast(p.cellCenters[col])); + // Jacobian entries + if (wantJac) + { + const double val = static_cast(d_rad_left) * static_cast(p.cellBounds[col]) / static_cast(denom) / (static_cast(p.cellCenters[col-1]) - static_cast(p.cellCenters[col])); + jac[0] += val; + jac[-p.strideCell] -= val; + } + } + + // ------------------- Convection ------------------- + + // Add convection through this cell's right face + if (cadet_likely(col < p.nCol - 1)) + { + // Remember that vm still contains the reconstructed value of the previous + // cell's *left* face, which is identical to this cell's *right* face! + resBulkComp[col * p.strideCell] += p.u / denom * vm; + + // Jacobian entries + if (wantJac) + { + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + // Note that we have an offset of +1 here (compared to the left cell face below), since + // the reconstructed value depends on the previous stencil (which has now been moved by one cell) + jac[(wenoOrder - i) * p.strideCell] += static_cast(p.u) / static_cast(denom); + } + } + else + { + // In the last cell (z = L) we need to apply the boundary condition: inflow concentration + resBulkComp[col * p.strideCell] += p.u / denom * y[p.offsetToInlet + comp]; + } + + // Reconstruct concentration on this cell's left face + if (wantJac) + { + wenoOrder = 1; + vm = stencil[0]; +// wenoOrder = p.weno->template reconstruct(p.wenoEpsilon, col, p.nCol, stencil, vm, p.wenoDerivatives); + } + else + { + wenoOrder = 1; + vm = stencil[0]; +// wenoOrder = p.weno->template reconstruct(p.wenoEpsilon, col, p.nCol, stencil, vm); + } + + // Left face + resBulkComp[col * p.strideCell] -= p.u / denom * vm; + // Jacobian entries + if (wantJac) + { + for (int i = 0; i < 2 * wenoOrder - 1; ++i) + jac[(wenoOrder - i - 1) * p.strideCell] -= static_cast(p.u) / static_cast(denom); + } + + // Update stencil (be careful because of wrap-around, might cause reading memory very far away [although never used]) + const unsigned int shift = std::max(1, 2); + if (cadet_likely(col - shift < p.nCol)) + stencil.advance(yBulkComp[(col - shift) * p.strideCell]); + else + stencil.advance(0.0); + + if (wantJac) + { + if (cadet_likely(col > 0)) + jac -= p.strideCell; + } + } + } + + // Film diffusion with flux into beads is added in residualFlux() function + + return 0; + } + +} // namespace impl + + +template +int residualKernelRadial(const SimulationTime& simTime, StateType const* y, double const* yDot, ResidualType* res, RowIteratorType jacBegin, const RadialFlowParameters& p) +{ + if (p.u >= 0.0) + return impl::residualForwardsRadialFlow(simTime, y, yDot, res, jacBegin, p); + else + return impl::residualBackwardsRadialFlow(simTime, y, yDot, res, jacBegin, p); +} + +void sparsityPatternRadial(linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, Weno& weno); + +} // namespace convdisp +} // namespace parts +} // namespace model +} // namespace cadet + +#endif // LIBCADET_RADIALCONVECTIONDISPERSIONKERNEL_HPP_ diff --git a/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.cpp b/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.cpp index 0a6026b26..adefb8726 100644 --- a/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.cpp +++ b/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.cpp @@ -19,7 +19,10 @@ #include "SensParamUtil.hpp" #include "SimulationTypes.hpp" #include "linalg/CompressedSparseMatrix.hpp" -#include "model/parts/ConvectionDispersionKernel.hpp" +#include "model/parts/AxialConvectionDispersionKernel.hpp" +#include "model/ParameterDependence.hpp" +#include "ConfigurationHelper.hpp" + #ifdef SUPERLU_FOUND #include "linalg/SuperLUSparseMatrix.hpp" @@ -654,7 +657,7 @@ class TwoDimensionalConvectionDispersionOperator::DenseDirectSolver : public Two * @brief Creates a TwoDimensionalConvectionDispersionOperator */ TwoDimensionalConvectionDispersionOperator::TwoDimensionalConvectionDispersionOperator() : _colPorosities(0), _dir(0), _stencilMemory(sizeof(active) * Weno::maxStencilSize()), - _wenoDerivatives(new double[Weno::maxStencilSize()]), _weno(), _linearSolver(nullptr) + _wenoDerivatives(new double[Weno::maxStencilSize()]), _weno(), _linearSolver(nullptr), _dispersionDep(nullptr) { } @@ -662,6 +665,7 @@ TwoDimensionalConvectionDispersionOperator::~TwoDimensionalConvectionDispersionO { delete[] _wenoDerivatives; delete _linearSolver; + delete _dispersionDep; } /** @@ -673,13 +677,16 @@ TwoDimensionalConvectionDispersionOperator::~TwoDimensionalConvectionDispersionO * @param [in] dynamicReactions Determines whether the sparsity pattern accounts for dynamic reactions * @return @c true if configuration went fine, @c false otherwise */ -bool TwoDimensionalConvectionDispersionOperator::configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int nCol, unsigned int nRad, bool dynamicReactions) +bool TwoDimensionalConvectionDispersionOperator::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol, unsigned int nRad, bool dynamicReactions) { _nComp = nComp; _nCol = nCol; _nRad = nRad; _hasDynamicReactions = dynamicReactions; + // TODO: Add support for parameter dependent dispersion + _dispersionDep = helper.createParameterParameterDependence("CONSTANT_ONE"); + paramProvider.pushScope("discretization"); // Read WENO settings and apply them @@ -944,40 +951,40 @@ const active& TwoDimensionalConvectionDispersionOperator::radialDispersion(unsig * @param [in] wantJac Determines whether the Jacobian is computed or not * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error */ -int TwoDimensionalConvectionDispersionOperator::residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity) +int TwoDimensionalConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity) { if (wantJac) - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); else - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); } -int TwoDimensionalConvectionDispersionOperator::residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity) +int TwoDimensionalConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity) { if (wantJac) - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); else - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); } -int TwoDimensionalConvectionDispersionOperator::residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) +int TwoDimensionalConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) { if (wantJac) - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); else - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); } -int TwoDimensionalConvectionDispersionOperator::residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) +int TwoDimensionalConvectionDispersionOperator::residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity) { if (wantJac) - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); else - return residualImpl(t, secIdx, y, yDot, res); + return residualImpl(model, t, secIdx, y, yDot, res); } template -int TwoDimensionalConvectionDispersionOperator::residualImpl(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res) +int TwoDimensionalConvectionDispersionOperator::residualImpl(const IModel& model, double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res) { if (wantJac) { @@ -991,7 +998,7 @@ int TwoDimensionalConvectionDispersionOperator::residualImpl(double t, unsigned { active const* const d_c = getSectionDependentSlice(_axialDispersion, _nRad * _nComp, secIdx) + i * _nComp; - convdisp::FlowParameters fp{ + convdisp::AxialFlowParameters fp{ static_cast(_curVelocity[i]), d_c, h, @@ -1003,13 +1010,15 @@ int TwoDimensionalConvectionDispersionOperator::residualImpl(double t, unsigned _nComp, _nCol, _nComp * i, // Offset to the first component of the inlet DOFs in the local state vector - _nComp * (_nRad + i) // Offset to the first component of the first bulk cell in the local state vector + _nComp * (_nRad + i), // Offset to the first component of the first bulk cell in the local state vector + _dispersionDep, + model }; if (wantJac) - convdisp::residualKernel(SimulationTime{t, secIdx}, y, yDot, res, _jacC.row(i * _nComp), fp); + convdisp::residualKernelAxial(SimulationTime{t, secIdx}, y, yDot, res, _jacC.row(i * _nComp), fp); else - convdisp::residualKernel(SimulationTime{t, secIdx}, y, yDot, res, _jacC.row(i * _nComp), fp); + convdisp::residualKernelAxial(SimulationTime{t, secIdx}, y, yDot, res, _jacC.row(i * _nComp), fp); } // Handle radial dispersion @@ -1112,7 +1121,7 @@ void TwoDimensionalConvectionDispersionOperator::setSparsityPattern() // Handle convection, axial dispersion (WENO) for (unsigned int i = 0; i < _nRad; ++i) - cadet::model::parts::convdisp::sparsityPattern(pattern.row(i * _nComp), _nComp, _nCol, _nComp * _nRad, static_cast(_curVelocity[i]), _weno); + cadet::model::parts::convdisp::sparsityPatternAxial(pattern.row(i * _nComp), _nComp, _nCol, _nComp * _nRad, static_cast(_curVelocity[i]), _weno); // Handle radial dispersion if (_nRad > 1) diff --git a/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.hpp b/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.hpp index b90db84d3..960cfa206 100644 --- a/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.hpp +++ b/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperator.hpp @@ -34,10 +34,14 @@ namespace cadet { class IParameterProvider; +class IModel; +class IConfigHelper; namespace model { +class IParameterParameterDependence; + namespace parts { @@ -70,14 +74,14 @@ class TwoDimensionalConvectionDispersionOperator void setFlowRates(int compartment, const active& in, const active& out) CADET_NOEXCEPT; void setFlowRates(active const* in, active const* out) CADET_NOEXCEPT; - bool configureModelDiscretization(IParameterProvider& paramProvider, unsigned int nComp, unsigned int nCol, unsigned int nRad, bool dynamicReactions); + bool configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper, unsigned int nComp, unsigned int nCol, unsigned int nRad, bool dynamicReactions); bool configure(UnitOpIdx unitOpIdx, IParameterProvider& paramProvider, std::unordered_map& parameters); bool notifyDiscontinuousSectionTransition(double t, unsigned int secIdx); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity); - int residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity); - int residual(double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); - int residual(double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, double* res, bool wantJac, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithoutParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, active const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); + int residual(const IModel& model, double t, unsigned int secIdx, double const* y, double const* yDot, active* res, bool wantJac, WithParamSensitivity); bool solveTimeDerivativeSystem(const SimulationTime& simTime, double* const rhs); void multiplyWithDerivativeJacobian(const SimulationTime& simTime, double const* sDot, double* ret) const; @@ -118,7 +122,7 @@ class TwoDimensionalConvectionDispersionOperator void assembleDiscretizedJacobian(double alpha); template - int residualImpl(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res); + int residualImpl(const IModel& model, double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res); void setSparsityPattern(); @@ -177,6 +181,8 @@ class TwoDimensionalConvectionDispersionOperator linalg::CompressedSparseMatrix _jacC; //!< Jacobian LinearSolver* _linearSolver; //!< Solves linear system with time discretized Jacobian + + IParameterParameterDependence* _dispersionDep; }; } // namespace parts diff --git a/src/tools/ToolsHelper.hpp b/src/tools/ToolsHelper.hpp index 153a95ac1..f8d83e38f 100644 --- a/src/tools/ToolsHelper.hpp +++ b/src/tools/ToolsHelper.hpp @@ -75,6 +75,14 @@ inline void parseUnitType(std::string& unitType) unitType = "GENERAL_RATE_MODEL_2D"; } +inline void parseUnitType(std::string& unitType, bool radialFlow) +{ + parseUnitType(unitType); + + if (radialFlow) + unitType = "RADIAL_" + unitType; +} + inline void addSensitivitiyParserToCmdLine(TCLAP::CmdLine& cmd, std::vector& sensitivities) { cmd >> (new TCLAP::MultiArg("S", "sens", diff --git a/src/tools/createLWE.cpp b/src/tools/createLWE.cpp index adb541efb..43baf037f 100644 --- a/src/tools/createLWE.cpp +++ b/src/tools/createLWE.cpp @@ -33,6 +33,8 @@ struct ProgramOptions double endTime; double constAlg; double stddevAlg; + bool radialFlow; + bool velocityDependence; bool reverseFlow; bool adJacobian; int nPar; @@ -64,6 +66,8 @@ int main(int argc, char** argv) cmd >> (new TCLAP::ValueArg("c", "constAlg", "Set all algebraic variables to constant value", false, nanVal, "Value"))->storeIn(&opts.constAlg); cmd >> (new TCLAP::ValueArg("s", "stddevAlg", "Perturb algebraic variables with normal variates", false, nanVal, "Value"))->storeIn(&opts.stddevAlg); cmd >> (new TCLAP::SwitchArg("", "reverseFlow", "Reverse the flow for column"))->storeIn(&opts.reverseFlow); + cmd >> (new TCLAP::SwitchArg("", "radialFlow", "Use radial flow column"))->storeIn(&opts.radialFlow); + cmd >> (new TCLAP::SwitchArg("", "velDep", "Use velocity dependent dispersion and film diffusion"))->storeIn(&opts.velocityDependence); cmd >> (new TCLAP::ValueArg("", "rad", "Number of radial cells (default: 3)", false, 3, "Value"))->storeIn(&opts.nRad); cmd >> (new TCLAP::ValueArg("", "parTypes", "Number of particle types (default: 1)", false, 1, "Value"))->storeIn(&opts.nParType); addMiscToCmdLine(cmd, opts); @@ -83,7 +87,7 @@ int main(int argc, char** argv) writer.openFile(opts.fileName, "co"); writer.pushGroup("input"); - parseUnitType(opts.unitType); + parseUnitType(opts.unitType, opts.radialFlow); const bool isGRM2D = (opts.unitType == "GENERAL_RATE_MODEL_2D"); // Model @@ -100,9 +104,9 @@ int main(int argc, char** argv) // Transport if (!opts.reverseFlow) - writer.scalar("VELOCITY", 5.75e-4); + writer.scalar("VELOCITY", 1.0); else - writer.scalar("VELOCITY", -5.75e-4); + writer.scalar("VELOCITY", -1.0); writer.scalar("COL_DISPERSION", 5.75e-8); writer.scalar("COL_DISPERSION_RADIAL", 1e-6); @@ -135,9 +139,26 @@ int main(int argc, char** argv) writer.vector("PAR_SURFDIFFUSION", 4, parSurfDiff); } + if (opts.velocityDependence) + { + writer.scalar("COL_DISPERSION_DEP", "POWER_LAW"); + writer.scalar("COL_DISPERSION_DEP_BASE", 1.25); + writer.scalar("COL_DISPERSION_DEP_EXPONENT", 1.0); + + writer.scalar("FILM_DIFFUSION_DEP", "POWER_LAW"); + writer.scalar("FILM_DIFFUSION_DEP_BASE", 1.25); + writer.scalar("FILM_DIFFUSION_DEP_EXPONENT", 1.0); + } + // Geometry - writer.scalar("COL_LENGTH", 0.014); + if (opts.radialFlow) + writer.scalar("COL_LENGTH", 0.0014); + else + writer.scalar("COL_LENGTH", 0.014); writer.scalar("COL_RADIUS", 0.01); + writer.scalar("COL_RADIUS_INNER", 0.01); + writer.scalar("COL_RADIUS_OUTER", 0.04); + writer.scalar("CROSS_SECTION_AREA", 0.0003141592653589793); writer.scalar("COL_POROSITY", 0.37); const double par_radius = 4.5e-5; const double par_coreradius = 0.0; @@ -168,7 +189,7 @@ int main(int argc, char** argv) } else { - writer.scalar("PAR_GEOM", std::string("SPHERE")); + writer.scalar("PAR_GEOM", std::string("SPHERE")); writer.scalar("PAR_RADIUS", par_radius); writer.scalar("PAR_CORERADIUS", par_coreradius); writer.scalar("PAR_POROSITY", par_porosity); @@ -447,12 +468,12 @@ int main(int argc, char** argv) { // Connection list is 1x7 since we have 1 connection between // the two unit operations (and we need to have 7 columns) - const double connMatrix[] = {1, 0, -1, -1, -1, -1, 1.0}; + const double connMatrix[] = {1, 0, -1, -1, -1, -1, 6.683738370512285e-8}; // Connections: From unit operation 1 port -1 (i.e., all ports) // to unit operation 0 port -1 (i.e., all ports), // connect component -1 (i.e., all components) // to component -1 (i.e., all components) with - // a flow rate of 1.0 + // a flow rate of 6.683738370512285e-8 m^3/s writer.vector("CONNECTIONS", 7, connMatrix); } diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 0a24bd8c7..acf576df2 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -31,6 +31,25 @@ endif() add_executable(testLogging testLogging.cpp) target_link_libraries(testLogging PRIVATE CADET::CompileOptions) +if (NOT WIN32) + add_executable(testRadialKernel testRadialKernel.cpp TimeIntegrator.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/Logging.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/AutoDiff.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/paramdep/ParameterDependenceBase.cpp) + target_link_libraries(testRadialKernel PRIVATE CADET::CompileOptions CADET::AD SUNDIALS::sundials_idas ${SUNDIALS_NVEC_TARGET} ) + target_include_directories(testRadialKernel PRIVATE ${CMAKE_SOURCE_DIR}/test ${CMAKE_BINARY_DIR}/src/libcadet) + + if (ENABLE_GRM_2D) + target_include_directories(testRadialKernel PRIVATE ${CMAKE_BINARY_DIR}/src/libcadet) + if (SUPERLU_FOUND) + target_link_libraries(testRadialKernel PRIVATE SuperLU::SuperLU) + endif() + if (UMFPACK_FOUND) + target_link_libraries(testRadialKernel PRIVATE UMFPACK::UMFPACK) + endif() + endif() + + list(APPEND TEST_NONLINALG_TARGETS testRadialKernel) + list(APPEND TEST_HDF5_TARGETS testRadialKernel) +endif() + # CATCH unit tests set(LIBCADET_SHARED_LIBFILE "$") @@ -44,6 +63,7 @@ endif() add_executable(testRunner testRunner.cpp JsonTestModels.cpp ColumnTests.cpp UnitOperationTests.cpp SimHelper.cpp ParticleHelper.cpp GeneralRateModel.cpp GeneralRateModel2D.cpp LumpedRateModelWithPores.cpp LumpedRateModelWithoutPores.cpp LumpedRateModelWithoutPoresDG.cpp + RadialGeneralRateModel.cpp RadialLumpedRateModelWithPores.cpp RadialLumpedRateModelWithoutPores.cpp CSTR-Residual.cpp CSTR-Simulation.cpp ConvectionDispersionOperator.cpp CellKernelTests.cpp @@ -97,8 +117,7 @@ endforeach() # --------------------------------------------------- - -if(WIN32) +if (WIN32) add_executable(testCAPIv1 "${CMAKE_CURRENT_BINARY_DIR}/Paths_$.cpp" testCAPIv1.cpp) target_include_directories(testCAPIv1 PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/ThirdParty/json) endif() diff --git a/test/ColumnTests.cpp b/test/ColumnTests.cpp index b7e9d3990..aae9d6c4c 100644 --- a/test/ColumnTests.cpp +++ b/test/ColumnTests.cpp @@ -301,7 +301,8 @@ namespace column pp.pushScope("discretization"); nlohmann::json discretization = setupJson["model"]["unit_" + unitID]["discretization"]; discretization["NBOUND"] = pp.getIntArray("NBOUND"); // note: in the future this might be included somewhere else in the setup as its part of the model - discretization["RECONSTRUCTION"] = pp.getString("RECONSTRUCTION"); + if (pp.exists("RECONSTRUCTION")) + discretization["RECONSTRUCTION"] = pp.getString("RECONSTRUCTION"); discretization["USE_ANALYTIC_JACOBIAN"] = pp.getInt("USE_ANALYTIC_JACOBIAN"); if (pp.exists("GS_TYPE")) discretization["GS_TYPE"] = pp.getInt("GS_TYPE"); @@ -315,13 +316,16 @@ namespace column discretization["PAR_DISC_TYPE"] = pp.getStringArray("PAR_DISC_TYPE"); if (pp.exists("PAR_GEOM")) // note: in the future this might be included somewhere else in the setup as its part of the model discretization["PAR_GEOM"] = pp.getStringArray("PAR_GEOM"); - pp.pushScope("weno"); - nlohmann::json weno; - weno["WENO_ORDER"] = pp.getInt("WENO_ORDER"); - weno["WENO_EPS"] = pp.getDouble("WENO_EPS"); - weno["BOUNDARY_MODEL"] = pp.getInt("BOUNDARY_MODEL"); - discretization["weno"] = weno; - pp.popScope(); + if (pp.exists("weno")) + { + pp.pushScope("weno"); + nlohmann::json weno; + weno["WENO_ORDER"] = pp.getInt("WENO_ORDER"); + weno["WENO_EPS"] = pp.getDouble("WENO_EPS"); + weno["BOUNDARY_MODEL"] = pp.getInt("BOUNDARY_MODEL"); + discretization["weno"] = weno; + pp.popScope(); + } setupJson["model"]["unit_" + unitID]["discretization"] = discretization; pp.popScope(); pp.popScope(); @@ -686,7 +690,7 @@ namespace column } } - void testJacobianAD(cadet::JsonParameterProvider& jpp) + void testJacobianAD(cadet::JsonParameterProvider& jpp, const double absTolFDpattern) { cadet::IModelBuilder* const mb = cadet::createModelBuilder(); REQUIRE(nullptr != mb); @@ -730,8 +734,8 @@ namespace column std::fill(jacDir.begin(), jacDir.end(), 0.0); // Compare Jacobians - cadet::test::checkJacobianPatternFD(unitAna, unitAD, y.data(), nullptr, jacDir.data(), jacCol1.data(), jacCol2.data(), tls); - cadet::test::checkJacobianPatternFD(unitAna, unitAna, y.data(), nullptr, jacDir.data(), jacCol1.data(), jacCol2.data(), tls); + cadet::test::checkJacobianPatternFD(unitAna, unitAD, y.data(), nullptr, jacDir.data(), jacCol1.data(), jacCol2.data(), tls, absTolFDpattern); + cadet::test::checkJacobianPatternFD(unitAna, unitAna, y.data(), nullptr, jacDir.data(), jacCol1.data(), jacCol2.data(), tls, absTolFDpattern); cadet::test::compareJacobian(unitAna, unitAD, nullptr, nullptr, jacDir.data(), jacCol1.data(), jacCol2.data()); // cadet::test::compareJacobianFD(unitAna, unitAD, y.data(), nullptr, jacDir.data(), jacCol1.data(), jacCol2.data()); @@ -799,12 +803,24 @@ namespace column cadet::test::compareJacobian(unitAna, unitAD, nullptr, nullptr, jacDir.data(), jacCol1.data(), jacCol2.data()); // cadet::test::compareJacobianFD(unitAna, unitAD, y.data(), nullptr, jacDir.data(), jacCol1.data(), jacCol2.data()); - // Reverse flow - const bool paramSet = unitAna->setParameter(cadet::makeParamId(cadet::hashString("VELOCITY"), 0, cadet::CompIndep, cadet::ParTypeIndep, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep), -jpp.getDouble("VELOCITY")); - REQUIRE(paramSet); - // Reverse flow - const bool paramSet2 = unitAD->setParameter(cadet::makeParamId(cadet::hashString("VELOCITY"), 0, cadet::CompIndep, cadet::ParTypeIndep, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep), -jpp.getDouble("VELOCITY")); - REQUIRE(paramSet2); + if (uoType.substr(0, 6) == "RADIAL") + { + // Reverse flow + const bool paramSet = unitAna->setParameter(cadet::makeParamId(cadet::hashString("VELOCITY_COEFF"), 0, cadet::CompIndep, cadet::ParTypeIndep, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep), -jpp.getDouble("VELOCITY_COEFF")); + REQUIRE(paramSet); + // Reverse flow + const bool paramSet2 = unitAD->setParameter(cadet::makeParamId(cadet::hashString("VELOCITY_COEFF"), 0, cadet::CompIndep, cadet::ParTypeIndep, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep), -jpp.getDouble("VELOCITY_COEFF")); + REQUIRE(paramSet2); + } + else + { + // Reverse flow + const bool paramSet = unitAna->setParameter(cadet::makeParamId(cadet::hashString("VELOCITY"), 0, cadet::CompIndep, cadet::ParTypeIndep, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep), -jpp.getDouble("VELOCITY")); + REQUIRE(paramSet); + // Reverse flow + const bool paramSet2 = unitAD->setParameter(cadet::makeParamId(cadet::hashString("VELOCITY"), 0, cadet::CompIndep, cadet::ParTypeIndep, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep), -jpp.getDouble("VELOCITY")); + REQUIRE(paramSet2); + } // Setup unitAna->notifyDiscontinuousSectionTransition(0.0, 0u, {y.data(), nullptr}, noAdParams); diff --git a/test/ColumnTests.hpp b/test/ColumnTests.hpp index 4a811d41a..72457beb6 100644 --- a/test/ColumnTests.hpp +++ b/test/ColumnTests.hpp @@ -125,8 +125,9 @@ namespace column * @brief Checks the full Jacobian against AD and FD pattern switching * @details Checks the analytic Jacobian against the AD Jacobian and checks both against the FD pattern. * @param [in] jpp Configured column model + * @param [in] absTolFDpattern absolute tolerance when comparing the sign in the FD Jacobian pattern */ - void testJacobianAD(cadet::JsonParameterProvider& jpp); + void testJacobianAD(cadet::JsonParameterProvider& jpp, const double absTolFDpattern = 0.0); /** * @brief Checks the full Jacobian against AD and FD pattern switching in case of variable surface diffusion coefficient diff --git a/test/ConvectionDispersionOperator.cpp b/test/ConvectionDispersionOperator.cpp index 626711c47..475c243a1 100644 --- a/test/ConvectionDispersionOperator.cpp +++ b/test/ConvectionDispersionOperator.cpp @@ -15,18 +15,23 @@ #include "cadet/cadet.hpp" #include "model/parts/ConvectionDispersionOperator.hpp" -#include "model/parts/ConvectionDispersionKernel.hpp" +#include "model/parts/AxialConvectionDispersionKernel.hpp" +#include "model/parts/RadialConvectionDispersionKernel.hpp" #include "Weno.hpp" #include "AdUtils.hpp" #include "SimulationTypes.hpp" +#include "ModelBuilderImpl.hpp" +#include "ParameterDependenceFactory.hpp" #include "JsonTestModels.hpp" #include "ColumnTests.hpp" #include "JacobianHelper.hpp" #include "Utils.hpp" +#include "Dummies.hpp" #include #include +#include namespace { @@ -112,7 +117,8 @@ namespace } } - inline cadet::active* createAndConfigureOperator(cadet::model::parts::ConvectionDispersionOperator& convDispOp, int& nComp, int& nCol, int wenoOrder) + template + inline cadet::active* createAndConfigureOperator(OperatorType& convDispOp, int& nComp, int& nCol, int wenoOrder) { // Obtain parameters from some test case cadet::JsonParameterProvider jpp = createColumnWithSMA("GENERAL_RATE_MODEL"); @@ -126,7 +132,8 @@ namespace // Configure the operator typedef std::unordered_map ParameterMap; ParameterMap parameters; - REQUIRE(convDispOp.configureModelDiscretization(jpp, nComp, nCol)); + cadet::ModelBuilder builder; + REQUIRE(convDispOp.configureModelDiscretization(jpp, builder, nComp, nCol)); REQUIRE(convDispOp.configure(0, jpp, parameters)); // Make sure that VELOCITY parameter is present @@ -139,13 +146,14 @@ namespace } // namespace +template void testResidualBulkWenoForwardBackward(int wenoOrder) { SECTION("Forward vs backward flow residual bulk (WENO=" + std::to_string(wenoOrder) + ")") { int nComp = 0; int nCol = 0; - cadet::model::parts::ConvectionDispersionOperator convDispOp; + OperatorType convDispOp; cadet::active* const velocity = createAndConfigureOperator(convDispOp, nComp, nCol, wenoOrder); const double origVelocity = velocity->getValue(); @@ -165,7 +173,7 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) SECTION("Forward flow yields backwards flow residual (zero state)") { // Forward flow residual - convDispOp.residual(0.0, 0u, y.data(), nullptr, res.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, res.data(), false, cadet::WithoutParamSensitivity()); // Reverse flow velocity->setValue(-origVelocity); @@ -173,7 +181,7 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) std::vector resRev(nDof, 0.0); // Backward flow residual - convDispOp.residual(0.0, 0u, y.data(), nullptr, resRev.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, resRev.data(), false, cadet::WithoutParamSensitivity()); // Compare compareResidualBulkFwdBwd(res.data(), resRev.data(), nComp, nCol); @@ -184,7 +192,7 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) std::vector resFwd2(nDof, 0.0); // Forward flow residual - convDispOp.residual(0.0, 0u, y.data(), nullptr, resFwd2.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, resFwd2.data(), false, cadet::WithoutParamSensitivity()); // Compare against first forward flow residual compareResidualBulkFwdFwd(res.data(), resFwd2.data(), nComp, nCol); @@ -196,7 +204,7 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) // Fill state vector with some values fillStateBulkFwd(y.data(), [](unsigned int comp, unsigned int col, unsigned int idx) { return std::abs(std::sin(idx * 0.13)); }, nComp, nCol); - convDispOp.residual(0.0, 0u, y.data(), nullptr, res.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, res.data(), false, cadet::WithoutParamSensitivity()); // Reverse state for backwards flow fillStateBulkBwd(y.data(), [](unsigned int comp, unsigned int col, unsigned int idx) { return std::abs(std::sin(idx * 0.13)); }, nComp, nCol); @@ -207,7 +215,7 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) std::vector resRev(nDof, 0.0); // Backward flow residual - convDispOp.residual(0.0, 0u, y.data(), nullptr, resRev.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, resRev.data(), false, cadet::WithoutParamSensitivity()); // Compare compareResidualBulkFwdBwd(res.data(), resRev.data(), nComp, nCol); @@ -221,7 +229,7 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) fillStateBulkFwd(y.data(), [](unsigned int comp, unsigned int col, unsigned int idx) { return std::abs(std::sin(idx * 0.13)); }, nComp, nCol); // Forward flow residual - convDispOp.residual(0.0, 0u, y.data(), nullptr, resFwd2.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, resFwd2.data(), false, cadet::WithoutParamSensitivity()); // Compare against first forward flow residual compareResidualBulkFwdFwd(res.data(), resFwd2.data(), nComp, nCol); @@ -229,11 +237,12 @@ void testResidualBulkWenoForwardBackward(int wenoOrder) } } +template void testTimeDerivativeBulkJacobianFD(double h, double absTol, double relTol) { int nComp = 0; int nCol = 0; - cadet::model::parts::ConvectionDispersionOperator convDispOp; + OperatorType convDispOp; createAndConfigureOperator(convDispOp, nComp, nCol, cadet::Weno::maxOrder()); // Setup matrices @@ -253,19 +262,20 @@ void testTimeDerivativeBulkJacobianFD(double h, double absTol, double relTol) // Compare Jacobians cadet::test::compareJacobianFD( - [&](double const* dir, double* res) -> void { convDispOp.residual(0.0, 0u, y.data(), dir, res, false, cadet::WithoutParamSensitivity()); }, + [&](double const* dir, double* res) -> void { convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), dir, res, false, cadet::WithoutParamSensitivity()); }, [&](double const* dir, double* res) -> void { convDispOp.multiplyWithDerivativeJacobian(cadet::SimulationTime{0.0, 0u}, dir, res); }, yDot.data(), jacDir.data(), jacCol1.data(), jacCol2.data(), nDof, h, absTol, relTol); } +template void testBulkJacobianWenoForwardBackward(int wenoOrder) { SECTION("Forward vs backward flow Jacobian (WENO=" + std::to_string(wenoOrder) + ")") { int nComp = 0; int nCol = 0; - cadet::model::parts::ConvectionDispersionOperator opAna; - cadet::model::parts::ConvectionDispersionOperator opAD; + OperatorType opAna; + OperatorType opAD; cadet::active* const anaVelocity = createAndConfigureOperator(opAna, nComp, nCol, wenoOrder); cadet::active* const adVelocity = createAndConfigureOperator(opAD, nComp, nCol, wenoOrder); @@ -293,17 +303,17 @@ void testBulkJacobianWenoForwardBackward(int wenoOrder) SECTION("Forward then backward flow (nonzero state)") { // Compute state Jacobian - opAna.residual(0.0, 0u, y.data(), nullptr, jacDir.data(), true, cadet::WithoutParamSensitivity()); + opAna.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, jacDir.data(), true, cadet::WithoutParamSensitivity()); std::fill(jacDir.begin(), jacDir.end(), 0.0); cadet::ad::copyToAd(y.data(), adY, nDof); cadet::ad::resetAd(adRes, nDof); - opAD.residual(0.0, 0u, adY, nullptr, adRes, false, cadet::WithoutParamSensitivity()); + opAD.residual(DummyModel(), 0.0, 0u, adY, nullptr, adRes, false, cadet::WithoutParamSensitivity()); opAD.extractJacobianFromAD(adRes, 0); const std::function anaResidual = [&](double const* lDir, double* res) -> void { - opAna.residual(0.0, 0u, lDir - nComp, nullptr, res - nComp, false, cadet::WithoutParamSensitivity()); + opAna.residual(DummyModel(), 0.0, 0u, lDir - nComp, nullptr, res - nComp, false, cadet::WithoutParamSensitivity()); }; const std::function anaMultJac = [&](double const* lDir, double* res) -> void @@ -335,12 +345,12 @@ void testBulkJacobianWenoForwardBackward(int wenoOrder) opAD.notifyDiscontinuousSectionTransition(0.0, 0u, cadet::AdJacobianParams{adRes, adY, 0u}); // Compute state Jacobian - opAna.residual(0.0, 0u, y.data(), nullptr, jacDir.data(), true, cadet::WithoutParamSensitivity()); + opAna.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, jacDir.data(), true, cadet::WithoutParamSensitivity()); std::fill(jacDir.begin(), jacDir.end(), 0.0); cadet::ad::copyToAd(y.data(), adY, nDof); cadet::ad::resetAd(adRes, nDof); - opAD.residual(0.0, 0u, adY, nullptr, adRes, false, cadet::WithoutParamSensitivity()); + opAD.residual(DummyModel(), 0.0, 0u, adY, nullptr, adRes, false, cadet::WithoutParamSensitivity()); opAD.extractJacobianFromAD(adRes, 0); // Compare Jacobians @@ -359,6 +369,121 @@ void testBulkJacobianWenoForwardBackward(int wenoOrder) } } + +struct AxialFlow +{ + typedef cadet::model::parts::convdisp::AxialFlowParameters Params; + + static void sparsityPattern(cadet::linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, cadet::Weno& weno) + { + cadet::model::parts::convdisp::sparsityPatternAxial(itBegin, nComp, nCol, strideCell, u, weno); + } + + static void residual(double const* y, double const* yDot, double* res, const Params& fp) + { + cadet::model::parts::convdisp::residualKernelAxial(cadet::SimulationTime{0.0, 0u}, y, yDot, res, cadet::linalg::BandedSparseRowIterator(), fp); + } + + template + static void residualWithJacobian(double const* y, double const* yDot, double* res, IteratorType jacBegin, const Params& fp) + { + cadet::model::parts::convdisp::residualKernelAxial(cadet::SimulationTime{0.0, 0u}, y, yDot, res, jacBegin, fp); + } + + std::unique_ptr parDep; + + AxialFlow() + { + cadet::ParameterDependenceFactory paramDepFactory; + parDep.reset(paramDepFactory.createParameterDependence("CONSTANT_ONE")); + } + + Params makeParams(double u, cadet::active const* d_ax, double h, double* wenoDerivatives, cadet::Weno* weno, cadet::ArrayPool* stencilMemory, int strideCell, int nComp, int nCol) + { + return Params { + u, + d_ax, + h, + wenoDerivatives, + weno, + stencilMemory, + 1e-12, + strideCell, + static_cast(nComp), + static_cast(nCol), + 0u, + static_cast(nComp), + parDep.get(), + DummyModel() + }; + } +}; + +struct RadialFlow +{ + typedef cadet::model::parts::convdisp::RadialFlowParameters Params; + + static void sparsityPattern(cadet::linalg::SparsityPatternRowIterator itBegin, unsigned int nComp, unsigned int nCol, int strideCell, double u, cadet::Weno& weno) + { + cadet::model::parts::convdisp::sparsityPatternRadial(itBegin, nComp, nCol, strideCell, u, weno); + } + + static void residual(double const* y, double const* yDot, double* res, const Params& fp) + { + cadet::model::parts::convdisp::residualKernelRadial(cadet::SimulationTime{0.0, 0u}, y, yDot, res, cadet::linalg::BandedSparseRowIterator(), fp); + } + + template + static void residualWithJacobian(double const* y, double const* yDot, double* res, IteratorType jacBegin, const Params& fp) + { + cadet::model::parts::convdisp::residualKernelRadial(cadet::SimulationTime{0.0, 0u}, y, yDot, res, jacBegin, fp); + } + + std::unique_ptr parDep; + std::vector centers; + std::vector sizes; + std::vector bounds; + + RadialFlow() + { + cadet::ParameterDependenceFactory paramDepFactory; + parDep.reset(paramDepFactory.createParameterDependence("CONSTANT_ONE")); + } + + Params makeParams(double u, cadet::active const* d_rad, double h, double* wenoDerivatives, cadet::Weno* weno, cadet::ArrayPool* stencilMemory, int strideCell, int nComp, int nCol) + { + centers.resize(10); + sizes.resize(10); + bounds.resize(11); + + for (int i = 0; i < 10; ++i) + { + centers[i] = (i + 1.5) * h; + sizes[i] = h; + bounds[i] = (i + 1) * h; + } + bounds.back() = 11 * h; + + return Params { + u, + d_rad, + centers.data(), + sizes.data(), + bounds.data(), + stencilMemory, + strideCell, + static_cast(nComp), + static_cast(nCol), + 0u, + static_cast(nComp), + parDep.get(), + DummyModel() + }; + } +}; + + +template void testBulkJacobianSparsityWeno(int wenoOrder, bool forwardFlow) { SECTION("WENO=" + std::to_string(wenoOrder)) @@ -378,24 +503,25 @@ void testBulkJacobianSparsityWeno(int wenoOrder, bool forwardFlow) weno.order(wenoOrder); weno.boundaryTreatment(cadet::Weno::BoundaryTreatment::ReduceOrder); - cadet::model::parts::convdisp::FlowParameters fp{ + cadet::ParameterDependenceFactory paramDepFactory; + std::unique_ptr parDep(paramDepFactory.createParameterDependence("CONSTANT_ONE")); + + FlowType ft; + typename FlowType::Params fp = ft.makeParams( u, d_c.data(), h, wenoDerivatives.data(), &weno, &stencilMemory, - 1e-12, strideCell, - static_cast(nComp), - static_cast(nCol), - 0u, - static_cast(nComp) - }; + nComp, + nCol + ); // Obtain sparsity pattern cadet::linalg::SparsityPattern pattern(nComp * nCol, std::max(weno.lowerBandwidth() + 1u, 1u) + 1u + std::max(weno.upperBandwidth(), 1u)); - cadet::model::parts::convdisp::sparsityPattern(pattern.row(0), nComp, nCol, strideCell, u, weno); + FlowType::sparsityPattern(pattern.row(0), nComp, nCol, strideCell, u, weno); // Obtain memory for state, Jacobian columns const int nDof = nComp + nComp * nCol; @@ -412,10 +538,10 @@ void testBulkJacobianSparsityWeno(int wenoOrder, bool forwardFlow) // Central finite differences y[nComp + col] = ref * (1.0 + 1e-6); - cadet::model::parts::convdisp::residualKernel(cadet::SimulationTime{0.0, 0u}, y.data(), nullptr, jacCol1.data(), cadet::linalg::BandedSparseRowIterator(), fp); + FlowType::residual(y.data(), nullptr, jacCol1.data(), fp); y[nComp + col] = ref * (1.0 - 1e-6); - cadet::model::parts::convdisp::residualKernel(cadet::SimulationTime{0.0, 0u}, y.data(), nullptr, jacCol2.data(), cadet::linalg::BandedSparseRowIterator(), fp); + FlowType::residual(y.data(), nullptr, jacCol2.data(), fp); y[nComp + col] = ref; @@ -433,6 +559,7 @@ void testBulkJacobianSparsityWeno(int wenoOrder, bool forwardFlow) } } +template void testBulkJacobianSparseBandedWeno(int wenoOrder, bool forwardFlow) { SECTION("WENO=" + std::to_string(wenoOrder)) @@ -452,26 +579,27 @@ void testBulkJacobianSparseBandedWeno(int wenoOrder, bool forwardFlow) weno.order(wenoOrder); weno.boundaryTreatment(cadet::Weno::BoundaryTreatment::ReduceOrder); - cadet::model::parts::convdisp::FlowParameters fp{ + cadet::ParameterDependenceFactory paramDepFactory; + std::unique_ptr parDep(paramDepFactory.createParameterDependence("CONSTANT_ONE")); + + FlowType ft; + typename FlowType::Params fp = ft.makeParams( u, d_c.data(), h, wenoDerivatives.data(), &weno, &stencilMemory, - 1e-12, strideCell, - static_cast(nComp), - static_cast(nCol), - 0u, - static_cast(nComp) - }; + nComp, + nCol + ); // Obtain sparsity pattern const unsigned int lowerBandwidth = std::max(weno.lowerBandwidth() + 1u, 1u); const unsigned int upperBandwidth = std::max(weno.upperBandwidth(), 1u); cadet::linalg::SparsityPattern pattern(nComp * nCol, lowerBandwidth + 1u + upperBandwidth); - cadet::model::parts::convdisp::sparsityPattern(pattern.row(0), nComp, nCol, strideCell, u, weno); + FlowType::sparsityPattern(pattern.row(0), nComp, nCol, strideCell, u, weno); // Obtain memory for state const int nDof = nComp + nComp * nCol; @@ -483,7 +611,7 @@ void testBulkJacobianSparseBandedWeno(int wenoOrder, bool forwardFlow) // Populate sparse matrix cadet::linalg::CompressedSparseMatrix sparseMat(pattern); - cadet::model::parts::convdisp::residualKernel(cadet::SimulationTime{0.0, 0u}, y.data(), nullptr, res.data(), sparseMat.row(0), fp); + FlowType::template residualWithJacobian(y.data(), nullptr, res.data(), sparseMat.row(0), fp); // Populate dense matrix cadet::linalg::BandMatrix bandMat; @@ -491,7 +619,8 @@ void testBulkJacobianSparseBandedWeno(int wenoOrder, bool forwardFlow) bandMat.resize(nComp * nCol, lowerBandwidth * strideCell, upperBandwidth * strideCell); else bandMat.resize(nComp * nCol, upperBandwidth * strideCell, lowerBandwidth * strideCell); - cadet::model::parts::convdisp::residualKernel(cadet::SimulationTime{0.0, 0u}, y.data(), nullptr, res.data(), bandMat.row(0), fp); + + FlowType::template residualWithJacobian(y.data(), nullptr, res.data(), bandMat.row(0), fp); for (int col = 0; col < bandMat.rows(); ++col) { @@ -510,53 +639,105 @@ void testBulkJacobianSparseBandedWeno(int wenoOrder, bool forwardFlow) } } -TEST_CASE("ConvectionDispersionOperator residual forward vs backward flow", "[Operator],[Residual]") +TEST_CASE("AxialConvectionDispersionOperator residual forward vs backward flow", "[Operator],[AxialFlow],[Residual]") { // Test all WENO orders for (unsigned int i = 1; i <= cadet::Weno::maxOrder(); ++i) - testResidualBulkWenoForwardBackward(i); + testResidualBulkWenoForwardBackward(i); } -TEST_CASE("ConvectionDispersionOperator time derivative Jacobian vs FD", "[Operator],[Residual],[Jacobian]") +TEST_CASE("AxialConvectionDispersionOperator time derivative Jacobian vs FD", "[Operator],[AxialFlow],[Residual],[Jacobian]") { - testTimeDerivativeBulkJacobianFD(1e-6, 0.0, 1e-5); + testTimeDerivativeBulkJacobianFD(1e-6, 0.0, 1e-5); } -TEST_CASE("ConvectionDispersionOperator Jacobian forward vs backward flow", "[Operator],[Residual],[Jacobian],[AD]") +TEST_CASE("AxialConvectionDispersionOperator Jacobian forward vs backward flow", "[Operator],[AxialFlow],[Residual],[Jacobian],[AD]") { // Test all WENO orders for (unsigned int i = 1; i <= cadet::Weno::maxOrder(); ++i) - testBulkJacobianWenoForwardBackward(i); + testBulkJacobianWenoForwardBackward(i); } -TEST_CASE("ConvectionDispersionKernel Jacobian sparsity pattern vs FD", "[Operator],[Residual],[Jacobian],[SparseMatrix]") +TEST_CASE("AxialConvectionDispersionKernel Jacobian sparsity pattern vs FD", "[Operator],[AxialFlow],[Residual],[Jacobian],[SparseMatrix]") { SECTION("Forward flow") { // Test all WENO orders for (unsigned int i = 1; i <= cadet::Weno::maxOrder(); ++i) - testBulkJacobianSparsityWeno(i, true); + testBulkJacobianSparsityWeno(i, true); } SECTION("Backward flow") { // Test all WENO orders for (unsigned int i = 1; i <= cadet::Weno::maxOrder(); ++i) - testBulkJacobianSparsityWeno(i, false); + testBulkJacobianSparsityWeno(i, false); } } -TEST_CASE("ConvectionDispersionKernel Jacobian sparse vs banded", "[Operator],[Jacobian],[SparseMatrix]") +TEST_CASE("AxialConvectionDispersionKernel Jacobian sparse vs banded", "[Operator],[AxialFlow],[Jacobian],[SparseMatrix]") { SECTION("Forward flow") { // Test all WENO orders for (unsigned int i = 1; i <= cadet::Weno::maxOrder(); ++i) - testBulkJacobianSparseBandedWeno(i, true); + testBulkJacobianSparseBandedWeno(i, true); } SECTION("Backward flow") { // Test all WENO orders for (unsigned int i = 1; i <= cadet::Weno::maxOrder(); ++i) - testBulkJacobianSparseBandedWeno(i, false); + testBulkJacobianSparseBandedWeno(i, false); + } +} + + +TEST_CASE("RadialConvectionDispersionOperator residual forward vs backward flow", "[Operator],[RadialFlow],[Residual]") +{ + // Test all WENO orders + for (unsigned int i = 1; i <= 1; ++i) + testResidualBulkWenoForwardBackward(i); +} + +TEST_CASE("RadialConvectionDispersionOperator time derivative Jacobian vs FD", "[Operator],[RadialFlow],[Residual],[Jacobian]") +{ + testTimeDerivativeBulkJacobianFD(1e-6, 0.0, 1e-5); +} + +TEST_CASE("RadialConvectionDispersionOperator Jacobian forward vs backward flow", "[Operator],[RadialFlow],[Residual],[Jacobian],[AD]") +{ + // Test all WENO orders + for (unsigned int i = 1; i <= 1; ++i) + testBulkJacobianWenoForwardBackward(i); +} + +TEST_CASE("RadialConvectionDispersionKernel Jacobian sparsity pattern vs FD", "[Operator],[RadialFlow],[Residual],[Jacobian],[SparseMatrix]") +{ + SECTION("Forward flow") + { + // Test all WENO orders + for (unsigned int i = 1; i <= 1; ++i) + testBulkJacobianSparsityWeno(i, true); + } + SECTION("Backward flow") + { + // Test all WENO orders + for (unsigned int i = 1; i <= 1; ++i) + testBulkJacobianSparsityWeno(i, false); + } +} + +TEST_CASE("RadialConvectionDispersionKernel Jacobian sparse vs banded", "[Operator],[RadialFlow],[Jacobian],[SparseMatrix]") +{ + SECTION("Forward flow") + { + // Test all WENO orders + for (unsigned int i = 1; i <= 1; ++i) + testBulkJacobianSparseBandedWeno(i, true); + } + SECTION("Backward flow") + { + // Test all WENO orders + for (unsigned int i = 1; i <= 1; ++i) + testBulkJacobianSparseBandedWeno(i, false); } } diff --git a/test/Dummies.hpp b/test/Dummies.hpp new file mode 100644 index 000000000..b839779b9 --- /dev/null +++ b/test/Dummies.hpp @@ -0,0 +1,65 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 "cadet/Model.hpp" +#include "cadet/ParameterId.hpp" + +#include "ConfigurationHelper.hpp" + +namespace +{ + class DummyConfigHelper : public cadet::IConfigHelper + { + public: + + DummyConfigHelper() { } + + virtual cadet::IInletProfile* createInletProfile(const std::string& type) const { return nullptr; } + virtual cadet::model::IBindingModel* createBindingModel(const std::string& name) const { return nullptr; } + virtual bool isValidBindingModel(const std::string& name) const { return false; } + virtual cadet::IExternalFunction* createExternalFunction(const std::string& type) const { return nullptr; } + virtual cadet::model::IDynamicReactionModel* createDynamicReactionModel(const std::string& name) const { return nullptr; } + virtual bool isValidDynamicReactionModel(const std::string& name) const { return false; } + virtual cadet::model::IParameterStateDependence* createParameterStateDependence(const std::string& name) const { return nullptr; } + virtual bool isValidParameterStateDependence(const std::string& name) const { return false; } + virtual cadet::model::IParameterParameterDependence* createParameterParameterDependence(const std::string& name) const { return nullptr; } + virtual bool isValidParameterParameterDependence(const std::string& name) const { return false; } + }; + + class DummyModel : public cadet::IModel + { + public: + DummyModel() { } + virtual ~DummyModel() CADET_NOEXCEPT { } + + virtual cadet::UnitOpIdx unitOperationId() const CADET_NOEXCEPT { return 0; }; + virtual const char* unitOperationName() const CADET_NOEXCEPT { return "DUMMY"; } + + virtual bool setParameter(const cadet::ParameterId& pId, int value) { return false; } + virtual bool setParameter(const cadet::ParameterId& pId, double value) { return false; } + virtual bool setParameter(const cadet::ParameterId& pId, bool value) { return false; } + + virtual bool hasParameter(const cadet::ParameterId& pId) const { return false; } + + virtual std::unordered_map getAllParameterValues() const { return std::unordered_map(); } + + virtual double getParameterDouble(const cadet::ParameterId& pId) const { return 0.0; } + + virtual void useAnalyticJacobian(const bool analyticJac) { } + +#ifdef CADET_BENCHMARK_MODE + virtual std::vector benchmarkTimings() const { return std::vector(0); } + virtual char const* const* benchmarkDescriptions() const { return nullptr; } +#endif + }; + +} diff --git a/test/GeneralRateModel.cpp b/test/GeneralRateModel.cpp index 9e7b022ba..2460917d4 100644 --- a/test/GeneralRateModel.cpp +++ b/test/GeneralRateModel.cpp @@ -10,12 +10,6 @@ // is available at http://www.gnu.org/licenses/gpl.html // ============================================================================= -// we have the following tests: -// fixGRM: test that does not work and is not based on FD -// CI: test that works and is not based on FD -// FDtestGRM: Test based on FD that works on my machine -// failedFDtestGRM: Test based on FD that does not work on my machine - #include #include "ColumnTests.hpp" diff --git a/test/JacobianHelper.hpp b/test/JacobianHelper.hpp index b98949b0e..763d31974 100644 --- a/test/JacobianHelper.hpp +++ b/test/JacobianHelper.hpp @@ -366,9 +366,10 @@ inline void compareJacobianFD(cadet::IUnitOperation* modelA, cadet::IUnitOperati * @param [in] colB Memory for Jacobian column * @param [in] n Number of DOFs in the model * @param [in] m Number of equations in the model + * @param [in] absTol absolute tolerance when comparing the sign in the pattern */ inline void checkJacobianPatternFD(const std::function& residual, const std::function& multiplyJacobian, double const* y, double* dir, - double* colA, double* colB, unsigned int n, unsigned int m) + double* colA, double* colB, unsigned int n, unsigned int m, const double absTol = 0.0) { const double h = 1e-5; for (unsigned int col = 0; col < n; ++col) @@ -405,14 +406,12 @@ inline void checkJacobianPatternFD(const std::function 0.0) - CHECK(colB[row] > 0.0); - else if (colA[row] < 0.0) - CHECK(colB[row] < 0.0); + if (std::abs(colA[row]) <= absTol) + CHECK(std::abs(colA[row]) <= absTol); else if (std::isnan(colA[row])) CHECK(std::isnan(colB[row])); + else + CHECK(std::signbit(colA[row]) == std::signbit(colB[row])); } } } @@ -428,11 +427,12 @@ inline void checkJacobianPatternFD(const std::function& residual, const std::function& multiplyJacobian, double const* y, - double* dir, double* colA, double* colB, unsigned int n) + double* dir, double* colA, double* colB, unsigned int n, const double absTol = 0.0) { - checkJacobianPatternFD(residual, multiplyJacobian, y, dir, colA, colB, n, n); + checkJacobianPatternFD(residual, multiplyJacobian, y, dir, colA, colB, n, n, absTol); } /** @@ -448,13 +448,15 @@ inline void checkJacobianPatternFD(const std::function void { modelA->residual(SimulationTime{0.0, 0u}, ConstSimulationState{lDir, yDot}, res, tls); }, [=](double const* lDir, double* res) -> void { modelB->multiplyWithJacobian(SimulationTime{0.0, 0u}, ConstSimulationState{y, yDot}, lDir, 1.0, 0.0, res); }, - y, dir, colA, colB, modelA->numDofs(), modelA->numDofs()); + y, dir, colA, colB, modelA->numDofs(), modelA->numDofs(), + absTol); } /** diff --git a/test/JsonTestModels.cpp b/test/JsonTestModels.cpp index fd63b36e0..c6df85707 100644 --- a/test/JsonTestModels.cpp +++ b/test/JsonTestModels.cpp @@ -11,6 +11,7 @@ // ============================================================================= #include +#include #include "common/JsonParameterProvider.hpp" @@ -21,7 +22,6 @@ json createColumnWithSMAJson(const std::string& uoType) json config; config["UNIT_TYPE"] = uoType; config["NCOMP"] = 4; - config["VELOCITY"] = 5.75e-4; config["COL_DISPERSION"] = 5.75e-8; config["COL_DISPERSION_RADIAL"] = 1e-6; config["FILM_DIFFUSION"] = {6.9e-6, 6.9e-6, 6.9e-6, 6.9e-6}; @@ -29,8 +29,18 @@ json createColumnWithSMAJson(const std::string& uoType) config["PAR_SURFDIFFUSION"] = {0.0, 0.0, 0.0, 0.0}; // Geometry - config["COL_LENGTH"] = 0.014; - config["COL_RADIUS"] = 0.01; + if (uoType.substr(0, 6) == "RADIAL") + { + config["COL_RADIUS_INNER"] = 0.001; + config["COL_RADIUS_OUTER"] = 0.004; + config["VELOCITY_COEFF"] = 5.75e-4; + } + else + { + config["COL_LENGTH"] = 0.014; + config["COL_RADIUS"] = 0.01; + config["VELOCITY"] = 5.75e-4; + } config["PAR_RADIUS"] = 4.5e-5; config["COL_POROSITY"] = 0.37; config["PAR_POROSITY"] = 0.75; @@ -149,7 +159,6 @@ json createColumnWithTwoCompLinearJson(const std::string& uoType) json config; config["UNIT_TYPE"] = uoType; config["NCOMP"] = 2; - config["VELOCITY"] = 5.75e-4; config["COL_DISPERSION"] = 5.75e-8; config["COL_DISPERSION_RADIAL"] = 1e-6; config["FILM_DIFFUSION"] = {6.9e-6, 6.9e-6}; @@ -157,8 +166,18 @@ json createColumnWithTwoCompLinearJson(const std::string& uoType) config["PAR_SURFDIFFUSION"] = {1e-10, 1e-10}; // Geometry - config["COL_LENGTH"] = 0.014; - config["COL_RADIUS"] = 0.01; + if (uoType.substr(0, 6) == "RADIAL") + { + config["COL_RADIUS_INNER"] = 0.001; + config["COL_RADIUS_OUTER"] = 0.004; + config["VELOCITY_COEFF"] = 5.75e-4; + } + else + { + config["COL_LENGTH"] = 0.014; + config["COL_RADIUS"] = 0.01; + config["VELOCITY"] = 5.75e-4; + } config["PAR_RADIUS"] = 4.5e-5; config["COL_POROSITY"] = 0.37; config["PAR_POROSITY"] = 0.75; @@ -304,12 +323,12 @@ json createLWEJson(const std::string& uoType) { // Connection list is 1x7 since we have 1 connection between // the two unit operations (and we need to have 7 columns) - sw["CONNECTIONS"] = {1.0, 0.0, -1.0, -1.0, -1.0, -1.0, 1.0}; + sw["CONNECTIONS"] = {1.0, 0.0, -1.0, -1.0, -1.0, -1.0, 6.683738370512285e-8}; // Connections: From unit operation 1 port -1 (i.e., all ports) // to unit operation 0 port -1 (i.e., all ports), // connect component -1 (i.e., all components) // to component -1 (i.e., all components) with - // volumetric flow rate 1.0 m^3/s + // volumetric flow rate 6.683738370512285e-8 m^3/s } con["switch_000"] = sw; @@ -433,7 +452,6 @@ cadet::JsonParameterProvider createPulseInjectionColumn(const std::string& uoTyp json grm; grm["UNIT_TYPE"] = uoType; grm["NCOMP"] = 1; - grm["VELOCITY"] = 5.75e-4; grm["COL_DISPERSION"] = 5.75e-8; grm["COL_DISPERSION_MULTIPLEX"] = 0; grm["COL_DISPERSION_RADIAL"] = 1e-6; @@ -442,8 +460,18 @@ cadet::JsonParameterProvider createPulseInjectionColumn(const std::string& uoTyp grm["PAR_SURFDIFFUSION"] = {0.0}; // Geometry - grm["COL_LENGTH"] = 0.014; - grm["COL_RADIUS"] = 0.01; + if (uoType.substr(0, 6) == "RADIAL") + { + grm["COL_RADIUS_INNER"] = 0.001; + grm["COL_RADIUS_OUTER"] = 0.004; + grm["VELOCITY_COEFF"] = 5.75e-4; + } + else + { + grm["COL_LENGTH"] = 0.014; + grm["COL_RADIUS"] = 0.01; + grm["VELOCITY"] = 5.75e-4; + } grm["PAR_RADIUS"] = 4.5e-5; grm["PAR_CORERADIUS"] = 0.0; grm["COL_POROSITY"] = 0.37; @@ -665,7 +693,6 @@ json createLinearBenchmarkColumnJson(bool dynamicBinding, bool nonBinding, const json grm; grm["UNIT_TYPE"] = uoType; grm["NCOMP"] = 1; - grm["VELOCITY"] = 0.5 / (100.0 * 60.0); grm["COL_DISPERSION"] = 0.002 / (100.0 * 100.0 * 60.0); grm["COL_DISPERSION_MULTIPLEX"] = 0; grm["COL_DISPERSION_RADIAL"] = 1e-6; @@ -674,8 +701,18 @@ json createLinearBenchmarkColumnJson(bool dynamicBinding, bool nonBinding, const grm["PAR_SURFDIFFUSION"] = {0.0}; // Geometry - grm["COL_LENGTH"] = 0.017; - grm["COL_RADIUS"] = 0.01; + if (uoType.substr(0, 6) == "RADIAL") + { + grm["COL_RADIUS_INNER"] = 0.001; + grm["COL_RADIUS_OUTER"] = 0.004; + grm["VELOCITY_COEFF"] = 5.75e-4; + } + else + { + grm["COL_LENGTH"] = 0.017; + grm["COL_RADIUS"] = 0.01; + grm["VELOCITY"] = 0.5 / (100.0 * 60.0); + } grm["PAR_RADIUS"] = 4e-5; grm["COL_POROSITY"] = 0.4; grm["PAR_POROSITY"] = 0.333; diff --git a/test/LumpedRateModelWithPores.cpp b/test/LumpedRateModelWithPores.cpp index 370e40b7f..d6ecc9138 100644 --- a/test/LumpedRateModelWithPores.cpp +++ b/test/LumpedRateModelWithPores.cpp @@ -85,12 +85,12 @@ TEST_CASE("LRMP numerical EOC Benchmark with parameter sensitivities for SMA LWE cadet::test::column::testEOCReferenceBenchmark(modelFilePath, refFilePath, convFilePath, "000", absTol, relTol, 2, 8, 0, true); } -TEST_CASE("LRMP time derivative Jacobian vs FD", "[LRMP],[UnitOp],[Residual],[Jacobian],[CI],[CI]") +TEST_CASE("LRMP time derivative Jacobian vs FD", "[LRMP],[UnitOp],[Residual],[Jacobian],[CI]") { cadet::test::column::testTimeDerivativeJacobianFD("LUMPED_RATE_MODEL_WITH_PORES"); } -TEST_CASE("LRMP flux Jacobian vs FD", "[LRMP],[UnitOp],[Residual],[Jacobian],[CI],[CI]") +TEST_CASE("LRMP flux Jacobian vs FD", "[LRMP],[UnitOp],[Residual],[Jacobian],[CI]") { cadet::test::column::testArrowHeadJacobianFD("LUMPED_RATE_MODEL_WITH_PORES"); } diff --git a/test/ModelSystem.cpp b/test/ModelSystem.cpp index e7e6820a4..a14e4ba54 100644 --- a/test/ModelSystem.cpp +++ b/test/ModelSystem.cpp @@ -24,6 +24,7 @@ #include "JacobianHelper.hpp" #include "ColumnTests.hpp" #include "Utils.hpp" +#include "Dummies.hpp" #include "model/UnitOperation.hpp" #include @@ -32,21 +33,6 @@ namespace { - class DummyConfigHelper : public cadet::IConfigHelper - { - public: - - DummyConfigHelper() { } - - virtual cadet::IInletProfile* createInletProfile(const std::string& type) const { return nullptr; } - virtual cadet::model::IBindingModel* createBindingModel(const std::string& name) const { return nullptr; } - virtual bool isValidBindingModel(const std::string& name) const { return false; } - virtual cadet::IExternalFunction* createExternalFunction(const std::string& type) const { return nullptr; } - virtual cadet::model::IDynamicReactionModel* createDynamicReactionModel(const std::string& name) const { return nullptr; } - virtual bool isValidDynamicReactionModel(const std::string& name) const { return false; } - virtual cadet::model::IParameterDependence* createParameterDependence(const std::string& name) const { return nullptr; } - virtual bool isValidParameterDependence(const std::string& name) const { return false; } - }; class DummyUnitOperation : public cadet::IUnitOperation { @@ -103,7 +89,7 @@ namespace virtual bool usesAD() const CADET_NOEXCEPT { return false; } virtual unsigned int requiredADdirs() const CADET_NOEXCEPT { return 0; } - virtual bool configureModelDiscretization(cadet::IParameterProvider& paramProvider, cadet::IConfigHelper& helper) { return true; } + virtual bool configureModelDiscretization(cadet::IParameterProvider& paramProvider, const cadet::IConfigHelper& helper) { return true; } virtual bool configure(cadet::IParameterProvider& paramProvider) { return true; } virtual void reportSolution(cadet::ISolutionRecorder& reporter, double const* const solution) const { } diff --git a/test/ParamDepTests.cpp b/test/ParamDepTests.cpp index 8978bbb65..816c3e9bf 100644 --- a/test/ParamDepTests.cpp +++ b/test/ParamDepTests.cpp @@ -33,10 +33,10 @@ namespace { - inline cadet::model::IParameterDependence* createParameterDependence(const char* name) + inline cadet::model::IParameterStateDependence* createParameterStateDependence(const char* name) { cadet::ParameterDependenceFactory pdf; - cadet::model::IParameterDependence* const pd = pdf.create(name); + cadet::model::IParameterStateDependence* const pd = pdf.createStateDependence(name); REQUIRE(nullptr != pd); return pd; @@ -60,7 +60,7 @@ ConfiguredParameterDependence::~ConfiguredParameterDependence() ConfiguredParameterDependence ConfiguredParameterDependence::create(const char* name, unsigned int nComp, unsigned int const* nBound, const char* config) { - cadet::model::IParameterDependence* const pd = createParameterDependence(name); + cadet::model::IParameterStateDependence* const pd = createParameterStateDependence(name); // Calculate offset of bound states unsigned int* boundOffset = new unsigned int[nComp]; diff --git a/test/ParamDepTests.hpp b/test/ParamDepTests.hpp index 7585b9dfe..50cf47793 100644 --- a/test/ParamDepTests.hpp +++ b/test/ParamDepTests.hpp @@ -27,7 +27,7 @@ namespace cadet namespace model { - class IParameterDependence; + class IParameterStateDependence; } namespace test @@ -66,8 +66,8 @@ namespace paramdep static ConfiguredParameterDependence create(const char* name, unsigned int nComp, unsigned int const* nBound, const char* config); - inline cadet::model::IParameterDependence& model() { return *_paramDep; } - inline const cadet::model::IParameterDependence& model() const { return *_paramDep; } + inline cadet::model::IParameterStateDependence& model() { return *_paramDep; } + inline const cadet::model::IParameterStateDependence& model() const { return *_paramDep; } inline unsigned int nComp() const { return _nComp; } inline unsigned int const* nBound() const { return _nBound; } @@ -77,12 +77,12 @@ namespace paramdep private: - ConfiguredParameterDependence(cadet::model::IParameterDependence* paramDep, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) + ConfiguredParameterDependence(cadet::model::IParameterStateDependence* paramDep, unsigned int nComp, unsigned int const* nBound, unsigned int const* boundOffset) : _paramDep(paramDep), _nComp(nComp), _nBound(nBound), _boundOffset(boundOffset) { } - cadet::model::IParameterDependence* _paramDep; + cadet::model::IParameterStateDependence* _paramDep; unsigned int _nComp; unsigned int const* _nBound; unsigned int const* _boundOffset; diff --git a/test/RadialGeneralRateModel.cpp b/test/RadialGeneralRateModel.cpp new file mode 100644 index 000000000..632e41aeb --- /dev/null +++ b/test/RadialGeneralRateModel.cpp @@ -0,0 +1,326 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2024: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 + +#include "ColumnTests.hpp" +#include "ParticleHelper.hpp" +#include "ReactionModelTests.hpp" +#include "JsonTestModels.hpp" +#include "Utils.hpp" + +// todo add a meaningful backward flow test + +// todo find analytical solution with linear binding + +// todo find analytical solution without binding + +// todo add (more) numerical reference (and EOC) tests + +TEST_CASE("Radial GRM numerical Benchmark with parameter sensitivities for linear case", "[RadGRM],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +{ + const std::string& modelFilePath = std::string("/data/model_radGRM_dynLin_1comp_sensbenchmark1.json"); + const std::string& refFilePath = std::string("/data/ref_radGRM_dynLin_1comp_sensbenchmark1_FV_Z32parZ4.h5"); + const std::vector absTol = { 1e-12, 1e-12, 1e-12, 1e-12 }; + const std::vector relTol = { 1e-6, 1e-6, 1e-6, 1e-6 }; + cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "001", absTol, relTol, 32, 4, true); +} + +TEST_CASE("Radial GRM transport Jacobian", "[RadGRM],[UnitOp],[Jacobian],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnLinearBenchmark(false, true, "RADIAL_GENERAL_RATE_MODEL"); + cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); +} + +// NOTE: THE FOLLOWING TESTS ARE ONLY INCLUDED IN THE RELEASE CI, NOT THE STANDARD CI SINCE THEY ARE (TO A HIGH DEGREE) REDUNDANT WITH THE AXIAL FLOW TESTS + +TEST_CASE("Radial GRM Jacobian forward vs backward flow", "[RadGRM],[UnitOp],[Residual],[Jacobian],[AD],[ReleaseCI]") +{ + cadet::test::column::testJacobianWenoForwardBackward("RADIAL_GENERAL_RATE_MODEL", 0); // note: no weno for radial flow models atm +} + +TEST_CASE("Radial GRM time derivative Jacobian vs FD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ReleaseCI],[FD]") +{ + cadet::test::column::testTimeDerivativeJacobianFD("RADIAL_GENERAL_RATE_MODEL", 1e-6, 0.0, 9e-4); +} + +TEST_CASE("Radial GRM rapid-equilibrium binding flux Jacobian vs FD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ReleaseCI],[FD]") +{ + cadet::test::column::testArrowHeadJacobianFD("RADIAL_GENERAL_RATE_MODEL", false, 1e-6, 2e-9); +} + +TEST_CASE("Radial GRM rapid-equilibrium binding with surf diff par dep flux Jacobian vs FD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ParameterDependence],[ReleaseCI],[FD]") +{ + cadet::test::column::testArrowHeadJacobianFDVariableParSurfDiff("RADIAL_GENERAL_RATE_MODEL", 1e-6, 5e-9); +} + +TEST_CASE("Radial GRM dynamic binding with surf diff par dep Jacobian vs AD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ParameterDependence],[ReleaseCI]") +{ + cadet::test::column::testJacobianADVariableParSurfDiff("RADIAL_GENERAL_RATE_MODEL", true); +} + +TEST_CASE("Radial GRM rapid-equilibrium binding with surf diff par dep Jacobian vs AD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ParameterDependence],[ReleaseCI]") +{ + cadet::test::column::testJacobianADVariableParSurfDiff("RADIAL_GENERAL_RATE_MODEL", false); +} + +TEST_CASE("Radial GRM dynamic binding flux Jacobian vs FD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ReleaseCI],[FD]") +{ + cadet::test::column::testArrowHeadJacobianFD("RADIAL_GENERAL_RATE_MODEL", true, 1e-6, 2e-9); +} + +TEST_CASE("Radial GRM sensitivity Jacobians", "[RadGRM],[UnitOp],[Sensitivity],[ReleaseCI]") +{ + cadet::test::column::testFwdSensJacobians("RADIAL_GENERAL_RATE_MODEL", 1e-4, 6e-7); +} + +//TEST_CASE("Radial GRM forward sensitivity vs FD", "[RadGRM],[Sensitivity],[Simulation],[failedFDtestGRM]") // todo fix. fails for SMA_KA for both binding modes +//{ +// // todo comment +// // Relative error is checked first, we use high absolute error for letting +// // some points that are far off pass the error test, too. This is required +// // due to errors in finite differences. +// const double fdStepSize[] = {5e-5, 1e-4, 1e-4, 1e-3}; +// const double absTols[] = {3e5, 2e-3, 2e-4, 5.0}; +// const double relTols[] = {5e-3, 7e-2, 8e-2, 1e-4}; +// const double passRatio[] = {0.95, 0.9, 0.91, 0.83}; +// cadet::test::column::testFwdSensSolutionFD("RADIAL_GENERAL_RATE_MODEL", false, fdStepSize, absTols, relTols, passRatio); +//} +// +//TEST_CASE("Radial GRM forward sensitivity forward vs backward flow", "[RadGRM],[Sensitivity],[Simulation],[fixGRM]") // todo fix. fails for COL_DISPERION for both binding modes +//{ +// const double absTols[] = {4e-5, 1e-11, 1e-11, 8e-9}; +// const double relTols[] = {6e-9, 5e-8, 5e-6, 5e-10}; +// const double passRatio[] = {0.99, 0.95, 0.98, 0.98}; +// cadet::test::column::testFwdSensSolutionForwardBackward("RADIAL_GENERAL_RATE_MODEL", absTols, relTols, passRatio); +//} + +TEST_CASE("Radial GRM consistent initialization with linear binding", "[RadGRM],[ConsistentInit],[ReleaseCI]") +{ + cadet::test::column::testConsistentInitializationLinearBinding("RADIAL_GENERAL_RATE_MODEL", 1e-12, 1e-12); +} + +//TEST_CASE("Radial GRM consistent initialization with SMA binding", "[RadGRM],[ConsistentInit],[fixGRM]") // todo fix +//{ +// std::vector y(4 + 4 * 16 + 16 * 4 * (4 + 4) + 4 * 16, 0.0); +//// Optimal values: +//// const double bindingCell[] = {1.2, 2.0, 1.0, 1.5, 858.034, 66.7896, 3.53273, 2.53153, +//// 1.0, 1.8, 1.5, 1.6, 856.173, 64.457, 5.73227, 2.85286}; +// const double bindingCell[] = {1.2, 2.0, 1.0, 1.5, 840.0, 63.0, 3.0, 3.0, +// 1.0, 1.8, 1.5, 1.6, 840.0, 63.0, 6.0, 3.0}; +// cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 + 4 * 16); +// cadet::test::util::repeat(y.data() + 4 + 4 * 16, bindingCell, 16, 4 * 16 / 2); +// cadet::test::util::populate(y.data() + 4 + 4 * 16 + 16 * 4 * (4 + 4), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 * 16); +// +// cadet::test::column::testConsistentInitializationSMABinding("RADIAL_GENERAL_RATE_MODEL", y.data(), 1e-14, 1e-5); +//} + +TEST_CASE("Radial GRM consistent sensitivity initialization with linear binding", "[RadGRM],[ConsistentInit],[Sensitivity],[ReleaseCI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 + 4 * 16 + 16 * 4 * (4 + 4) + 4 * 16; + std::vector y(numDofs, 0.0); + std::vector yDot(numDofs, 0.0); + cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, numDofs); + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + cadet::test::column::testConsistentInitializationSensitivity("RADIAL_GENERAL_RATE_MODEL", y.data(), yDot.data(), true, 1e-14); +} + +TEST_CASE("Radial GRM consistent sensitivity initialization with SMA binding", "[RadGRM],[ConsistentInit],[Sensitivity],[ReleaseCI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 + 4 * 16 + 16 * 4 * (4 + 4) + 4 * 16; + std::vector y(numDofs, 0.0); + std::vector yDot(numDofs, 0.0); + + const double bindingCell[] = {1.0, 1.8, 1.5, 1.6, 840.0, 63.0, 6.0, 3.0}; + cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 + 4 * 16); + cadet::test::util::repeat(y.data() + 4 + 4 * 16, bindingCell, 8, 4 * 16); + cadet::test::util::populate(y.data() + 4 + 4 * 16 + 16 * 4 * (4 + 4), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 * 16); + + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + cadet::test::column::testConsistentInitializationSensitivity("RADIAL_GENERAL_RATE_MODEL", y.data(), yDot.data(), false, 1e-9); +} + +TEST_CASE("Radial GRM inlet DOF Jacobian", "[RadGRM],[UnitOp],[Jacobian],[Inlet],[ReleaseCI]") +{ + cadet::test::column::testInletDofJacobian("RADIAL_GENERAL_RATE_MODEL"); +} + +TEST_CASE("Radial GRM with two component linear binding Jacobian", "[RadGRM],[UnitOp],[Jacobian],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("RADIAL_GENERAL_RATE_MODEL"); + cadet::test::column::testJacobianAD(jpp); +} + +TEST_CASE("Radial GRM LWE one vs two identical particle types match", "[RadGRM],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testOneVsTwoIdenticalParticleTypes("RADIAL_GENERAL_RATE_MODEL", 2e-8, 5e-5); +} + +TEST_CASE("Radial GRM LWE separate identical particle types match", "[RadGRM],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testSeparateIdenticalParticleTypes("RADIAL_GENERAL_RATE_MODEL", 1e-15, 1e-15); +} + +TEST_CASE("Radial GRM linear binding single particle matches particle distribution", "[RadGRM],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testLinearMixedParticleTypes("RADIAL_GENERAL_RATE_MODEL", 5e-8, 5e-5); +} + +TEST_CASE("Radial GRM multiple particle types Jacobian analytic vs AD", "[RadGRM],[Jacobian],[AD],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testJacobianMixedParticleTypes("RADIAL_GENERAL_RATE_MODEL"); +} + +TEST_CASE("Radial GRM multiple particle types time derivative Jacobian vs FD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testTimeDerivativeJacobianMixedParticleTypesFD("RADIAL_GENERAL_RATE_MODEL", 1e-6, 0.0, 9e-4); +} + +TEST_CASE("Radial GRM multiple spatially dependent particle types Jacobian analytic vs AD", "[RadGRM],[Jacobian],[AD],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testJacobianSpatiallyMixedParticleTypes("RADIAL_GENERAL_RATE_MODEL"); +} + +TEST_CASE("Radial GRM linear binding single particle matches spatially dependent particle distribution", "[RadGRM],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testLinearSpatiallyMixedParticleTypes("RADIAL_GENERAL_RATE_MODEL", 5e-8, 5e-5); +} + +TEST_CASE("Radial GRM multiple spatially dependent particle types flux Jacobian vs FD", "[RadGRM],[UnitOp],[Residual],[Jacobian],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::test::particle::testArrowHeadJacobianSpatiallyMixedParticleTypes("RADIAL_GENERAL_RATE_MODEL", 1e-6, 1e-8, 1e-5); +} + +TEST_CASE("Radial GRM dynamic reactions Jacobian vs AD bulk", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_GENERAL_RATE_MODEL", true, false, false); +} + +TEST_CASE("Radial GRM dynamic reactions Jacobian vs AD particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_GENERAL_RATE_MODEL", false, true, false); +} + +TEST_CASE("Radial GRM dynamic reactions Jacobian vs AD modified particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_GENERAL_RATE_MODEL", false, true, true); +} + +TEST_CASE("Radial GRM dynamic reactions Jacobian vs AD bulk and particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_GENERAL_RATE_MODEL", true, true, false); +} + +TEST_CASE("Radial GRM dynamic reactions Jacobian vs AD bulk and modified particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_GENERAL_RATE_MODEL", true, true, true); +} + +TEST_CASE("Radial GRM dynamic reactions time derivative Jacobian vs FD bulk", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_GENERAL_RATE_MODEL", true, false, false, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM dynamic reactions time derivative Jacobian vs FD particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_GENERAL_RATE_MODEL", false, true, false, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM dynamic reactions time derivative Jacobian vs FD modified particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_GENERAL_RATE_MODEL", false, true, true, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM dynamic reactions time derivative Jacobian vs FD bulk and particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_GENERAL_RATE_MODEL", true, true, false, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_GENERAL_RATE_MODEL", true, true, true, 1e-6, 1e-14, 9e-4); +} + +inline cadet::JsonParameterProvider createColumnWithTwoCompLinearBindingThreeParticleTypes() +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("RADIAL_GENERAL_RATE_MODEL"); + + const double parVolFrac[] = {0.3, 0.6, 0.1}; + const double parFactor[] = {0.9, 0.8}; + cadet::test::particle::extendModelToManyParticleTypes(jpp, 3, parFactor, parVolFrac); + + return jpp; +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions Jacobian vs AD bulk", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, false, false); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions Jacobian vs AD particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, false); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions Jacobian vs AD modified particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, true); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions Jacobian vs AD bulk and particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, false); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions Jacobian vs AD bulk and modified particle", "[RadGRM],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, true); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, false, false, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, false, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, true, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, false, 1e-6, 1e-14, 9e-4); +} + +TEST_CASE("Radial GRM multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[RadGRM],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, true, 1e-6, 1e-14, 9e-4); +} diff --git a/test/RadialLumpedRateModelWithPores.cpp b/test/RadialLumpedRateModelWithPores.cpp new file mode 100644 index 000000000..9d3494403 --- /dev/null +++ b/test/RadialLumpedRateModelWithPores.cpp @@ -0,0 +1,307 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2024: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 + +#include "ColumnTests.hpp" +#include "ParticleHelper.hpp" +#include "ReactionModelTests.hpp" +#include "JsonTestModels.hpp" +#include "Utils.hpp" + +// todo add a meaningful backward flow test + +// todo find analytical solution with linear binding + +// todo find analytical solution without binding + +// todo add (more) numerical reference (and EOC) tests + +TEST_CASE("Radial LRMP numerical Benchmark with parameter sensitivities for linear case", "[RadLRMP],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +{ + const std::string& modelFilePath = std::string("/data/model_radLRMP_dynLin_1comp_sensbenchmark1.json"); + const std::string& refFilePath = std::string("/data/ref_radLRMP_dynLin_1comp_sensbenchmark1_FV_Z32.h5"); + const std::vector absTol = { 1e-12, 1e-12, 1e-12, 1e-12 }; + const std::vector relTol = { 1e-6, 1e-6, 1e-6, 1e-6 }; + cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "001", absTol, relTol, 32, 0, true); +} + +TEST_CASE("Radial LRMP transport Jacobian", "[RadLRMP],[UnitOp],[Jacobian],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnLinearBenchmark(false, true, "RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); + cadet::test::column::testJacobianAD(jpp); +} + +// NOTE: THE FOLLOWING TESTS ARE ONLY INCLUDED IN THE RELEASE CI, NOT THE STANDARD CI SINCE THEY ARE (TO A HIGH DEGREE) REDUNDANT WITH THE AXIAL FLOW TESTS + +TEST_CASE("Radial LRMP Jacobian forward vs backward flow", "[RadLRMP],[UnitOp],[Residual],[Jacobian],[AD],[ReleaseCI]") +{ + cadet::test::column::testJacobianWenoForwardBackward("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 0); // note: no weno for radial flow models atm +} + +TEST_CASE("Radial LRMP time derivative Jacobian vs FD", "[RadLRMP],[UnitOp],[Residual],[Jacobian],[ReleaseCI]") +{ + cadet::test::column::testTimeDerivativeJacobianFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); +} + +TEST_CASE("Radial LRMP flux Jacobian vs FD", "[RadLRMP],[UnitOp],[Residual],[Jacobian],[ReleaseCI]") +{ + cadet::test::column::testArrowHeadJacobianFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); +} + +TEST_CASE("Radial LRMP sensitivity Jacobians", "[RadLRMP],[UnitOp],[Sensitivity],[ReleaseCI]") +{ + cadet::test::column::testFwdSensJacobians("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 1e-4, 6e-7); +} + +//TEST_CASE("Radial LRMP forward sensitivity vs FD", "[RadLRMP],[Sensitivity],[Simulation],[failedFDtestLRMP]") // todo fix +//{ +// // todo comment on FD issue +// // Relative error is checked first, we use high absolute error for letting +// // some points that are far off pass the error test, too. This is required +// // due to errors in finite differences. +// const double fdStepSize[] = {1e-5, 1e-6, 1e-3, 1e-5}; +// const double absTols[] = {6e5, 2e-2, 2e-2, 1.0}; +// const double relTols[] = {5e-3, 1e-1, 5e-1, 6e-3}; +// const double passRatio[] = {0.87, 0.84, 0.88, 0.95}; +// cadet::test::column::testFwdSensSolutionFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, fdStepSize, absTols, relTols, passRatio); +//} + +//TEST_CASE("Radial LRMP forward sensitivity forward vs backward flow", "[RadLRMP],[Sensitivity],[Simulation],[fixLRMP]") // todo fix +//{ +// // todo why is there a pass ratio when we compare fwd and bwd flow? +// const double absTols[] = {50.0, 2e-10, 1.0, 5e-7}; +// const double relTols[] = {2e-4, 9e-6, 5e-7, 1e-7}; +// const double passRatio[] = {1.0, 0.99, 0.98, 0.99}; // todo? works for 0.79, 0.99, 0.98, 0.99 +// cadet::test::column::testFwdSensSolutionForwardBackward("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", absTols, relTols, passRatio); +//} + +TEST_CASE("Radial LRMP consistent initialization with linear binding", "[RadLRMP],[ConsistentInit],[ReleaseCI]") +{ + cadet::test::column::testConsistentInitializationLinearBinding("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 1e-12, 1e-12); +} + +//TEST_CASE("Radial LRMP consistent initialization with SMA binding", "[RadLRMP],[ConsistentInit],[fixLRMP]") +//{ +// std::vector y(4 + 4 * 16 + 16 * (4 + 4) + 4 * 16, 0.0); +//// Optimal values: +//// const double bindingCell[] = {1.2, 2.0, 1.0, 1.5, 858.034, 66.7896, 3.53273, 2.53153, +//// 1.0, 1.8, 1.5, 1.6, 856.173, 64.457, 5.73227, 2.85286}; +// const double bindingCell[] = {1.2, 2.0, 1.0, 1.5, 840.0, 63.0, 3.0, 3.0, +// 1.0, 1.8, 1.5, 1.6, 840.0, 63.0, 6.0, 3.0}; +// cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 + 4 * 16); +// cadet::test::util::repeat(y.data() + 4 + 4 * 16, bindingCell, 16, 8); +// cadet::test::util::populate(y.data() + 4 + 4 * 16 + 16 * (4 + 4), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 * 16); +// +// cadet::test::column::testConsistentInitializationSMABinding("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", y.data(), 1e-14, 1e-5); +//} + +TEST_CASE("Radial LRMP consistent sensitivity initialization with linear binding", "[RadLRMP],[ConsistentInit],[Sensitivity],[ReleaseCI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 + 4 * 16 + 16 * (4 + 4) + 4 * 16; + std::vector y(numDofs, 0.0); + std::vector yDot(numDofs, 0.0); + cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, numDofs); + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + cadet::test::column::testConsistentInitializationSensitivity("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", y.data(), yDot.data(), true, 1e-14); +} + +TEST_CASE("Radial LRMP consistent sensitivity initialization with SMA binding", "[RadLRMP],[ConsistentInit],[Sensitivity],[ReleaseCI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 + 4 * 16 + 16 * (4 + 4) + 4 * 16; + std::vector y(numDofs, 0.0); + std::vector yDot(numDofs, 0.0); + + const double bindingCell[] = {1.0, 1.8, 1.5, 1.6, 840.0, 63.0, 6.0, 3.0}; + cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 + 4 * 16); + cadet::test::util::repeat(y.data() + 4 + 4 * 16, bindingCell, 8, 16); + cadet::test::util::populate(y.data() + 4 + 4 * 16 + 16 * (4 + 4), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 * 16); + + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + cadet::test::column::testConsistentInitializationSensitivity("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", y.data(), yDot.data(), false, 1e-10); +} + +TEST_CASE("Radial LRMP inlet DOF Jacobian", "[RadLRMP],[UnitOp],[Jacobian],[Inlet],[ReleaseCI]") +{ + cadet::test::column::testInletDofJacobian("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); +} + +TEST_CASE("Radial LRMP with two component linear binding Jacobian", "[RadLRMP],[UnitOp],[Jacobian],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); + cadet::test::column::testJacobianAD(jpp); +} + +TEST_CASE("Radial LRMP LWE one vs two identical particle types match", "[RadLRMP],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testOneVsTwoIdenticalParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 2.2e-8, 6e-5); +} + +TEST_CASE("Radial LRMP LWE separate identical particle types match", "[RadLRMP],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testSeparateIdenticalParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 1e-15, 1e-15); +} + +TEST_CASE("Radial LRMP linear binding single particle matches particle distribution", "[RadLRMP],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testLinearMixedParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 5e-8, 5e-5); +} + +TEST_CASE("Radial LRMP multiple particle types Jacobian analytic vs AD", "[RadLRMP],[Jacobian],[AD],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testJacobianMixedParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); +} + +TEST_CASE("Radial LRMP multiple particle types time derivative Jacobian vs FD", "[RadLRMP],[UnitOp],[Residual],[Jacobian],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::test::particle::testTimeDerivativeJacobianMixedParticleTypesFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 1e-6, 0.0, 9e-4); +} + +TEST_CASE("Radial LRMP multiple spatially dependent particle types Jacobian analytic vs AD", "[RadLRMP],[Jacobian],[AD],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testJacobianSpatiallyMixedParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); +} + +TEST_CASE("Radial LRMP linear binding single particle matches spatially dependent particle distribution", "[RadLRMP],[Simulation],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testLinearSpatiallyMixedParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 5e-8, 5e-5); +} + +TEST_CASE("Radial LRMP multiple spatially dependent particle types flux Jacobian vs FD", "[RadLRMP],[UnitOp],[Residual],[Jacobian],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::test::particle::testArrowHeadJacobianSpatiallyMixedParticleTypes("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", 1e-6, 1e-8, 1e-5); +} + +TEST_CASE("Radial LRMP dynamic reactions Jacobian vs AD bulk", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, false, false); +} + +TEST_CASE("Radial LRMP dynamic reactions Jacobian vs AD particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", false, true, false); +} + +TEST_CASE("Radial LRMP dynamic reactions Jacobian vs AD modified particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", false, true, true); +} + +TEST_CASE("Radial LRMP dynamic reactions Jacobian vs AD bulk and particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, true, false); +} + +TEST_CASE("Radial LRMP dynamic reactions Jacobian vs AD bulk and modified particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, true, true); +} + +TEST_CASE("Radial LRMP dynamic reactions time derivative Jacobian vs FD bulk", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, false, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP dynamic reactions time derivative Jacobian vs FD particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", false, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP dynamic reactions time derivative Jacobian vs FD modified particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", false, true, true, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP dynamic reactions time derivative Jacobian vs FD bulk and particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITH_PORES", true, true, true, 1e-6, 1e-14, 8e-4); +} + +inline cadet::JsonParameterProvider createColumnWithTwoCompLinearBindingThreeParticleTypes() +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("RADIAL_LUMPED_RATE_MODEL_WITH_PORES"); + + const double parVolFrac[] = {0.3, 0.6, 0.1}; + const double parFactor[] = {0.9, 0.8}; + cadet::test::particle::extendModelToManyParticleTypes(jpp, 3, parFactor, parVolFrac); + + return jpp; +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions Jacobian vs AD bulk", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, false, false); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions Jacobian vs AD particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, false); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions Jacobian vs AD modified particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, true); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions Jacobian vs AD bulk and particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, false); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions Jacobian vs AD bulk and modified particle", "[RadLRMP],[Jacobian],[AD],[ReactionModel],[ParticleType],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, true); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, false, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, true, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRMP multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[RadLRMP],[Jacobian],[Residual],[ReactionModel],[ParticleType],[ReleaseCI],[FD]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, true, 1e-6, 1e-14, 8e-4); +} diff --git a/test/RadialLumpedRateModelWithoutPores.cpp b/test/RadialLumpedRateModelWithoutPores.cpp new file mode 100644 index 000000000..eeba85612 --- /dev/null +++ b/test/RadialLumpedRateModelWithoutPores.cpp @@ -0,0 +1,156 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2024: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 + +#include "ColumnTests.hpp" +#include "ReactionModelTests.hpp" +#include "Utils.hpp" +#include "JsonTestModels.hpp" + +// todo add a meaningful backward flow test + +// todo find analytical solution with linear binding + +// todo find analytical solution without binding + +// todo add (more) numerical reference (and EOC) tests + +TEST_CASE("Radial LRM numerical Benchmark with parameter sensitivities for linear case", "[RadLRM],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +{ + const std::string& modelFilePath = std::string("/data/model_radLRM_dynLin_1comp_sensbenchmark1.json"); + const std::string& refFilePath = std::string("/data/ref_radLRM_dynLin_1comp_sensbenchmark1_FV_Z32.h5"); + const std::vector absTol = { 1e-12, 1e-12, 1e-12, 1e-12 }; + const std::vector relTol = { 1e-5, 1e-6, 1e-6, 1e-6 }; + cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "001", absTol, relTol, 32, 0, false); +} + +TEST_CASE("Radial LRM transport Jacobian", "[RadLRM],[UnitOp],[Jacobian],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnLinearBenchmark(false, true, "RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES"); + cadet::test::column::testJacobianAD(jpp); +} + +// NOTE: THE FOLLOWING TESTS ARE ONLY INCLUDED IN THE RELEASE CI, NOT THE STANDARD CI SINCE THEY ARE (TO A HIGH DEGREE) REDUNDANT WITH THE AXIAL FLOW TESTS + +TEST_CASE("Radial LRM Jacobian forward vs backward flow", "[RadLRM],[UnitOp],[Residual],[Jacobian],[AD],[ReleaseCI]") +{ + cadet::test::column::testJacobianWenoForwardBackward("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", 0); // note: no weno available for radial flow atm +} + +TEST_CASE("Radial LRM time derivative Jacobian vs FD", "[RadLRM],[UnitOp],[Residual],[Jacobian],[ReleaseCI],[FD]") +{ + cadet::test::column::testTimeDerivativeJacobianFD("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES"); +} + +TEST_CASE("Radial LRM sensitivity Jacobians", "[RadLRM],[UnitOp],[Sensitivity],[ReleaseCI]") +{ + cadet::test::column::testFwdSensJacobians("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", 1e-4, 3e-7, 5e-5); +} + +//TEST_CASE("Radial LRM forward sensitivity vs FD", "[RadLRM],[Sensitivity],[Simulation],[failedFDtestLRM],[FD]") // todo (for all models) find tolerances +//{ +// // Relative error is checked first, we use high absolute error for letting +// // some points that are far off pass the error test, too. This is required +// // due to errors in finite differences. +// const double fdStepSize[] = {5e-3, 5e-3, 5e-3, 1e-3}; +// const double absTols[] = {2e8, 8e-3, 2e-2, 3e-1}; +// const double relTols[] = {1e-1, 5e-1, 5e-2, 1e-2}; +// const double passRatio[] = {0.88, 0.84, 0.73, 0.87}; +// cadet::test::column::testFwdSensSolutionFD("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", false, fdStepSize, absTols, relTols, passRatio); +//} +// +//TEST_CASE("Radial LRM forward sensitivity forward vs backward flow", "[RadLRM],[Sensitivity],[Simulation],[fixLRM]") // todo (for all models) find tolerances? why is there a pass ratio here, shouldnt this be precise? +//{ +// const double absTols[] = {500.0, 8e-7, 9e-7, 2e-3}; +// const double relTols[] = {7e-3, 5e-5, 5e-5, 9e-4}; +// const double passRatio[] = {0.99, 0.97, 0.97, 0.98}; +// cadet::test::column::testFwdSensSolutionForwardBackward("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", absTols, relTols, passRatio); +//} + +TEST_CASE("Radial LRM consistent initialization with linear binding", "[RadLRM],[ConsistentInit],[ReleaseCI]") +{ + cadet::test::column::testConsistentInitializationLinearBinding("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", 1e-12, 1e-12); +} + +//TEST_CASE("Radial LRM consistent initialization with SMA binding", "[RadLRM],[ConsistentInit],[fixLRM]") // todo (for all models) fix +//{ +// std::vector y(4 + 16 * (4 + 4), 0.0); +// // Optimal values: +// // const double bindingCell[] = {1.2, 2.0, 1.0, 1.5, 858.034, 66.7896, 3.53273, 2.53153, +// // 1.0, 1.8, 1.5, 1.6, 856.173, 64.457, 5.73227, 2.85286}; +// const double bindingCell[] = {1.2, 2.0, 1.0, 1.5, 840.0, 63.0, 3.0, 3.0, +// 1.0, 1.8, 1.5, 1.6, 840.0, 63.0, 6.0, 3.0}; +// cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4); +// cadet::test::util::repeat(y.data() + 4, bindingCell, 16, 8); +// +// cadet::test::column::testConsistentInitializationSMABinding("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", y.data(), 1e-14, 1e-5); +//} + +TEST_CASE("Radial LRM consistent sensitivity initialization with linear binding", "[RadLRM],[ConsistentInit],[Sensitivity],[ReleaseCI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 + 16 * (4 + 4); + std::vector y(numDofs, 0.0); + std::vector yDot(numDofs, 0.0); + cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, numDofs); + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + cadet::test::column::testConsistentInitializationSensitivity("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", y.data(), yDot.data(), true, 1e-12); +} + +TEST_CASE("Radial LRM consistent sensitivity initialization with SMA binding", "[RadLRM],[ConsistentInit],[Sensitivity],[ReleaseCI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 + 16 * (4 + 4); + std::vector y(numDofs, 0.0); + std::vector yDot(numDofs, 0.0); + + const double bindingCell[] = {1.0, 1.8, 1.5, 1.6, 840.0, 63.0, 6.0, 3.0}; + cadet::test::util::populate(y.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4); + cadet::test::util::repeat(y.data() + 4, bindingCell, 8, 16); + + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + cadet::test::column::testConsistentInitializationSensitivity("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", y.data(), yDot.data(), false, 1e-9); +} + +TEST_CASE("Radial LRM inlet DOF Jacobian", "[RadLRM],[UnitOp],[Jacobian],[Inlet],[ReleaseCI]") +{ + cadet::test::column::testInletDofJacobian("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES"); +} + +TEST_CASE("Radial LRM with two component linear binding Jacobian", "[RadLRM],[UnitOp],[Jacobian],[ReleaseCI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES"); + cadet::test::column::testJacobianAD(jpp); +} + +TEST_CASE("Radial LRM dynamic reactions Jacobian vs AD bulk", "[RadLRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", true, false, false); +} + +TEST_CASE("Radial LRM dynamic reactions Jacobian vs AD modified bulk", "[RadLRM],[Jacobian],[AD],[ReactionModel],[ReleaseCI]") +{ + cadet::test::reaction::testUnitJacobianDynamicReactionsAD("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", true, false, true); +} + +TEST_CASE("Radial LRM dynamic reactions time derivative Jacobian vs FD bulk", "[RadLRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", true, false, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("Radial LRM dynamic reactions time derivative Jacobian vs FD modified bulk", "[RadLRM],[Jacobian],[Residual],[ReactionModel],[ReleaseCI],[FD]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", true, false, true, 1e-6, 1e-14, 8e-4); +} diff --git a/test/TimeIntegrator.cpp b/test/TimeIntegrator.cpp new file mode 100644 index 000000000..7852c4cc1 --- /dev/null +++ b/test/TimeIntegrator.cpp @@ -0,0 +1,534 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 +#include +#include "TimeIntegrator.hpp" + +#include +#include +#include +#include + +#include "LoggingUtils.hpp" +#include "Logging.hpp" + +namespace +{ + inline bool hasNaN(double const* const y, unsigned int size) + { + for (unsigned int i = 0; i < size; ++i) + { + if (std::isnan(y[i])) + return true; + } + return false; + } + + inline bool hasNaN(const N_Vector p) + { + return hasNaN(NVEC_DATA(p), NVEC_LENGTH(p)); + } + + inline std::string getIDAReturnFlagName(int solverFlag) + { + char const* const retFlagName = IDAGetReturnFlagName(solverFlag); + const std::string flagName = retFlagName; + std::free(const_cast(retFlagName)); + + return flagName; + } + + /** + * @brief IDAS error handler function + * @details Handles errors reported by the IDAS solver. See section 4.6.2 of the IDAS manual for details. + */ + void idasErrorHandler(int error_code, const char* module, const char* function, char* msg, void* eh_data) + { + std::ostringstream oss; + oss << "In function '" << function << "' of module '" << module << "', error code '" << getIDAReturnFlagName(error_code) << "':\n" << msg; + + // @todo Find an error handling system and put it here + if (error_code < 0) + { + // Error + LOG(Error) << oss.str(); + } + else + { + // Warning + LOG(Warning) << oss.str(); + } + } + + /** + * @brief IDAS wrapper function to call the model's residual() method + */ + int residualDaeWrapper(double t, N_Vector y, N_Vector yDot, N_Vector res, void* userData) + { + cadet::test::TimeIntegrator* const sim = static_cast(userData); + const int secIdx = sim->currentSection(); + + LOG(Trace) << "==> Residual at t = " << t << " sec = " << secIdx; + + return sim->model()->residualWithJacobian(t, secIdx, NVEC_DATA(y), NVEC_DATA(yDot), NVEC_DATA(res)); + } + + /** + * @brief IDAS wrapper function to call the model's linearSolve() method + */ + int linearSolveWrapper(IDAMem IDA_mem, N_Vector rhs, N_Vector weight, N_Vector y, N_Vector yDot, N_Vector res) + { + cadet::test::TimeIntegrator* const sim = static_cast(IDA_mem->ida_lmem); + const double t = IDA_mem->ida_tn; + const double alpha = IDA_mem->ida_cj; + const double tol = IDA_mem->ida_epsNewt; + + LOG(Trace) << "==> Solve at t = " << t << " alpha = " << alpha << " tol = " << tol; + + return sim->model()->linearSolve(t, alpha, tol, NVEC_DATA(rhs), NVEC_DATA(weight), NVEC_DATA(y), NVEC_DATA(yDot)); + } +} + +namespace cadet +{ + +namespace test +{ + + TimeIntegrator::TimeIntegrator() : _model(nullptr), _idaMemBlock(nullptr), _vecStateY(nullptr), + _vecStateYdot(nullptr), _absTol(1, 1.0e-8), _relTol(1.0e-6), _initStepSize(1, 1.0e-6), + _maxSteps(10000), _maxStepSize(0.0), _maxNewtonIter(3), _maxErrorTestFail(7), _maxConvTestFail(10), + _curSec(0) + { + } + + TimeIntegrator::~TimeIntegrator() CADET_NOEXCEPT + { + if (_vecStateYdot) + NVec_Destroy(_vecStateYdot); + if (_vecStateY) + NVec_Destroy(_vecStateY); + + if (_idaMemBlock) + IDAFree(&_idaMemBlock); + } + + void TimeIntegrator::initializeModel(IDiffEqModel& model) + { + _model = &model; + + // Allocate and initialize state vectors + const unsigned int nDOFs = _model->numDofs(); + _vecStateY = NVec_New(nDOFs); + _vecStateYdot = NVec_New(nDOFs); + + // Initialize with all zeros, correct initial conditions will be set later + NVec_Const(0.0, _vecStateY); + NVec_Const(0.0, _vecStateYdot); + + // Create IDAS internal memory + _idaMemBlock = IDACreate(); + + // IDAS Step 4.1: Specify error handler function + IDASetErrHandlerFn(_idaMemBlock, &idasErrorHandler, this); + + // IDAS Step 5: Initialize the solver + _model->applyInitialCondition(NVEC_DATA(_vecStateY), NVEC_DATA(_vecStateYdot)); + + // Use 0.0 as beginning of simulation time if we haven't set section times yet + if (_sectionTimes.size() > 0) + IDAInit(_idaMemBlock, &residualDaeWrapper, _sectionTimes[0], _vecStateY, _vecStateYdot); + else + IDAInit(_idaMemBlock, &residualDaeWrapper, 0.0, _vecStateY, _vecStateYdot); + + // IDAS Step 6: Specify integration tolerances (S: scalar; V: array) + updateMainErrorTolerances(); + + // IDAS Step 7.1: Set optional inputs + + // Set time integrator parameters + IDASetMaxNumSteps(_idaMemBlock, _maxSteps); + IDASetMaxStep(_idaMemBlock, _maxStepSize); + IDASetMaxNonlinIters(_idaMemBlock, _maxNewtonIter); + IDASetMaxErrTestFails(_idaMemBlock, _maxErrorTestFail); + IDASetMaxConvFails(_idaMemBlock, _maxConvTestFail); + + // Specify the linear solver. + IDAMem IDA_mem = static_cast(_idaMemBlock); + + IDA_mem->ida_lsolve = &linearSolveWrapper; + IDA_mem->ida_lmem = this; + IDA_mem->ida_linit = nullptr; + IDA_mem->ida_lsetup = nullptr; + IDA_mem->ida_lperf = nullptr; + IDA_mem->ida_lfree = nullptr; +// IDA_mem->ida_efun = &weightWrapper; +// IDA_mem->ida_user_efun = 1; +#if CADET_SUNDIALS_IFACE <= 2 + IDA_mem->ida_setupNonNull = false; +#endif + + // Attach user data structure + IDASetUserData(_idaMemBlock, this); + } + + void TimeIntegrator::configureTimeIntegrator(double relTol, double absTol, double initStepSize, unsigned int maxSteps, double maxStepSize) + { + _absTol.clear(); + _absTol.push_back(absTol); + + _relTol = relTol; + _maxSteps = maxSteps; + _maxStepSize = maxStepSize; + + _initStepSize.clear(); + _initStepSize.push_back(initStepSize); + + updateMainErrorTolerances(); + } + + void TimeIntegrator::configureTimeIntegrator(double relTol, double absTol, const std::vector& initStepSizes, unsigned int maxSteps, double maxStepSize) + { + _absTol.clear(); + _absTol.push_back(absTol); + + _relTol = relTol; + _maxSteps = maxSteps; + _maxStepSize = maxStepSize; + _initStepSize = initStepSizes; + + updateMainErrorTolerances(); + } + + void TimeIntegrator::updateMainErrorTolerances() + { + if (!_idaMemBlock) + return; + + if (_absTol.size() > 1) + { + if (!_model) + return; + + const unsigned int nDofs = _model->numDofs(); + N_Vector absTolTemp = NVec_New(nDofs); + + // Check whether user has given us full absolute error for all (pure) DOFs + if (_absTol.size() >= nDofs) + { + // Copy error tolerances for pure data + std::copy(_absTol.data(), _absTol.data() + nDofs, NVEC_DATA(absTolTemp)); + } + + IDASVtolerances(_idaMemBlock, _relTol, absTolTemp); + NVec_Destroy(absTolTemp); + } + else + IDASStolerances(_idaMemBlock, _relTol, _absTol[0]); + } + + const std::vector& TimeIntegrator::getSolutionTimes() const + { + return _solutionTimes; + } + + void TimeIntegrator::setSectionTimes(const std::vector& sectionTimes) + { + setSectionTimes(sectionTimes, std::vector(sectionTimes.size() - 1, false)); + } + + void TimeIntegrator::setSectionTimes(const std::vector& sectionTimes, const std::vector& sectionContinuity) + { + // Ensure that at least one section is defined + if (sectionTimes.size() < 2) + throw std::invalid_argument("At least one section has to be specified!"); + + // Ensure that all section start times are smaller than their end times + for (std::size_t i = 0; i < sectionTimes.size() - 1; ++i) + if (sectionTimes[i] > sectionTimes[i + 1]) + { + LOG(Error) << "The end time of each section must be greater than its start time (failed for section " << i << ")!"; + return; + } + + _sectionTimes = sectionTimes; + _sectionContinuity = sectionContinuity; + } + + int TimeIntegrator::getNextSection(double t, int startIdx) const + { + if (t < _sectionTimes[startIdx]) + return -1; + + for (std::size_t i = startIdx; i < _sectionTimes.size() - 1; ++i) + { + if (_sectionTimes[i] >= t) + return i; + } + + return -1; + } + + void TimeIntegrator::integrate() + { + // In this function the model is integrated by IDAS from the SUNDIALS package. + // The authors of IDAS recommend to restart the time integrator when a discontinuity + // is encountered (see https://computation.llnl.gov/casc/sundials/support/notes.html#disc). + // The sectionTime (together with the sectionContinuity) array indicates such + // discontinuitites and the solver is restarted accordingly. This also requires + // the computation of consistent initial values for each restart. + + std::vector::const_iterator it; + double tOut = 0.0; + + const bool writeAtUserTimes = _solutionTimes.size() > 0; + + // Decide whether to use user specified solution output times (IDA_NORMAL) + // or internal integrator steps (IDA_ONE_STEP) + int idaTask = IDA_ONE_STEP; + if (writeAtUserTimes) + { + idaTask = IDA_NORMAL; + } + + LOG(Debug) << "Integration span: [" << _sectionTimes[0] << ", " << _sectionTimes.back() << "] sections"; + + if (writeAtUserTimes) + { + LOG(Debug) << "Solution time span: [" << _solutionTimes[0] << ", " << _solutionTimes.back() << "]"; + } + + double curT = _sectionTimes[0]; + _curSec = 0; + const double tEnd = writeAtUserTimes ? _solutionTimes.back() : _sectionTimes.back(); + while (curT < tEnd) + { + // Get smallest index with t_i >= curT (t_i being a _sectionTimes element) + // This will return i if curT == _sectionTimes[i], which effectively advances + // the index if required + _curSec = getNextSection(curT, _curSec); + const double startTime = _sectionTimes[_curSec]; + + // Determine continuous time slice + unsigned int skip = 1; // Always finish the current section + for (std::size_t i = _curSec; i < _sectionTimes.size() - 2; ++i) + { + if (!_sectionContinuity[i]) + break; + + // This is a continuous section transition, so we don't need to + // restart the integrator and just integrate for a longer time + ++skip; + } + + const double endTime = writeAtUserTimes ? std::min(_sectionTimes[_curSec + skip], tEnd) : _sectionTimes[_curSec + skip]; + curT = startTime; + + LOG(Debug) << " ###### SECTION " << _curSec << " from " << startTime << " to " << endTime; + + // IDAS Step 7.3: Set the initial step size + const double stepSize = _initStepSize.size() > 1 ? _initStepSize[_curSec] : _initStepSize[0]; + IDASetInitStep(_idaMemBlock, stepSize); + + // IDAS Step 7.4: Set the stop time + IDASetStopTime(_idaMemBlock, endTime); + + // Update Jacobian and compute consistent initial values + _model->notifyDiscontinuousSectionTransition(curT, _curSec, NVEC_DATA(_vecStateY), NVEC_DATA(_vecStateYdot)); + + // IDAS Step 5.2: Re-initialization of the solver + IDAReInit(_idaMemBlock, startTime, _vecStateY, _vecStateYdot); + + // Inititalize the IDA solver flag + int solverFlag = IDA_SUCCESS; + + if (writeAtUserTimes) + { + // Write initial conditions only if desired by user + if (_curSec == 0 && _solutionTimes.front() == curT) + _model->saveSolution(curT, NVEC_DATA(_vecStateY), NVEC_DATA(_vecStateYdot)); + + // Initialize iterator and forward it to the first solution time that lies inside the current section + it = _solutionTimes.begin(); + while ((*it) <= startTime) ++it; + } + else + { + // Always write initial conditions if solutions are written at integration times + if (_curSec == 0) + _model->saveSolution(curT, NVEC_DATA(_vecStateY), NVEC_DATA(_vecStateYdot)); + + // Here tOut - only during the first call to IDASolve - specifies the direction + // and rough scale of the independent variable, see IDAS Guide p.33 + tOut = endTime; + } + + // Main loop which integrates the system until reaching the end time of the current section + // or until an error occures + while ((solverFlag == IDA_SUCCESS) || (solverFlag == IDA_ROOT_RETURN)) + { + // Update tOut if we write solutions at user specified times + if (writeAtUserTimes) + { + // Check if user specified times are sufficiently long. + // otherwise integrate till IDA_TSTOP_RETURN + if (it == _solutionTimes.end()) + break; + else + tOut = *it; + } + + // IDA Step 11: Advance solution in time + solverFlag = IDASolve(_idaMemBlock, tOut, &curT, _vecStateY, _vecStateYdot, idaTask); + LOG(Debug) << "Solve from " << curT << " to " << tOut << " => " + << (solverFlag == IDA_SUCCESS ? "IDA_SUCCESS" : "") << (solverFlag == IDA_TSTOP_RETURN ? "IDA_TSTOP_RETURN" : ""); + +#ifdef CADET_DEBUG + { + long nTimeSteps = 0; + IDAGetNumSteps(_idaMemBlock, &nTimeSteps); + + double curStepSize = 0.0; + IDAGetCurrentStep(_idaMemBlock, &curStepSize); + + double lastStepSize = 0.0; + IDAGetLastStep(_idaMemBlock, &lastStepSize); + + long nResEvals = 0; + IDAGetNumResEvals(_idaMemBlock, &nResEvals); + + long nErrTestFail = 0; + IDAGetNumErrTestFails(_idaMemBlock, &nErrTestFail); + + long nNonLin = 0; + IDAGetNumNonlinSolvIters(_idaMemBlock, &nNonLin); + + long nConvFail = 0; + IDAGetNumNonlinSolvConvFails(_idaMemBlock, &nConvFail); + + LOG(Debug) << "=== #Steps: " << nTimeSteps << "\n=== #Residual evals: " << nResEvals << "\n=== #Error test fails: " << nErrTestFail + << "\n=== #Newton iters: " << nNonLin << "\n=== #Conv test fails: " << nConvFail << "\n=== Last step size: " << lastStepSize + << "\n=== Next step size: " << curStepSize; + } + #endif + switch (solverFlag) + { + case IDA_SUCCESS: + // tOut was reached + _model->saveSolution(curT, NVEC_DATA(_vecStateY), NVEC_DATA(_vecStateYdot)); + + if (writeAtUserTimes) + ++it; + + break; + case IDA_ROOT_RETURN: + // A root was found + // Eventually call some routine + break; + case IDA_TSTOP_RETURN: + // Section end time was reached (in previous step) + if (!writeAtUserTimes && (endTime == _sectionTimes.back())) + { + // Write a solution for the ultimate endTime in the last section, + // when we write at integration times. + _model->saveSolution(curT, NVEC_DATA(_vecStateY), NVEC_DATA(_vecStateYdot)); + } + + break; + default: + // An error occured + const std::string errorFlag = getIDAReturnFlagName(solverFlag); + LOG(Error) << "IDASolve returned " << errorFlag << " at t = " << curT; + + return; + } // switch + + } // while + + } // for (_sec ...) + + } + + void TimeIntegrator::setRelativeErrorTolerance(double relTol) + { + _relTol = relTol; + updateMainErrorTolerances(); + } + + void TimeIntegrator::setAbsoluteErrorTolerance(double absTol) + { + _absTol.clear(); + _absTol.push_back(absTol); + updateMainErrorTolerances(); + } + + void TimeIntegrator::setAbsoluteErrorTolerance(const std::vector& absTol) + { + _absTol = absTol; + updateMainErrorTolerances(); + } + + void TimeIntegrator::setInitialStepSize(double stepSize) + { + _initStepSize.clear(); + _initStepSize.push_back(stepSize); + } + + void TimeIntegrator::setInitialStepSize(const std::vector& stepSize) + { + _initStepSize = stepSize; + } + + void TimeIntegrator::setMaximumSteps(unsigned int maxSteps) + { + _maxSteps = maxSteps; + if (_idaMemBlock) + IDASetMaxNumSteps(_idaMemBlock, _maxSteps); + } + + void TimeIntegrator::setMaximumStepSize(double maxStepSize) + { + _maxStepSize = std::max(0.0, maxStepSize); + if (_idaMemBlock) + IDASetMaxStep(_idaMemBlock, _maxStepSize); + } + + void TimeIntegrator::setMaxNewtonIteration(unsigned int nIter) + { + _maxNewtonIter = nIter; + if (_idaMemBlock) + IDASetMaxNonlinIters(_idaMemBlock, nIter); + } + + void TimeIntegrator::setMaxErrorTestFails(unsigned int nFails) + { + _maxErrorTestFail = nFails; + if (_idaMemBlock) + IDASetMaxErrTestFails(_idaMemBlock, nFails); + } + + void TimeIntegrator::setMaxConvergenceFails(unsigned int nFails) + { + _maxConvTestFail = nFails; + if (_idaMemBlock) + IDASetMaxConvFails(_idaMemBlock, nFails); + } + + void TimeIntegrator::setSolutionTimes(const std::vector& solutionTimes) + { + _solutionTimes = solutionTimes; + } + +} // namespace test + +} // namespace cadet diff --git a/test/TimeIntegrator.hpp b/test/TimeIntegrator.hpp new file mode 100644 index 000000000..ea82a390c --- /dev/null +++ b/test/TimeIntegrator.hpp @@ -0,0 +1,236 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 +// ============================================================================= + +/** + * @file + * ODE/DAE solver for tests + */ + +#ifndef CADETTEST_TIMEINTEGRATOR_SKEL_HPP_ +#define CADETTEST_TIMEINTEGRATOR_SKEL_HPP_ + +#include +#include + +#include "SundialsVector.hpp" +#include + +#include "cadet/cadetCompilerInfo.hpp" + +namespace cadet +{ + +namespace test +{ + +class IDiffEqModel +{ +public: + virtual ~IDiffEqModel() CADET_NOEXCEPT { } + + /** + * @brief Return the number of required DOFs + * @return The number of required DOFs + */ + virtual int numDofs() const CADET_NOEXCEPT = 0; + + /** + * @brief Notifies the model that a discontinuous section transition is in progress + * @details This function is called after time integration of a section has finished and a new + * section is about to be integrated. This allows the model to update internal state before + * consistent initialization is performed. + * + * This function is also called at the beginning of the time integration, which allows + * the model to perform setup operations. + * + * If AD is used by the model, the function has the opportunity to update the seed vectors. + * The general initialization of the seed vectors is performed by prepareADvectors(). + * + * @param [in] t Current time point + * @param [in] secIdx Index of the new section that is about to be integrated + * @param [in,out] vecStateY State of the simulation + * @param [in,out] vecStateYdot Time derivative of simulation state + */ + virtual void notifyDiscontinuousSectionTransition(double t, int secIdx, double* vecStateY, double* vecStateYdot) = 0; + + /** + * @brief Computes the residual + * + * @param [in] time Simulation time + * @param [in] secIdx Current time section + * @param [in] vecStateY State of the simulation + * @param [in] vecStateYdot Time derivative of simulation state + * @param [out] res Pointer to global residual vector + * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error + */ + virtual int residual(double time, int secIdx, double const* vecStateY, double const* vecStateYdot, double* res) = 0; + + /** + * @brief Computes the residual and updates the Jacobian + * + * @param [in] time Simulation time + * @param [in] secIdx Current time section + * @param [in] vecStateY State of the simulation + * @param [in] vecStateYdot Time derivative of simulation state + * @param [out] res Pointer to global residual vector + * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error + */ + virtual int residualWithJacobian(double time, int secIdx, double const* vecStateY, double const* vecStateYdot, double* res) = 0; + + /** + * @brief Computes the @f$ \ell^\infty@f$-norm of the residual vector + * + * @param [in] time Simulation time + * @param [in] secIdx Current time section + * @param [in] vecStateY State of the simulation + * @param [in] vecStateYdot Time derivative of simulation state + * @return the @f$ \ell^\infty@f$-norm of the residual vector + */ + virtual double residualNorm(double time, int secIdx, double const* vecStateY, double const* vecStateYdot) = 0; + + /** + * @brief Computes the solution of the linear system involving the system Jacobian + * @details The system \f[ \left( \frac{\partial F}{\partial y} + \alpha \frac{\partial F}{\partial \dot{y}} \right) x = b \f] + * has to be solved. The right hand side \f$ b \f$ is given by @p rhs, the Jacobians are evaluated at the + * point \f$(y, \dot{y})\f$ given by @p y and @p yDot. The residual @p res at this point, \f$ F(t, y, \dot{y}) \f$, + * may help with this. Error weights (see IDAS guide) are given in @p weight. The solution is returned in @p rhs. + * + * Prior to calling linearSolve() the time integrator calls assembleDAEJacobian() with the same point + * in time and state \f$(t, y, \dot{y})\f$. + * + * @param [in] t Current time point + * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) + * @param [in] tol Error tolerance for the solution of the linear system from outer Newton iteration + * @param [in,out] rhs On entry the right hand side of the linear equation system, on exit the solution + * @param [in] weight Vector with error weights + * @param [in] vecStateY State of the simulation + * @param [in] vecStateYdot Time derivative of simulation state + * @return @c 0 on success, @c -1 on non-recoverable error, and @c +1 on recoverable error + */ + virtual int linearSolve(double t, double alpha, double tol, double* rhs, double const* weight, + double const* vecStateY, double const* vecStateYdot) = 0; + + /** + * @brief Applies initial conditions to the state vector and its time derivative + * @details The initial conditions do not need to be consistent at this point. On a (discontinuous) + * transition from one section to the next, notifyDiscontinuousSectionTransition() is called by + * the time integrator in order to compute consistent initial conditions. Therefore, + * notifyDiscontinuousSectionTransition() is also called at the beginning of the simulation, that is, + * the initial conditions set by this function will be corrected for consistency. + * Note that the state vector and its time derivative are pre-initialized with zero by the + * time integrator. + * + * @param [in,out] vecStateY State of the simulation + * @param [in,out] vecStateYdot Time derivative of simulation state + */ + virtual void applyInitialCondition(double* vecStateY, double* vecStateYdot) const = 0; + + /** + * @brief Save the current solution + * @param [in] t Current time point + * @param [in] vecStateY State of the simulation + * @param [in] vecStateYdot Time derivative of simulation state + */ + virtual void saveSolution(double t, double const* vecStateY, double const* vecStateYdot) = 0; +}; + +/** + * @brief Solves an ODE or DAE + * @details This class is used to run tests that involve ODE / DAE systems + * but not the CADET simulator. + */ +class TimeIntegrator +{ +public: + + TimeIntegrator(); + ~TimeIntegrator() CADET_NOEXCEPT; + + void setSolutionTimes(const std::vector& solutionTimes); + const std::vector& getSolutionTimes() const; + void setSectionTimes(const std::vector& sectionTimes); + void setSectionTimes(const std::vector& sectionTimes, const std::vector& sectionContinuity); + + void initializeModel(IDiffEqModel& model); + + void integrate(); + + void configureTimeIntegrator(double relTol, double absTol, double initStepSize, unsigned int maxSteps, double maxStepSize); + void configureTimeIntegrator(double relTol, double absTol, const std::vector& initStepSizes, unsigned int maxSteps, double maxStepSize); + + void setRelativeErrorTolerance(double relTol); + void setAbsoluteErrorTolerance(double absTol); + void setAbsoluteErrorTolerance(const std::vector& absTol); + void setInitialStepSize(double stepSize); + void setInitialStepSize(const std::vector& stepSize); + void setMaximumSteps(unsigned int maxSteps); + void setMaximumStepSize(double maxStepSize); + void setMaxNewtonIteration(unsigned int nIter); + void setMaxErrorTestFails(unsigned int nFails); + void setMaxConvergenceFails(unsigned int nFails); + void setMaxSensNewtonIteration(unsigned int nIter); + + IDiffEqModel* model() CADET_NOEXCEPT { return _model; } + IDiffEqModel const* model() const CADET_NOEXCEPT { return _model; } + + int currentSection() const CADET_NOEXCEPT { return _curSec; } + +protected: + + /** + * @brief Computes the index of the next section from the given time @p t + * @details Returns the lowest index @c i with @f$ t_i \geq t @f$, where + * @f$ t_i @f$ is an element of @c _sectionTimes. + * @param [in] t Current time + * @param [in] startIdx Index of the first section the search should begin with + * @return Index of the next section corresponding to time @p t + */ + int getNextSection(double t, int startIdx) const; + + void updateMainErrorTolerances(); + + IDiffEqModel* _model; //!< Simulated model, not owned by the Simulator + + void* _idaMemBlock; //!< IDAS internal memory + + /** + * @brief Determines whether the transition from section i to section i+1 is continuous. + * @details The solver will be reset only at discontinuous transitions. The i-th element + * corresponds to the transition from _sectionTimes[i+1] to _sectionTimes[i+2]. + * Therefore size = nsec - 1. + */ + std::vector _sectionContinuity; + + std::vector _solutionTimes; //!< Contains the time transformed user specified times for writing solutions to the output + + N_Vector _vecStateY; //!< IDAS state vector + N_Vector _vecStateYdot; //!< IDAS state vector time derivative + std::vector _sectionTimes; //!< Stores the section times + + std::vector _absTol; //!< Absolute tolerance for the original system in the time integration + double _relTol; //!< Relative tolerance for the original system in the time integration + std::vector _initStepSize; //!< Initial step size for the time integrator + unsigned int _maxSteps; //!< Maximum number of time integration steps + double _maxStepSize; //!< Maximum time step size + + unsigned int _maxNewtonIter; //!< Maximum number of Newton iterations for original DAE system + unsigned int _maxErrorTestFail; //!< Maximum number of local time integration error test failures + unsigned int _maxConvTestFail; //!< Maximum number of Newton iteration failures + + int _curSec; //!< Index of the current section +}; + +} // namespace test + +} // namespace cadet + +#endif // CADETTEST_TIMEINTEGRATOR_SKEL_HPP_ diff --git a/test/TwoDimConvectionDispersionOperator.cpp b/test/TwoDimConvectionDispersionOperator.cpp index da7d1bf3f..8cd5df32e 100644 --- a/test/TwoDimConvectionDispersionOperator.cpp +++ b/test/TwoDimConvectionDispersionOperator.cpp @@ -15,9 +15,11 @@ #include "model/parts/TwoDimensionalConvectionDispersionOperator.hpp" #include "Weno.hpp" +#include "ModelBuilderImpl.hpp" #include "ColumnTests.hpp" #include "Utils.hpp" +#include "Dummies.hpp" #include "common/JsonParameterProvider.hpp" @@ -30,6 +32,9 @@ namespace cadet::JsonParameterProvider jpp(R"json({ "COL_LENGTH": 10, "COL_RADIUS": 1, + "COL_RADIUS_INNER": 0.001, + "COL_RADIUS_OUTER": 0.004, + "CROSS_SECTION_AREA": 0.0003141592653589793, "COL_POROSITY": 0.37, "COL_DISPERSION": 1e-6, "COL_DISPERSION_RADIAL": [1e-4, 1e-4, 1e-4, 1e-4, 1e-4], @@ -61,7 +66,8 @@ namespace // Configure the operator typedef std::unordered_map ParameterMap; ParameterMap parameters; - REQUIRE(convDispOp.configureModelDiscretization(jpp, nComp, nCol, nRad, false)); + cadet::ModelBuilder builder; + REQUIRE(convDispOp.configureModelDiscretization(jpp, builder, nComp, nCol, nRad, false)); REQUIRE(convDispOp.configure(0, jpp, parameters)); } @@ -73,10 +79,10 @@ namespace // Central finite differences y[nInletDof + col] = ref * (1.0 + h); - convDispOp.residual(0.0, 0u, y, nullptr, jacCol1, false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y, nullptr, jacCol1, false, cadet::WithoutParamSensitivity()); y[nInletDof + col] = ref * (1.0 - h); - convDispOp.residual(0.0, 0u, y, nullptr, jacCol2, false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y, nullptr, jacCol2, false, cadet::WithoutParamSensitivity()); y[nInletDof + col] = ref; @@ -128,14 +134,14 @@ void testBulk2DJacobianWenoForwardBackward(int wenoOrder) convDispOp.setFlowRates(i, 1e-2 * convDispOp.crossSection(i) * convDispOp.columnPorosity(i), 0.0); convDispOp.notifyDiscontinuousSectionTransition(0.0, 0u); - convDispOp.residual(0.0, 0u, y.data(), nullptr, jacCol1.data(), true, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, jacCol1.data(), true, cadet::WithoutParamSensitivity()); // Compare Jacobian pattern against FD compareSparseJacobianAgainstFD(convDispOp, nInletDof, nPureDof, y.data(), jacCol1.data(), jacCol2.data(), h, relTol, absTol); // Reverse flow convDispOp.notifyDiscontinuousSectionTransition(0.0, 1u); - convDispOp.residual(0.0, 1u, y.data(), nullptr, jacCol1.data(), true, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 1u, y.data(), nullptr, jacCol1.data(), true, cadet::WithoutParamSensitivity()); // Compare Jacobian pattern against FD compareSparseJacobianAgainstFD(convDispOp, nInletDof, nPureDof, y.data(), jacCol1.data(), jacCol2.data(), h, relTol, absTol); @@ -170,7 +176,7 @@ void testBulk2DJacobianSparsityWeno(int wenoOrder, bool forwardFlow) convDispOp.setFlowRates(i, 1e-2 * convDispOp.crossSection(i) * convDispOp.columnPorosity(i), 0.0); convDispOp.notifyDiscontinuousSectionTransition(0.0, 0u); - convDispOp.residual(0.0, 0u, y.data(), nullptr, jacCol1.data(), true, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, jacCol1.data(), true, cadet::WithoutParamSensitivity()); // Compare Jacobian pattern with FD for (int col = 0; col < nPureDof; ++col) @@ -179,10 +185,10 @@ void testBulk2DJacobianSparsityWeno(int wenoOrder, bool forwardFlow) // Central finite differences y[nInletDof + col] = ref * (1.0 + h); - convDispOp.residual(0.0, 0u, y.data(), nullptr, jacCol1.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, jacCol1.data(), false, cadet::WithoutParamSensitivity()); y[nInletDof + col] = ref * (1.0 - h); - convDispOp.residual(0.0, 0u, y.data(), nullptr, jacCol2.data(), false, cadet::WithoutParamSensitivity()); + convDispOp.residual(DummyModel(), 0.0, 0u, y.data(), nullptr, jacCol2.data(), false, cadet::WithoutParamSensitivity()); y[nInletDof + col] = ref; diff --git a/test/data/convergence_radGRM_dynLin_1comp_sensbenchmark1.json b/test/data/convergence_radGRM_dynLin_1comp_sensbenchmark1.json new file mode 100644 index 000000000..57d0899ca --- /dev/null +++ b/test/data/convergence_radGRM_dynLin_1comp_sensbenchmark1.json @@ -0,0 +1,325 @@ +{ + "meta": { + "source": "CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "info": "CADET-Reference convergence data for the CADET-Database model configuration: radGRM_dynLin_1comp_sensbenchmark1 whose source is CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "name": "convergence_radGRM_dynLin_1comp_sensbenchmark1" + }, + "convergence": { + "outlet": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ], + "$N_e^p$": [ + 1.0, + 2.0, + 4.0, + 8.0, + 16.0 + ], + "Max. error": [ + 0.10047717887169323, + 0.06884771930293548, + 0.04113207219922341, + 0.020686921710650563, + 0.0075757920097664355 + ], + "Max. EOC": [ + 0.0, + 0.5453870943705798, + 0.7431451080075526, + 0.9915447725442371, + 1.4492503550209173 + ], + "$L^1$ error": [ + 27.444772307093782, + 16.96726884808722, + 9.311031948593717, + 4.412999790523286, + 1.5590825969554845 + ], + "$L^1$ EOC": [ + 0.0, + 0.6937770113368859, + 0.8657413821229443, + 1.0771813927774951, + 1.5010623177868905 + ], + "Sim. time": [ + 0.0, + 1.0, + 5.0, + 29.0, + 181.0 + ], + "Min. value": [ + -2.4206371198233206e-17, + -4.786984115568194e-17, + -7.384736368978145e-17, + -1.163931944161052e-16, + -2.8332498260539317e-16 + ], + "DoF": [ + 33.0, + 97.0, + 321.0, + 1153.0, + 4353.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ] + }, + "sens_COL_DISPERSION": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ], + "$N_e^p$": [ + 1.0, + 2.0, + 4.0, + 8.0, + 16.0 + ], + "Max. error": [ + 5038.112863804435, + 4359.864470180447, + 3208.7570210677845, + 1903.648036184422, + 794.1488832005302 + ], + "Max. EOC": [ + 0.0, + 0.20860015330041834, + 0.4422687408289547, + 0.7532477828783793, + 1.2612853573388654 + ], + "$L^1$ error": [ + 1093611.387963013, + 942109.0738332383, + 667668.4747609716, + 374733.5944462904, + 146613.2862224208 + ], + "$L^1$ EOC": [ + 0.0, + 0.2151341667579377, + 0.4967621770510688, + 0.8332666027876728, + 1.3538494717919773 + ], + "Sim. time": [ + 0.0, + 1.0, + 5.0, + 29.0, + 181.0 + ], + "Min. value": [ + -390.51816069704233, + -1125.3188035211267, + -2321.8192860485724, + -3639.2836888135857, + -4707.3722266501445 + ], + "DoF": [ + 33.0, + 97.0, + 321.0, + 1153.0, + 4353.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ] + }, + "sens_PAR_DIFFUSION": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ], + "$N_e^p$": [ + 1.0, + 2.0, + 4.0, + 8.0, + 16.0 + ], + "Max. error": [ + 823452544.0587317, + 669885237.4385803, + 472037312.46231127, + 272253121.34295905, + 109948207.69138962 + ], + "Max. EOC": [ + 0.0, + 0.29777155026765856, + 0.5050130561559956, + 0.7939523134296324, + 1.308124502878476 + ], + "$L^1$ error": [ + 186347443501.13043, + 137673751379.54492, + 91040832186.98495, + 49689754637.679, + 19193789794.114105 + ], + "$L^1$ EOC": [ + 0.0, + 0.43674150273512746, + 0.5966678739281284, + 0.8735653270639616, + 1.3723088197265803 + ], + "Sim. time": [ + 0.0, + 1.0, + 5.0, + 29.0, + 181.0 + ], + "Min. value": [ + -390.51816069704233, + -1125.3188035211267, + -2321.8192860485724, + -3639.2836888135857, + -4707.3722266501445 + ], + "DoF": [ + 33.0, + 97.0, + 321.0, + 1153.0, + 4353.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ] + }, + "sens_LIN_KA": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ], + "$N_e^p$": [ + 1.0, + 2.0, + 4.0, + 8.0, + 16.0 + ], + "Max. error": [ + 0.0017453266549911726, + 0.0013529357598690507, + 0.0008906937093877652, + 0.00047981482037792256, + 0.0001836991893327494 + ], + "Max. EOC": [ + 0.0, + 0.3674037373669696, + 0.60309202883902, + 0.8924516850493092, + 1.3851324600276052 + ], + "$L^1$ error": [ + 0.4243265275865404, + 0.30863597121699743, + 0.18908068816851165, + 0.09580837527650286, + 0.035156240103199045 + ], + "$L^1$ EOC": [ + 0.0, + 0.45926865790780025, + 0.7069041958085048, + 0.9807783379833593, + 1.4463709923973438 + ], + "Sim. time": [ + 0.0, + 1.0, + 5.0, + 29.0, + 181.0 + ], + "Min. value": [ + -390.51816069704233, + -1125.3188035211267, + -2321.8192860485724, + -3639.2836888135857, + -4707.3722266501445 + ], + "DoF": [ + 33.0, + 97.0, + 321.0, + 1153.0, + 4353.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0 + ] + } + } +} \ No newline at end of file diff --git a/test/data/convergence_radLRMP_dynLin_1comp_sensbenchmark1.json b/test/data/convergence_radLRMP_dynLin_1comp_sensbenchmark1.json new file mode 100644 index 000000000..75c798332 --- /dev/null +++ b/test/data/convergence_radLRMP_dynLin_1comp_sensbenchmark1.json @@ -0,0 +1,337 @@ +{ + "meta": { + "source": "CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "info": "CADET-Reference convergence data for the CADET-Database model configuration: radLRMP_dynLin_1comp_sensbenchmark1 whose source is CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "name": "convergence_radLRMP_dynLin_1comp_sensbenchmark1" + }, + "convergence": { + "outlet": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ], + "Max. error": [ + 0.4311317397467228, + 0.37016166042313636, + 0.3086279387929304, + 0.23570625153259728, + 0.15965016220748685, + 0.08070124420708309 + ], + "Max. EOC": [ + 0.0, + 0.2199733015751386, + 0.2622868065326815, + 0.38887864589423804, + 0.5620760043443033, + 0.9842511972531228 + ], + "$L^1$ error": [ + 20.348437026086728, + 13.884876384386581, + 8.990717523399642, + 5.4693592339230905, + 2.9752117664895783, + 1.2215293904164533 + ], + "$L^1$ EOC": [ + 0.0, + 0.551403651549361, + 0.6270061702460283, + 0.71706443458007, + 0.8783794644076103, + 1.2843037829426793 + ], + "Sim. time": [ + 0.0, + 0.0, + 1.0, + 4.0, + 12.0, + 46.0 + ], + "Min. value": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0 + ], + "DoF": [ + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ] + }, + "sens_COL_DISPERSION": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ], + "Max. error": [ + 149484.1135170346, + 147478.788052433, + 143195.052714192, + 133521.56093586746, + 118943.26047829755, + 81064.3357309392 + ], + "Max. EOC": [ + 0.0, + 0.019484703737898953, + 0.04252581645925623, + 0.10090892362954432, + 0.16679919716667635, + 0.553134283400471 + ], + "$L^1$ error": [ + 2144444.268931193, + 2215537.1065930147, + 2217778.4593278803, + 2093044.3922270765, + 1727038.3298645492, + 1056283.7496644596 + ], + "$L^1$ EOC": [ + 0.0, + -0.047052666928848984, + -0.0014587676888301229, + 0.08351234685576343, + 0.27730280824640857, + 0.7093026642276024 + ], + "Sim. time": [ + 0.0, + 0.0, + 1.0, + 4.0, + 12.0, + 46.0 + ], + "Min. value": [ + -2773.4619878355434, + -5272.691466691515, + -10864.506853547624, + -21902.07515411579, + -43279.4401245699, + -83288.2133424808 + ], + "DoF": [ + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ] + }, + "sens_FILM_DIFFUSION": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ], + "Max. error": [ + 0.27565668423466283, + 0.27353405577761086, + 0.26753303780168025, + 0.2515106294852524, + 0.21218163220829706, + 0.1429674860555759 + ], + "Max. EOC": [ + 0.0, + 0.01115212101815994, + 0.03200340216967092, + 0.08909768840013216, + 0.24531960051881002, + 0.569612689011094 + ], + "$L^1$ error": [ + 3.9631186398552236, + 4.036169370167841, + 4.000280213064058, + 3.735690389353944, + 3.0678788390670944, + 1.8767863980875146 + ], + "$L^1$ EOC": [ + 0.0, + -0.02635055921918342, + 0.012885653707187824, + 0.09872617121966332, + 0.28413338369077046, + 0.7089770443900714 + ], + "Sim. time": [ + 0.0, + 0.0, + 1.0, + 4.0, + 12.0, + 46.0 + ], + "Min. value": [ + -2773.4619878355434, + -5272.691466691515, + -10864.506853547624, + -21902.07515411579, + -43279.4401245699, + -83288.2133424808 + ], + "DoF": [ + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ] + }, + "sens_LIN_KA": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ], + "Max. error": [ + 0.02620287155587423, + 0.02444702509718229, + 0.022101310475639097, + 0.018825154939735286, + 0.014284484542565035, + 0.008107998021187158 + ], + "Max. EOC": [ + 0.0, + 0.10006600686627976, + 0.1455270022361719, + 0.23147017635386452, + 0.39821276161359476, + 0.8170313355623577 + ], + "$L^1$ error": [ + 0.43458147213190695, + 0.4073458791531312, + 0.3471827116773575, + 0.2766186166192918, + 0.18812996499615192, + 0.09416304629977275 + ], + "$L^1$ EOC": [ + 0.0, + 0.0933723547503428, + 0.23055920440230945, + 0.3277968549733272, + 0.5561685971179356, + 0.9984967576391011 + ], + "Sim. time": [ + 0.0, + 0.0, + 1.0, + 4.0, + 12.0, + 46.0 + ], + "Min. value": [ + -2773.4619878355434, + -5272.691466691515, + -10864.506853547624, + -21902.07515411579, + -43279.4401245699, + -83288.2133424808 + ], + "DoF": [ + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0 + ] + } + } +} \ No newline at end of file diff --git a/test/data/convergence_radLRM_dynLin_1comp_sensbenchmark1.json b/test/data/convergence_radLRM_dynLin_1comp_sensbenchmark1.json new file mode 100644 index 000000000..7583b16be --- /dev/null +++ b/test/data/convergence_radLRM_dynLin_1comp_sensbenchmark1.json @@ -0,0 +1,497 @@ +{ + "meta": { + "source": "CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "info": "CADET-Reference convergence data for the CADET-Database model configuration: radLRM_dynLin_1comp_sensbenchmark1 whose source is CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "name": "convergence_radLRM_dynLin_1comp_sensbenchmark1" + }, + "convergence": { + "outlet": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ], + "Max. error": [ + 0.4975717832314417, + 0.46200718321881296, + 0.42627563414302816, + 0.38504383808608067, + 0.3348173076934562, + 0.27324330708331, + 0.20139633977766813, + 0.1268977019952624, + 0.07254663078831636, + 0.032836807177456295 + ], + "Max. EOC": [ + 0.0, + 0.10698939145810153, + 0.1161286892158675, + 0.14676388425177833, + 0.2016486021808357, + 0.29318794851928676, + 0.44014869473625595, + 0.6663715202300713, + 0.806685424192692, + 1.1435947578714027 + ], + "$L^1$ error": [ + 21.389827894685073, + 15.154128456030893, + 10.48388300515408, + 7.134399286190311, + 4.760660744931694, + 3.0890785696029543, + 1.921313693552122, + 1.1160711191904673, + 0.5729687611511807, + 0.21932704576179402 + ], + "$L^1$ EOC": [ + 0.0, + 0.4972139921829293, + 0.5315377231364076, + 0.5553092935870628, + 0.5836301374582112, + 0.6239852571000174, + 0.685083478308365, + 0.7836641248719365, + 0.9619005736668261, + 1.3853727586186078 + ], + "Sim. time": [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 8.0, + 29.0, + 85.0, + 254.0 + ], + "Min. value": [ + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + 0.0, + -1.659417039264687e-13, + 0.0, + -8.663748507849175e-14 + ], + "DoF": [ + 17.0, + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0, + 2049.0, + 4097.0, + 8193.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ] + }, + "sens_COL_DISPERSION": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ], + "Max. error": [ + 902824.4700847311, + 903452.6949385885, + 904128.3486379011, + 903863.6746823641, + 900246.6286420951, + 886948.1896508607, + 849077.2606369036, + 758270.9989525096, + 579696.2562958247, + 307818.2710010075 + ], + "Max. EOC": [ + 0.0, + -0.001003541356020726, + -0.0010785267860800307, + 0.00042239545408195416, + 0.005784902804632006, + 0.021470458494876193, + 0.06295399730795206, + 0.1631822895863009, + 0.3874163775950881, + 0.9132183011845146 + ], + "$L^1$ error": [ + 5575738.809239914, + 5690394.741021582, + 5812634.722616133, + 5915638.181960925, + 5985986.252217818, + 5967015.3132825885, + 5553843.756067432, + 4486054.380701694, + 2857270.321357435, + 1514244.232840605 + ], + "$L^1$ EOC": [ + 0.0, + -0.029365754374545725, + -0.030663514291099675, + -0.025341565984419452, + -0.017055149845648314, + 0.004579486140573843, + 0.10352288831805784, + 0.30803948187868235, + 0.6508095755301118, + 0.9160396168464346 + ], + "Sim. time": [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 8.0, + 29.0, + 85.0, + 254.0 + ], + "Min. value": [ + -2687.815565903726, + -5248.209132317, + -10870.904911600479, + -21961.713845787115, + -43226.91876410747, + -84117.2658391678, + -154112.52612320668, + -287641.99575117085, + -427657.44807998, + -593729.7725772408 + ], + "DoF": [ + 17.0, + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0, + 2049.0, + 4097.0, + 8193.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ] + }, + "sens_TOTAL_POROSITY": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ], + "Max. error": [ + 5.4583351342279265, + 5.300381653310662, + 5.087265666139088, + 4.789999213343939, + 4.379669980505082, + 3.8259329232861727, + 3.1101368274746837, + 2.253844705571677, + 1.3521746958415175, + 0.5610204215731827 + ], + "Max. EOC": [ + 0.0, + 0.04236473232167658, + 0.0592058081990827, + 0.08686501726155411, + 0.12920325592128273, + 0.19501058270438976, + 0.29883352867256824, + 0.4645899373833007, + 0.7371065599594366, + 1.2691563622809852 + ], + "$L^1$ error": [ + 45.477610876117765, + 44.78385284947445, + 41.691002140811634, + 37.55009412817108, + 34.277528237195384, + 29.86727693148737, + 24.18144416430024, + 17.4134354331351, + 10.349692499804183, + 4.241139455724532 + ], + "$L^1$ EOC": [ + 0.0, + 0.022177813949935676, + 0.10324260051726024, + 0.15091952691533228, + 0.13155344416897838, + 0.1986973702199461, + 0.30466530201199293, + 0.473699552689535, + 0.7506129507273466, + 1.287064077340627 + ], + "Sim. time": [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 8.0, + 29.0, + 85.0, + 254.0 + ], + "Min. value": [ + -2687.815565903726, + -5248.209132317, + -10870.904911600479, + -21961.713845787115, + -43226.91876410747, + -84117.2658391678, + -154112.52612320668, + -287641.99575117085, + -427657.44807998, + -593729.7725772408 + ], + "DoF": [ + 17.0, + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0, + 2049.0, + 4097.0, + 8193.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ] + }, + "sens_LIN_KA": { + "$N_d$": [ + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0", + "0" + ], + "$N_e^z$": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ], + "Max. error": [ + 0.05888511851361439, + 0.05718109902512128, + 0.05488198036473526, + 0.05167503724517536, + 0.04724836044517446, + 0.041274584295878394, + 0.033552497451277946, + 0.024314724056906684, + 0.01458740902815614, + 0.006052349883343695 + ], + "Max. EOC": [ + 0.0, + 0.04236473232167658, + 0.0592058081990827, + 0.08686501726155496, + 0.1292032559212829, + 0.19501058270438976, + 0.2988335286725674, + 0.464589937383298, + 0.7371065599594377, + 1.269156362280968 + ], + "$L^1$ error": [ + 0.4906174575766654, + 0.48313311984106416, + 0.4497671069367848, + 0.4050945368065154, + 0.3697897367906081, + 0.32221146165266623, + 0.2608720737054296, + 0.18785805268338893, + 0.11165361862975429, + 0.045753877939276 + ], + "$L^1$ EOC": [ + 0.0, + 0.022177813949933727, + 0.10324260051726024, + 0.15091952691532587, + 0.13155344416897558, + 0.1986973702199007, + 0.304665302012005, + 0.47369955268954034, + 0.7506129507273264, + 1.2870640773405955 + ], + "Sim. time": [ + 0.0, + 0.0, + 0.0, + 0.0, + 1.0, + 2.0, + 8.0, + 29.0, + 85.0, + 254.0 + ], + "Min. value": [ + -2687.815565903726, + -5248.209132317, + -10870.904911600479, + -21961.713845787115, + -43226.91876410747, + -84117.2658391678, + -154112.52612320668, + -287641.99575117085, + -427657.44807998, + -593729.7725772408 + ], + "DoF": [ + 17.0, + 33.0, + 65.0, + 129.0, + 257.0, + 513.0, + 1025.0, + 2049.0, + 4097.0, + 8193.0 + ], + "Axial DoF": [ + 8.0, + 16.0, + 32.0, + 64.0, + 128.0, + 256.0, + 512.0, + 1024.0, + 2048.0, + 4096.0 + ] + } + } +} \ No newline at end of file diff --git a/test/data/model_radGRM_dynLin_1comp_sensbenchmark1.json b/test/data/model_radGRM_dynLin_1comp_sensbenchmark1.json new file mode 100644 index 000000000..e9249d0cd --- /dev/null +++ b/test/data/model_radGRM_dynLin_1comp_sensbenchmark1.json @@ -0,0 +1,115 @@ +{ + "meta": { + "INFO": "CADET-Reference convergence data for the CADET-Database model configuration: radGRM_dynLin_1comp_sensbenchmark1 whose source is CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "NAME": "convergence_radGRM_dynLin_1comp_sensbenchmark1", + "SOURCE": "CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data" + }, + "model": { + "NUNITS": 3, + "connections": { + "NSWITCHES": 1, + "switch_000": { + "CONNECTIONS": [ + 0.0, + 1.0, + -1.0, + -1.0, + 6e-05, + 1.0, + 2.0, + -1.0, + -1.0, + 6e-05 + ], + "SECTION": 0 + } + }, + "unit_000": { + "INLET_TYPE": "PIECEWISE_CUBIC_POLY", + "NCOMP": 1, + "UNIT_TYPE": "INLET", + "sec_000": { + "CONST_COEFF": [ + 1.0 + ], + "CUBE_COEFF": [ + 0.0 + ], + "LIN_COEFF": [ + 0.0 + ], + "QUAD_COEFF": [ + 0.0 + ] + }, + "sec_001": { + "CONST_COEFF": [ + 0.0 + ], + "CUBE_COEFF": [ + 0.0 + ], + "LIN_COEFF": [ + 0.0 + ], + "QUAD_COEFF": [ + 0.0 + ] + } + }, + "unit_001": { + "ADSORPTION_MODEL": "LINEAR", + "ADSORPTION_MODEL_MULTIPLEX": 1, + "COL_DISPERSION": 5.75e-08, + "COL_POROSITY": 0.37, + "COL_RADIUS_INNER": 0.01, + "COL_RADIUS_OUTER": 0.2, + "FILM_DIFFUSION": 6.9e-06, + "FILM_DIFFUSION_MULTIPLEX": 0, + "INIT_C": [ + 0.0 + ], + "INIT_CP": [ + 0.0 + ], + "INIT_Q": [ + 0.0 + ], + "NCOMP": 1, + "PAR_CORERADIUS": 0.0, + "PAR_DIFFUSION": 6.07e-11, + "PAR_POROSITY": 0.75, + "PAR_RADIUS": 4.5e-05, + "PAR_SURFDIFFUSION": 0.0, + "PAR_TYPE_VOLFRAC": 1, + "UNIT_TYPE": "RADIAL_GENERAL_RATE_MODEL", + "VELOCITY_COEFF": 0.000575, + "adsorption": { + "IS_KINETIC": true, + "LIN_KA": [ + 12.3 + ], + "LIN_KD": [ + 45.0 + ] + } + }, + "unit_002": { + "NCOMP": 1, + "UNIT_TYPE": "OUTLET" + } + }, + "solver": { + "sections": { + "NSEC": 2, + "SECTION_CONTINUITY": [ + 0 + ], + "SECTION_TIMES": [ + 0.0, + 10.0, + 1500.0 + ] + } + } +} \ No newline at end of file diff --git a/test/data/model_radLRMP_dynLin_1comp_sensbenchmark1.json b/test/data/model_radLRMP_dynLin_1comp_sensbenchmark1.json new file mode 100644 index 000000000..b26e08d74 --- /dev/null +++ b/test/data/model_radLRMP_dynLin_1comp_sensbenchmark1.json @@ -0,0 +1,115 @@ +{ + "meta": { + "INFO": "CADET-Reference convergence data for the CADET-Database model configuration: radLRMP_dynLin_1comp_sensbenchmark1 whose source is CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "NAME": "convergence_radLRMP_dynLin_1comp_sensbenchmark1", + "SOURCE": "CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data" + }, + "model": { + "NUNITS": 3, + "connections": { + "NSWITCHES": 1, + "switch_000": { + "CONNECTIONS": [ + 0.0, + 1.0, + -1.0, + -1.0, + 6e-05, + 1.0, + 2.0, + -1.0, + -1.0, + 6e-05 + ], + "SECTION": 0 + } + }, + "unit_000": { + "INLET_TYPE": "PIECEWISE_CUBIC_POLY", + "NCOMP": 1, + "UNIT_TYPE": "INLET", + "sec_000": { + "CONST_COEFF": [ + 1.0, + 1.0 + ], + "CUBE_COEFF": [ + 0.0 + ], + "LIN_COEFF": [ + 0.0 + ], + "QUAD_COEFF": [ + 0.0 + ] + }, + "sec_001": { + "CONST_COEFF": [ + 0.0 + ], + "CUBE_COEFF": [ + 0.0 + ], + "LIN_COEFF": [ + 0.0 + ], + "QUAD_COEFF": [ + 0.0 + ] + } + }, + "unit_001": { + "ADSORPTION_MODEL": "LINEAR", + "ADSORPTION_MODEL_MULTIPLEX": 1, + "COL_DISPERSION": 5.75e-08, + "COL_POROSITY": 0.8, + "COL_RADIUS_INNER": 0.01, + "COL_RADIUS_OUTER": 0.2, + "FILM_DIFFUSION": [ + 0.003333333333333333 + ], + "FILM_DIFFUSION_MULTIPLEX": 0, + "INIT_C": [ + 0.0 + ], + "INIT_CP": [ + 0.0 + ], + "INIT_Q": [ + 0.0 + ], + "NCOMP": 1, + "PAR_POROSITY": 0.2, + "PAR_RADIUS": 0.0001, + "PAR_TYPE_VOLFRAC": 1, + "UNIT_TYPE": "RADIAL_LUMPED_RATE_MODEL_WITH_PORES", + "VELOCITY_COEFF": 0.000575, + "adsorption": { + "IS_KINETIC": 1, + "LIN_KA": [ + 12.3 + ], + "LIN_KD": [ + 45.0 + ] + } + }, + "unit_002": { + "NCOMP": 1, + "UNIT_TYPE": "OUTLET" + } + }, + "solver": { + "sections": { + "NSEC": 2, + "SECTION_CONTINUITY": [ + 0 + ], + "SECTION_TIMES": [ + 0.0, + 60.0, + 130.0 + ] + } + } +} \ No newline at end of file diff --git a/test/data/model_radLRM_dynLin_1comp_sensbenchmark1.json b/test/data/model_radLRM_dynLin_1comp_sensbenchmark1.json new file mode 100644 index 000000000..b6e6748f1 --- /dev/null +++ b/test/data/model_radLRM_dynLin_1comp_sensbenchmark1.json @@ -0,0 +1,103 @@ +{ + "meta": { + "INFO": "CADET-Reference convergence data for the CADET-Database model configuration: radLRM_dynLin_1comp_sensbenchmark1 whose source is CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data", + "NAME": "convergence_radLRM_dynLin_1comp_sensbenchmark1", + "SOURCE": "CADET-Reference convergence data https://jugit.fz-juelich.de/IBG-1/ModSim/cadet/cadet-reference_data" + }, + "model": { + "NUNITS": 3, + "connections": { + "NSWITCHES": 1, + "switch_000": { + "CONNECTIONS": [ + 0.0, + 1.0, + -1.0, + -1.0, + 6e-05, + 1.0, + 2.0, + -1.0, + -1.0, + 6e-05 + ], + "SECTION": 0 + } + }, + "unit_000": { + "INLET_TYPE": "PIECEWISE_CUBIC_POLY", + "NCOMP": 1, + "UNIT_TYPE": "INLET", + "sec_000": { + "CONST_COEFF": [ + 1.0 + ], + "CUBE_COEFF": [ + 0.0 + ], + "LIN_COEFF": [ + 0.0 + ], + "QUAD_COEFF": [ + 0.0 + ] + }, + "sec_001": { + "CONST_COEFF": [ + 0.0 + ], + "CUBE_COEFF": [ + 0.0 + ], + "LIN_COEFF": [ + 0.0 + ], + "QUAD_COEFF": [ + 0.0 + ] + } + }, + "unit_001": { + "ADSORPTION_MODEL": "LINEAR", + "COL_DISPERSION": 5.75e-08, + "COL_RADIUS_INNER": 0.01, + "COL_RADIUS_OUTER": 0.2, + "INIT_C": [ + 0.0 + ], + "INIT_Q": [ + 0.0 + ], + "NCOMP": 1, + "TOTAL_POROSITY": 0.8425, + "UNIT_TYPE": "RADIAL_LUMPED_RATE_MODEL_WITHOUT_PORES", + "VELOCITY_COEFF": 0.000575, + "adsorption": { + "IS_KINETIC": 1, + "LIN_KA": [ + 12.3 + ], + "LIN_KD": [ + 45.0 + ] + } + }, + "unit_002": { + "NCOMP": 1, + "UNIT_TYPE": "OUTLET" + } + }, + "solver": { + "sections": { + "NSEC": 2, + "SECTION_CONTINUITY": [ + 0 + ], + "SECTION_TIMES": [ + 0.0, + 60.0, + 130.0 + ] + } + } +} \ No newline at end of file diff --git a/test/data/ref_radGRM_dynLin_1comp_sensbenchmark1_FV_Z32parZ4.h5 b/test/data/ref_radGRM_dynLin_1comp_sensbenchmark1_FV_Z32parZ4.h5 new file mode 100644 index 000000000..f1e04f322 Binary files /dev/null and b/test/data/ref_radGRM_dynLin_1comp_sensbenchmark1_FV_Z32parZ4.h5 differ diff --git a/test/data/ref_radLRMP_dynLin_1comp_sensbenchmark1_FV_Z32.h5 b/test/data/ref_radLRMP_dynLin_1comp_sensbenchmark1_FV_Z32.h5 new file mode 100644 index 000000000..0eb0ef9d9 Binary files /dev/null and b/test/data/ref_radLRMP_dynLin_1comp_sensbenchmark1_FV_Z32.h5 differ diff --git a/test/data/ref_radLRM_dynLin_1comp_sensbenchmark1_FV_Z32.h5 b/test/data/ref_radLRM_dynLin_1comp_sensbenchmark1_FV_Z32.h5 new file mode 100644 index 000000000..725be2fef Binary files /dev/null and b/test/data/ref_radLRM_dynLin_1comp_sensbenchmark1_FV_Z32.h5 differ diff --git a/test/testRadialKernel.cpp b/test/testRadialKernel.cpp new file mode 100644 index 000000000..7a4287889 --- /dev/null +++ b/test/testRadialKernel.cpp @@ -0,0 +1,560 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-2022: The CADET Authors +// Please see the AUTHORS and CONTRIBUTORS 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 "TimeIntegrator.hpp" +#include "cadet/Logging.hpp" +#include "Logging.hpp" + +#include "model/parts/RadialConvectionDispersionKernel.hpp" +#include "linalg/BandMatrix.hpp" +#include "Memory.hpp" +#include "AutoDiff.hpp" +#include "model/paramdep/DummyParameterDependence.cpp" +#include "Dummies.hpp" + +#include "io/hdf5/HDF5Writer.hpp" + +#include +#include +#include + +//#define TEST_BREAKTHROUGH 1 +#define TEST_MANUFACTURED 1 +#define TEST_MANUFACTURED_TEXPT 1 + +// Uncomment the next line to enable logging output of CADET in unit tests +//#define CADETTEST_ENABLE_LOG + +#ifdef CADETTEST_ENABLE_LOG + #include "cadet/Logging.hpp" + #include + + class LogReceiver : public cadet::ILogReceiver + { + public: + LogReceiver() { } + + virtual void message(const char* file, const char* func, const unsigned int line, cadet::LogLevel lvl, const char* lvlStr, const char* message) + { + std::cout << '[' << lvlStr << ": " << func << "::" << line << "] " << message << std::flush; + } + }; +#endif + +class RadialFlowModel : public cadet::test::IDiffEqModel +{ +public: + RadialFlowModel(int nComp, int nCol) : _nComp(nComp), _nCol(nCol), + _params{0.0, nullptr, nullptr, nullptr, nullptr, nullptr, 0, 0, 0, 0, 0, nullptr, _dummyModel}, + _stencilMemory(sizeof(cadet::active) * 5) + { + const int nPureDof = _nCol * _nComp; + _jacDisc.resize(nPureDof, 2 * nComp, 2 * nComp); + _jac.resize(nPureDof, 2 * nComp, 2 * nComp); + + _radDispersion = std::vector(_nComp, 1e-5); + + const double colLen = 0.1; + +// equidistantCells(0.1, 0.4, _nCol); + equidistantCells(1.0, 4.0, _nCol); + + _params.u = fromVolumetricFlowRate(8e-1, colLen); + _params.d_rad = _radDispersion.data(); + _params.cellBounds = _cellBounds.data(); + _params.cellCenters = _cellCenters.data(); + _params.cellSizes = _cellSizes.data(); + _params.stencilMemory = &_stencilMemory; + _params.offsetToBulk = _nComp; + _params.nCol = _nCol; + _params.nComp = _nComp; + _params.offsetToInlet = 0; + _params.strideCell = _nComp; + _params.parDep = new cadet::model::ConstantOneParameterParameterDependence(); + } + + virtual ~RadialFlowModel() CADET_NOEXCEPT + { + if (_params.parDep) + delete _params.parDep; + } + + int numPureDofs() const CADET_NOEXCEPT { return _nComp * _nCol; } + virtual int numDofs() const CADET_NOEXCEPT { return _nComp * (_nCol + 1); } + + virtual void notifyDiscontinuousSectionTransition(double t, int secIdx, double* vecStateY, double* vecStateYdot) + { + if (t == 0.0) + { + // Consistent init + std::fill_n(vecStateY, numDofs(), 0.0); + std::fill_n(vecStateYdot, numDofs(), 0.0); + + for (int i = 0; i < _nComp; ++i) + vecStateY[i] = inlet(0.0, 0, i); + +#if defined(TEST_MANUFACTURED) && !defined(TEST_MANUFACTURED_TEXPT) + const double pi = 3.14159265358979323846; + const double rOut = static_cast(_cellBounds.back()); + const double fourPiOverRout = 4.0 * pi / rOut; + const double tMinusFiveSq = (t - 5.0) * (t - 5.0); + const double expFactor = std::exp(-0.125 * tMinusFiveSq); + + int idx = _nComp; + for (int i = 0; i < _nCol; ++i) + { + const double denom = static_cast(_cellCenters[i]) * static_cast(_cellSizes[i]); + const double left = static_cast(_cellBounds[i]); + const double right = static_cast(_cellBounds[i+1]); + + const double val = (right * right - left*left) / denom + (std::sin(fourPiOverRout * right) * right - std::sin(fourPiOverRout * left) * left + (std::cos(fourPiOverRout * right) - std::cos(fourPiOverRout * left)) / fourPiOverRout) / (fourPiOverRout * denom) * expFactor; + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + vecStateY[idx] = val; + } + } +#elif defined(TEST_MANUFACTURED) && defined(TEST_MANUFACTURED_TEXPT) + int idx = _nComp; + for (int i = 0; i < _nCol; ++i) + { + const double denom = static_cast(_cellCenters[i]) * static_cast(_cellSizes[i]); + const double left = static_cast(_cellBounds[i]); + const double right = static_cast(_cellBounds[i+1]); + + const double val = (right * right - left*left) / denom; + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + vecStateY[idx] = val; + } + } +#endif + + std::vector res(numDofs(), 0.0); + residual(t, secIdx, vecStateY, nullptr, res.data()); + + double const* const resBulk = res.data() + _nComp; + double* const yDot = vecStateYdot + _nComp; + for (int i = 0; i < numPureDofs(); ++i) + yDot[i] = -resBulk[i]; + + } + + } + + virtual int residual(double time, int secIdx, double const* vecStateY, double const* vecStateYdot, double* res) + { + return residualWithJacobian(time, secIdx, vecStateY, vecStateYdot, res); + } + + virtual int residualWithJacobian(double time, int secIdx, double const* vecStateY, double const* vecStateYdot, double* res) + { + _jac.setAll(0.0); + + // Inlet block: c_i - val = 0 + for (int i = 0; i < _nComp; ++i) + res[i] = vecStateY[i] - inlet(time, secIdx, i); + + const int ret = cadet::model::parts::convdisp::residualKernelRadial( + cadet::SimulationTime{time, static_cast(secIdx)}, + vecStateY, vecStateYdot, res, _jac.row(0), _params + ); + + +#if defined(TEST_MANUFACTURED) && !defined(TEST_MANUFACTURED_TEXPT) + const double pi = 3.14159265358979323846; + const double rOut = static_cast(_cellBounds.back()); + const double fourPiOverRout = 4.0 * pi / rOut; + const double fourPiOverRoutSq = fourPiOverRout * fourPiOverRout; + const double tMinusFive = (time - 5.0); + const double tMinusFiveSq = tMinusFive * tMinusFive; + const double expFactor = std::exp(-0.125 * tMinusFiveSq); + + int idx = _nComp; + for (int i = 0; i < _nCol; ++i) + { + const double denom = static_cast(_cellCenters[i]) * static_cast(_cellSizes[i]); + const double left = static_cast(_cellBounds[i]); + const double right = static_cast(_cellBounds[i+1]); + + const double sinLeft = std::sin(fourPiOverRout * left); + const double cosLeft = std::cos(fourPiOverRout * left); + const double sinRight = std::sin(fourPiOverRout * right); + const double cosRight = std::cos(fourPiOverRout * right); + + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + const double d_rad = static_cast(_params.d_rad[comp]); + const double u = static_cast(_params.u); + const double leftTerm = fourPiOverRout * left * sinLeft * d_rad + u * cosLeft; + const double rightTerm = fourPiOverRout * right * sinRight * d_rad + u * cosRight; + + const double val = (tMinusFive * (left / (4.0 * fourPiOverRout) * sinLeft - right / (4.0 * fourPiOverRout) * sinRight + fourPiOverRoutSq / 4.0 * (-cosRight + cosLeft)) - leftTerm + rightTerm) * expFactor / denom; + res[idx] -= val; + } + } +#elif defined(TEST_MANUFACTURED) && defined(TEST_MANUFACTURED_TEXPT) + const double pi = 3.14159265358979323846; + const double rOut = static_cast(_cellBounds.back()); + const double fourPiOverRout = 4.0 * pi / rOut; + const double tMinusFive = (time - 5.0); + const double tMinusFiveSq = tMinusFive * tMinusFive; + const double expFactor = std::exp(-0.125 * tMinusFiveSq); + const double tPoly = 4.0 + time * (5.0 - time); + + int idx = _nComp; + for (int i = 0; i < _nCol; ++i) + { + const double denom = static_cast(_cellCenters[i]) * static_cast(_cellSizes[i]); + const double left = static_cast(_cellBounds[i]); + const double right = static_cast(_cellBounds[i+1]); + + const double sinLeft = std::sin(fourPiOverRout * left); + const double cosLeft = std::cos(fourPiOverRout * left); + const double sinRight = std::sin(fourPiOverRout * right); + const double cosRight = std::cos(fourPiOverRout * right); + + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + const double right1 = tPoly / (4.0 * fourPiOverRout) * (right * sinRight + cosRight / fourPiOverRout); + const double left1 = tPoly / (4.0 * fourPiOverRout) * (left * sinLeft + cosLeft / fourPiOverRout); + + const double d_rad = static_cast(_params.d_rad[comp]); + const double v = static_cast(_params.u); + const double left2 = d_rad * fourPiOverRout * left * sinLeft + v * cosLeft; + const double right2 = d_rad * fourPiOverRout * right * sinRight + v * cosRight; + + const double val = (right1 - left1 + time * (right2 - left2)) * expFactor / denom; + res[idx] -= val; + } + } +#endif + + return ret; + } + + virtual double residualNorm(double time, int secIdx, double const* vecStateY, double const* vecStateYdot) + { + return 0.0; + } + + virtual int linearSolve(double t, double alpha, double tol, double* rhs, double const* weight, + double const* vecStateY, double const* vecStateYdot) + { + _jacDisc.copyOver(_jac); + + // Add time derivative + cadet::linalg::FactorizableBandMatrix::RowIterator jac = _jacDisc.row(0); + for (int i = 0; i < _nCol; ++i) + { + for (int j = 0; j < _nComp; ++j, ++jac) + { + // Add time derivative to main diagonal + jac[0] += alpha; + } + } + + if (!_jacDisc.factorize()) + return 1; + + // Inlet-Bulk coupling Jacobian + // A * inlet + J * x = rhs + // J * x = rhs - A * inlet + const int idxInletCell = (_params.u >= 0.0) ? 0 : _nCol - 1; + const double factor = static_cast(_params.u) / (static_cast(_params.cellCenters[idxInletCell]) * static_cast(_params.cellSizes[idxInletCell])); + double* const rhsBulkInlet = rhs + _nComp * (idxInletCell + 1); + for (int i = 0; i < _nComp; ++i) + { + rhsBulkInlet[i] -= -factor * rhs[i]; + } + + if (!_jacDisc.solve(rhs + _nComp)) + return 1; + return 0; + } + + virtual void applyInitialCondition(double* vecStateY, double* vecStateYdot) const + { + // Consistent init + std::fill_n(vecStateY, numDofs(), 0.0); + std::fill_n(vecStateYdot, numDofs(), 0.0); + } + + virtual void saveSolution(double t, double const* vecStateY, double const* vecStateYdot) + { + const int nTimeSaved = _solTimes.size(); + _solTimes.push_back(t); + + const int nDof = numPureDofs(); + _solution.resize(_solution.size() + nDof); + std::copy(vecStateY + _nComp, vecStateY + nDof + _nComp, _solution.data() + nTimeSaved * nDof); + + _solutionInlet.resize(_solutionInlet.size() + _nComp); + std::copy(vecStateY, vecStateY + _nComp, _solutionInlet.data() + nTimeSaved * _nComp); + + _solutionOutlet.resize(_solutionOutlet.size() + _nComp); + if (_params.u > 0.0) + { + // Flow from inner to outer + std::copy(vecStateY + nDof, vecStateY + nDof + _nComp, _solutionOutlet.data() + nTimeSaved * _nComp); + } + else + { + // Flow from outer to inner + std::copy(vecStateY + _nComp, vecStateY + 2 * _nComp, _solutionOutlet.data() + nTimeSaved * _nComp); + } + } + + const std::vector& solutionTimes() const CADET_NOEXCEPT { return _solTimes; } + const std::vector& solution() const CADET_NOEXCEPT { return _solution; } + const std::vector& solutionInlet() const CADET_NOEXCEPT { return _solutionInlet; } + const std::vector& solutionOutlet() const CADET_NOEXCEPT { return _solutionOutlet; } + int numComp() const CADET_NOEXCEPT { return _nComp; } + int numCol() const CADET_NOEXCEPT { return _nCol; } + + const std::vector& referenceSolution() CADET_NOEXCEPT + { + if (!_trueSolution.empty()) + return _trueSolution; + + _trueSolution = std::vector(_solTimes.size() * numPureDofs(), 0.0); + + +#if defined(TEST_MANUFACTURED) && !defined(TEST_MANUFACTURED_TEXPT) + const double pi = 3.14159265358979323846; + const double fourPi = 4.0 * pi; + const double rOut = static_cast(_cellBounds.back()); + + int idx = 0; + for (int i = 0; i < _solTimes.size(); ++i) + { + const double t = _solTimes[i]; + const double tMinusFiveSq = (t - 5.0) * (t - 5.0); + const double expFactor = std::exp(-0.125 * tMinusFiveSq); + + for (int j = 0; j < _nCol; ++j) + { + const double r = static_cast(_cellCenters[j]); + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + _trueSolution[idx] = std::cos(fourPi * r / rOut) * expFactor + 2.0; + } + } + } +#elif defined(TEST_MANUFACTURED) && defined(TEST_MANUFACTURED_TEXPT) + const double pi = 3.14159265358979323846; + const double fourPi = 4.0 * pi; + const double rOut = static_cast(_cellBounds.back()); + + int idx = 0; + for (int i = 0; i < _solTimes.size(); ++i) + { + const double t = _solTimes[i]; + const double tMinusFiveSq = (t - 5.0) * (t - 5.0); + const double expFactor = t * std::exp(-0.125 * tMinusFiveSq); + + for (int j = 0; j < _nCol; ++j) + { + const double r = static_cast(_cellCenters[j]); + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + _trueSolution[idx] = std::cos(fourPi * r / rOut) * expFactor + 2.0; + } + } + } +#elif 0 + const double pi = 3.14159265358979323846; + const double rOut = static_cast(_cellBounds.back()); + const double fourPiOverRout = 4.0 * pi / rOut; + + int idx = 0; + for (int i = 0; i < _solTimes.size(); ++i) + { + const double t = _solTimes[i]; + const double tMinusFiveSq = (t - 5.0) * (t - 5.0); + const double expFactor = std::exp(-0.125 * tMinusFiveSq); + + for (int j = 0; j < _nCol; ++j) + { + const double denom = static_cast(_cellCenters[j]) * static_cast(_cellSizes[j]); + const double left = static_cast(_cellBounds[j]); + const double right = static_cast(_cellBounds[j+1]); + + const double val = (right * right - left*left) / denom + (std::sin(fourPiOverRout * right) * right - std::sin(fourPiOverRout * left) * left + (std::cos(fourPiOverRout * right) - std::cos(fourPiOverRout * left)) / fourPiOverRout) / (fourPiOverRout * denom) * expFactor; + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + _trueSolution[idx] = val; + } + } + } +#endif + + return _trueSolution; + } + + const std::vector& referenceOutlet() CADET_NOEXCEPT + { + if (!_trueOutlet.empty()) + return _trueOutlet; + + _trueOutlet = std::vector(_solTimes.size() * _nComp, 0.0); + + int idx = 0; + for (int i = 0; i < _solTimes.size(); ++i) + { + const double t = _solTimes[i]; + const double tMinusFiveSq = (t - 5.0) * (t - 5.0); + const double expFactor = std::exp(-0.125 * tMinusFiveSq); + + for (int comp = 0; comp < _nComp; ++comp, ++idx) + { + _trueOutlet[idx] = expFactor + 2.0; + } + } + + return _trueOutlet; + } + + std::vector coordinates() const CADET_NOEXCEPT + { + std::vector coords(_cellCenters.size(), 0.0); + for (int i = 0; i < _cellCenters.size(); ++i) + coords[i] = static_cast(_cellCenters[i]); + + return coords; + } + +protected: + int _nComp; + int _nCol; + + DummyModel _dummyModel; + cadet::model::parts::convdisp::RadialFlowParameters _params; + cadet::linalg::BandMatrix _jac; + cadet::linalg::FactorizableBandMatrix _jacDisc; + + std::vector _radDispersion; + std::vector _cellCenters; + std::vector _cellSizes; + std::vector _cellBounds; + cadet::ArrayPool _stencilMemory; + + std::vector _solTimes; + std::vector _solution; + std::vector _solutionInlet; + std::vector _solutionOutlet; + + std::vector _trueSolution; + std::vector _trueOutlet; + + double inlet(double t, int secIdx, int comp) const CADET_NOEXCEPT + { +/* + if (t <= 10.0) + return 0.1 * t; + if (t <= 50.0) + return 1.0; + if (t <= 60.0) + return 1.0 - (t-50.0) * 0.1; + return 0.0; +*/ + +#ifdef TEST_BREAKTHROUGH + return 1.0; + +#elif defined(TEST_MANUFACTURED) + const double pi = 3.14159265358979323846; + const double fourPiRinOverRout = 4.0 * pi * static_cast(_cellBounds[0]) / static_cast(_cellBounds.back()); + const double tMinusFiveSq = (t - 5.0) * (t - 5.0); + const double expFactor = t * std::exp(-0.125 * tMinusFiveSq); + + return 2.0 + (fourPiRinOverRout * static_cast(_params.d_rad[comp]) / static_cast(_params.u) * std::sin(fourPiRinOverRout) + std::cos(fourPiRinOverRout)) * expFactor; +#endif + + } + + void equidistantCells(double inner, double outer, int nCol) + { + const double dr = (outer - inner) / nCol; + std::vector centers(nCol, 0.0); + _cellSizes = std::vector(nCol, dr); + std::vector bounds(nCol + 1, 0.0); + + for (int i = 0; i < nCol; ++i) + { + centers[i] = (i + 0.5) * dr + inner; + bounds[i] = i * dr + inner; + } + bounds[nCol] = outer; + + _cellCenters = std::move(centers); + _cellBounds = std::move(bounds); + } + + double fromVolumetricFlowRate(double volRate, double len) + { + const double pi = 3.14159265358979323846; + return volRate / (pi * 2.0 * len); + } +}; + + +int main(int argc, char* argv[]) +{ +#ifdef CADETTEST_ENABLE_LOG + // Set LogLevel in CADET library + const cadet::LogLevel logLevel = cadet::LogLevel::Trace; + LogReceiver lr; + cadet::setLogReceiver(&lr); + cadet::setLogLevel(logLevel); +#endif + +#if 1 + + const double tEnd = 10.0; + std::vector secTimes = {0.0, tEnd}; + std::vector solTimes(101, 0.0); + + for (int i = 0; i < solTimes.size(); ++i) + solTimes[i] = i * 0.1; + +#elif 0 + const double tEnd = 10.0; + std::vector secTimes = {0.0, tEnd}; + std::vector solTimes(101, 0.0); + + for (int i = 0; i < 101; ++i) + solTimes[i] = i * 0.1; +#endif + + RadialFlowModel model(1, 1250); + + cadet::test::TimeIntegrator sim; + sim.configureTimeIntegrator(1e-6, 1e-8, 1e-4, 100000, 0.0); + sim.setSectionTimes(secTimes); + sim.setSolutionTimes(solTimes); + sim.initializeModel(model); + + sim.integrate(); + + cadet::io::HDF5Writer writer; + writer.openFile("radial.h5", "co"); + writer.vector("SOLUTION_TIMES", model.solutionTimes()); + const std::vector dims = {model.solutionTimes().size(), static_cast(model.numCol()), static_cast(model.numComp())}; + writer.template tensor("SOLUTION", 3, dims.data(), model.solution()); + writer.template matrix("SOLUTION_INLET", model.solutionTimes().size(), model.numComp(), model.solutionInlet()); + writer.template matrix("SOLUTION_OUTLET", model.solutionTimes().size(), model.numComp(), model.solutionOutlet()); + writer.template tensor("REF", 3, dims.data(), model.referenceSolution()); + writer.template matrix("REF_OUTLET", model.solutionTimes().size(), model.numComp(), model.referenceOutlet()); + writer.template vector("COORDS", model.coordinates()); + writer.closeFile(); + return 0; +} diff --git a/test/testRunner.cpp b/test/testRunner.cpp index 6f7ff0cde..ba0c547b7 100644 --- a/test/testRunner.cpp +++ b/test/testRunner.cpp @@ -47,10 +47,10 @@ int main(int argc, char* argv[]) { #ifdef CADETTEST_ENABLE_LOG // Set LogLevel in CADET library - cadet::LogLevel logLevel = cadet::LogLevel::Trace; + const cadet::LogLevel logLevel = cadet::LogLevel::Trace; LogReceiver lr; - cadetSetLogReceiver(&lr); - cadetSetLogLevel(static_cast::type>(logLevel)); + cadet::setLogReceiver(&lr); + cadet::setLogLevel(logLevel); #endif #ifdef CADET_PARALLELIZE