diff --git a/src/libcadet/CMakeLists.txt b/src/libcadet/CMakeLists.txt index bda1ed65e..f6c706883 100644 --- a/src/libcadet/CMakeLists.txt +++ b/src/libcadet/CMakeLists.txt @@ -216,6 +216,14 @@ if (ENABLE_DG) ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithoutPoresDG.cpp ${CMAKE_SOURCE_DIR}/src/libcadet/model/GeneralRateModelDG.cpp ) + if (ENABLE_2D_MODELS) + list (APPEND LIBCADET_SOURCES + ${CMAKE_SOURCE_DIR}/src/libcadet/model/parts/TwoDimensionalConvectionDispersionOperatorDG.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithPoresDG2D.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithPoresDG2D-LinearSolver.cpp + ${CMAKE_SOURCE_DIR}/src/libcadet/model/LumpedRateModelWithPoresDG2D-InitialConditions.cpp + ) + endif() endif() # Preprocess binding and reaction models diff --git a/src/libcadet/ModelBuilderImpl.cpp b/src/libcadet/ModelBuilderImpl.cpp index 6eb76911a..eef3c2005 100644 --- a/src/libcadet/ModelBuilderImpl.cpp +++ b/src/libcadet/ModelBuilderImpl.cpp @@ -40,6 +40,9 @@ namespace cadet #ifdef ENABLE_2D_MODELS void registerGeneralRateModel2D(std::unordered_map>& models); void registerMultiChannelTransportModel(std::unordered_map>& models); + #ifdef ENABLE_DG + void registerLumpedRateModelWithPoresDG2D(std::unordered_map>& models); + #endif #endif namespace inlet @@ -67,7 +70,11 @@ namespace cadet #ifdef ENABLE_2D_MODELS model::registerGeneralRateModel2D(_modelCreators); model::registerMultiChannelTransportModel(_modelCreators); + #ifdef ENABLE_DG + model::registerLumpedRateModelWithPoresDG2D(_modelCreators); + #endif #endif + // Register all available inlet profiles model::inlet::registerPiecewiseCubicPoly(_inletCreators); diff --git a/src/libcadet/model/LumpedRateModelWithPoresDG2D-InitialConditions.cpp b/src/libcadet/model/LumpedRateModelWithPoresDG2D-InitialConditions.cpp new file mode 100644 index 000000000..cadcd966f --- /dev/null +++ b/src/libcadet/model/LumpedRateModelWithPoresDG2D-InitialConditions.cpp @@ -0,0 +1,1418 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-present: The CADET-Core Authors +// Please see the AUTHORS.md file. +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the GNU Public License v3.0 (or, at +// your option, any later version) which accompanies this distribution, and +// is available at http://www.gnu.org/licenses/gpl.html +// ============================================================================= + +#include "model/LumpedRateModelWithPoresDG2D.hpp" +#include "model/BindingModel.hpp" +#include "linalg/DenseMatrix.hpp" +#include "linalg/Subset.hpp" +#include "ParamReaderHelper.hpp" +#include "AdUtils.hpp" +#include "model/parts/BindingCellKernel.hpp" +#include "SimulationTypes.hpp" +#include "SensParamUtil.hpp" + +#include +#include + +#include "LoggingUtils.hpp" +#include "Logging.hpp" + +#include "ParallelSupport.hpp" + +namespace cadet +{ + +namespace model +{ + +int LumpedRateModelWithPoresDG2D::multiplexInitialConditions(const cadet::ParameterId& pId, unsigned int adDirection, double adValue) +{ + if (pId.name == hashString("INIT_C") && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep)) + { + if ((pId.reaction == ReactionIndep) && _singleRadiusInitC) + { + _sensParams.insert(&_initC[pId.component]); + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + _initC[r * _disc.nComp + pId.component].setADValue(adDirection, adValue); + + return 1; + } + else if ((pId.reaction != ReactionIndep) && !_singleRadiusInitC) + { + _sensParams.insert(&_initC[pId.reaction * _disc.nComp + pId.component]); + _initC[pId.reaction * _disc.nComp + pId.component].setADValue(adDirection, adValue); + return 1; + } + + return -1; + } + else if (pId.name == hashString("INIT_C")) + return -1; + + if (_singleBinding) + { + if (_singleRadiusInitCp) + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + _sensParams.insert(&_initCp[pId.component]); + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initCp[r * _disc.nComp * _disc.nParType + t * _disc.nComp + pId.component].setADValue(adDirection, adValue); + } + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + _sensParams.insert(&_initCp[pId.reaction * _disc.nComp * _disc.nParType + pId.component]); + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initCp[pId.reaction * _disc.nComp * _disc.nParType + t * _disc.nComp + pId.component].setADValue(adDirection, adValue); + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + + if (_singleRadiusInitQ) + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + _sensParams.insert(&_initQ[_disc.nBoundBeforeType[0] + _disc.boundOffset[pId.component] + pId.boundState]); + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initQ[r * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[t] + _disc.boundOffset[t * _disc.nComp + pId.component] + pId.boundState].setADValue(adDirection, adValue); + } + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + _sensParams.insert(&_initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[0] + _disc.boundOffset[pId.component] + pId.boundState]); + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[t] + _disc.boundOffset[t * _disc.nComp + pId.component] + pId.boundState].setADValue(adDirection, adValue); + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + } + else + { + if (_singleRadiusInitCp) + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + _sensParams.insert(&_initCp[pId.particleType * _disc.nComp + pId.component]); + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + _initCp[r * _disc.nComp * _disc.nParType + pId.particleType * _disc.nComp + pId.component].setADValue(adDirection, adValue); + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + _sensParams.insert(&_initCp[pId.reaction * _disc.nComp * _disc.nParType + pId.particleType * _disc.nComp + pId.component]); + _initCp[pId.reaction * _disc.nComp * _disc.nParType + pId.particleType * _disc.nComp + pId.component].setADValue(adDirection, adValue); + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + + if (_singleRadiusInitQ) + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + _sensParams.insert(&_initQ[_disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState]); + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + _initQ[r * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState].setADValue(adDirection, adValue); + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + _sensParams.insert(&_initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState]); + _initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState].setADValue(adDirection, adValue); + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + } + return 0; +} + +int LumpedRateModelWithPoresDG2D::multiplexInitialConditions(const cadet::ParameterId& pId, double val, bool checkSens) +{ + if (pId.name == hashString("INIT_C") && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep)) + { + if ((pId.reaction == ReactionIndep) && _singleRadiusInitC) + { + if (checkSens && !contains(_sensParams, &_initC[pId.component])) + return -1; + + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + _initC[r * _disc.nComp + pId.component].setValue(val); + + return 1; + } + else if ((pId.reaction != ReactionIndep) && !_singleRadiusInitC) + { + if (checkSens && !contains(_sensParams, &_initC[pId.reaction * _disc.nComp + pId.component])) + return -1; + + _initC[pId.reaction * _disc.nComp + pId.component].setValue(val); + return 1; + } + else + return -1; + } + + if (_singleBinding) + { + if (_singleRadiusInitCp) + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initCp[pId.component])) + return -1; + + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initCp[r * _disc.nComp * _disc.nParType + t * _disc.nComp + pId.component].setValue(val); + } + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initCp[pId.reaction * _disc.nComp * _disc.nParType + pId.component])) + return -1; + + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initCp[pId.reaction * _disc.nComp * _disc.nParType + t * _disc.nComp + pId.component].setValue(val); + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + + if (_singleRadiusInitQ) + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initQ[_disc.nBoundBeforeType[0] + _disc.boundOffset[pId.component] + pId.boundState])) + return -1; + + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initQ[r * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[t] + _disc.boundOffset[t * _disc.nComp + pId.component] + pId.boundState].setValue(val); + } + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType == ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[0] + _disc.boundOffset[pId.component] + pId.boundState])) + return -1; + + for (unsigned int t = 0; t < _disc.nParType; ++t) + _initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[t] + _disc.boundOffset[t * _disc.nComp + pId.component] + pId.boundState].setValue(val); + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + } + else + { + if (_singleRadiusInitCp) + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initCp[pId.particleType * _disc.nComp + pId.component])) + return -1; + + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + _initCp[r * _disc.nComp * _disc.nParType + pId.particleType * _disc.nComp + pId.component].setValue(val); + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_CP")) && (pId.section == SectionIndep) && (pId.boundState == BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initCp[pId.reaction * _disc.nComp * _disc.nParType + pId.particleType * _disc.nComp + pId.component])) + return -1; + + _initCp[pId.reaction * _disc.nComp * _disc.nParType + pId.particleType * _disc.nComp + pId.component].setValue(val); + + return 1; + } + else if (pId.name == hashString("INIT_CP")) + return -1; + } + + if (_singleRadiusInitQ) + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction == ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initQ[_disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState])) + return -1; + + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + _initQ[r * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState].setValue(val); + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + else + { + if ((pId.name == hashString("INIT_Q")) && (pId.section == SectionIndep) && (pId.boundState != BoundStateIndep) && (pId.particleType != ParTypeIndep) && (pId.component != CompIndep) && (pId.reaction != ReactionIndep)) + { + if (checkSens && !contains(_sensParams, &_initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState])) + return -1; + + _initQ[pId.reaction * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[pId.particleType] + _disc.boundOffset[pId.particleType * _disc.nComp + pId.component] + pId.boundState].setValue(val); + + return 1; + } + else if (pId.name == hashString("INIT_Q")) + return -1; + } + } + return 0; +} + +void LumpedRateModelWithPoresDG2D::applyInitialCondition(const SimulationState& simState) const +{ + Indexer idxr(_disc); + + // Check whether full state vector is available as initial condition + if (!_initState.empty()) + { + std::fill(simState.vecStateY, simState.vecStateY + idxr.offsetC(), 0.0); + std::copy(_initState.data(), _initState.data() + numPureDofs(), simState.vecStateY + idxr.offsetC()); + + if (!_initStateDot.empty()) + { + std::fill(simState.vecStateYdot, simState.vecStateYdot + idxr.offsetC(), 0.0); + std::copy(_initStateDot.data(), _initStateDot.data() + numPureDofs(), simState.vecStateYdot + idxr.offsetC()); + } + else + std::fill(simState.vecStateYdot, simState.vecStateYdot + numDofs(), 0.0); + + return; + } + + double* const stateYbulk = simState.vecStateY + idxr.offsetC(); + + // Loop over axial cells + for (unsigned int col = 0; col < _disc.axNPoints; ++col) + { + // Loop over radial cells + for (unsigned int rad = 0; rad < _disc.radNPoints; ++rad) + { + // Loop over components in cell + for (unsigned comp = 0; comp < _disc.nComp; ++comp) + stateYbulk[col * idxr.strideColAxialNode() + rad * idxr.strideColRadialNode() + comp * idxr.strideColComp()] = static_cast(_initC[comp + rad * _disc.nComp]); + } + } + + // Loop over particles + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + for (unsigned int col = 0; col < _disc.axNPoints * _disc.radNPoints; ++col) + { + const unsigned int rad = col % _disc.radNPoints; + const unsigned int offset = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ col }); + + // Initialize c_p + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + simState.vecStateY[offset + comp] = static_cast(_initCp[comp + _disc.nComp * type + rad * _disc.nComp * _disc.nParType]); + + // Initialize q + active const* const iq = _initQ.data() + rad * _disc.strideBound[_disc.nParType] + _disc.nBoundBeforeType[type]; + for (unsigned int bnd = 0; bnd < _disc.strideBound[type]; ++bnd) + simState.vecStateY[offset + idxr.strideParLiquid() + bnd] = static_cast(iq[bnd]); + } + } +} + +void LumpedRateModelWithPoresDG2D::readInitialCondition(IParameterProvider& paramProvider) +{ + _initState.clear(); + _initStateDot.clear(); + + // Check if INIT_STATE is present + if (paramProvider.exists("INIT_STATE")) + { + const std::vector initState = paramProvider.getDoubleArray("INIT_STATE"); + _initState = std::vector(initState.begin(), initState.begin() + numPureDofs()); + + // Check if INIT_STATE contains the full state and its time derivative + if (initState.size() >= 2 * numPureDofs()) + _initStateDot = std::vector(initState.begin() + numPureDofs(), initState.begin() + 2 * numPureDofs()); + return; + } + + const std::vector initC = paramProvider.getDoubleArray("INIT_C"); + _singleRadiusInitC = (initC.size() < _disc.nComp * _disc.radNPoints); + + if (((initC.size() < _disc.nComp) && _singleRadiusInitC) || ((initC.size() < _disc.nComp * _disc.radNPoints) && !_singleRadiusInitC)) + throw InvalidParameterException("INIT_C does not contain enough values for all components (and radial zones)"); + + if (!_singleRadiusInitC) + ad::copyToAd(initC.data(), _initC.data(), _disc.nComp * _disc.radNPoints); + else + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + ad::copyToAd(initC.data(), _initC.data() + r * _disc.nComp, _disc.nComp); + } + + // Check if INIT_CP is present + if (paramProvider.exists("INIT_CP")) + { + const std::vector initCp = paramProvider.getDoubleArray("INIT_CP"); + + _singleRadiusInitCp = (initCp.size() == _disc.nComp * _disc.nParType) || (initCp.size() == _disc.nComp); + + if ( + ((initCp.size() < _disc.nComp * _disc.nParType) && !_singleBinding && _singleRadiusInitCp) || ((initCp.size() < _disc.nComp) && _singleBinding && _singleRadiusInitCp) + || ((initCp.size() < _disc.nComp * _disc.radNPoints) && _singleBinding && !_singleRadiusInitCp) + || ((initCp.size() < _disc.nComp * _disc.nParType * _disc.radNPoints) && !_singleBinding && !_singleRadiusInitCp) + ) + throw InvalidParameterException("INIT_CP does not contain enough values for all components"); + + if (!_singleBinding && !_singleRadiusInitCp) + ad::copyToAd(initCp.data(), _initCp.data(), _disc.nComp * _disc.nParType * _disc.radNPoints); + else if (!_singleBinding && _singleRadiusInitCp) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + ad::copyToAd(initCp.data(), _initCp.data() + r * _disc.nComp * _disc.nParType, _disc.nComp * _disc.nParType); + } + else if (_singleBinding && !_singleRadiusInitCp) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + ad::copyToAd(initCp.data() + r * _disc.nComp, _initCp.data() + t * _disc.nComp + r * _disc.nComp * _disc.nParType, _disc.nComp); + } + } + else if (_singleBinding && _singleRadiusInitCp) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + ad::copyToAd(initCp.data(), _initCp.data() + t * _disc.nComp + r * _disc.nComp * _disc.nParType, _disc.nComp); + } + } + } + else + { + _singleRadiusInitCp = _singleRadiusInitC; + + if (!_singleRadiusInitCp) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + ad::copyToAd(initC.data() + r * _disc.nComp, _initCp.data() + t * _disc.nComp + r * _disc.nComp * _disc.nParType, _disc.nComp); + } + } + else + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + ad::copyToAd(initC.data(), _initCp.data() + t * _disc.nComp + r * _disc.nComp * _disc.nParType, _disc.nComp); + } + } + } + + std::vector initQ; + if (paramProvider.exists("INIT_Q")) + { + initQ = paramProvider.getDoubleArray("INIT_Q"); + _singleRadiusInitQ = (initQ.size() == _disc.strideBound[0]) || (initQ.size() == _disc.strideBound[_disc.nParType]); + } + + if (initQ.empty() || (_disc.strideBound[_disc.nParType] == 0)) + return; + + if ((_disc.strideBound[_disc.nParType] > 0) && ( + ((initQ.size() < _disc.strideBound[_disc.nParType]) && !_singleBinding && _singleRadiusInitQ) || ((initQ.size() < _disc.strideBound[0]) && _singleBinding && _singleRadiusInitQ) + || ((initQ.size() < _disc.strideBound[0] * _disc.radNPoints) && _singleBinding && !_singleRadiusInitQ) + || ((initQ.size() < _disc.strideBound[_disc.nParType] * _disc.radNPoints) && !_singleBinding && !_singleRadiusInitQ) + )) + throw InvalidParameterException("INIT_Q does not contain enough values for all bound states"); + + if (!_singleBinding && !_singleRadiusInitQ) + ad::copyToAd(initQ.data(), _initQ.data(), _disc.strideBound[_disc.nParType] * _disc.radNPoints); + else if (!_singleBinding && _singleRadiusInitQ) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + ad::copyToAd(initQ.data(), _initQ.data() + r * _disc.strideBound[_disc.nParType], _disc.strideBound[_disc.nParType]); + } + else if (_singleBinding && !_singleRadiusInitQ) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + ad::copyToAd(initQ.data() + r * _disc.strideBound[0], _initQ.data() + _disc.nBoundBeforeType[t] + r * _disc.strideBound[_disc.nParType], _disc.strideBound[t]); + } + } + else if (_singleBinding && _singleRadiusInitQ) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int t = 0; t < _disc.nParType; ++t) + ad::copyToAd(initQ.data(), _initQ.data() + _disc.nBoundBeforeType[t] + r * _disc.strideBound[_disc.nParType], _disc.strideBound[t]); + } + } +} + +/** + * @brief Computes consistent initial values (state variables without their time derivatives) + * @details Given the DAE \f[ F(t, y, \dot{y}) = 0, \f] the initial values \f$ y_0 \f$ and \f$ \dot{y}_0 \f$ have + * to be consistent. This functions updates the initial state \f$ y_0 \f$ and overwrites the time + * derivative \f$ \dot{y}_0 \f$ such that they are consistent. + * + * The process works in two steps: + *
    + *
  1. Solve all algebraic equations in the model (e.g., quasi-stationary isotherms, reaction equilibria). + * Once all @f$ c_i @f$, @f$ c_{p,i} @f$, and @f$ q_i^{(j)} @f$ have been computed, solve for the + * fluxes @f$ j_{f,i} @f$ (only linear equations).
  2. + *
  3. Compute the time derivatives of the state @f$ \dot{y} @f$ such that the residual is 0. + * However, because of the algebraic equations, we need additional conditions to fully determine + * @f$ \dot{y}@f$. By differentiating the algebraic equations with respect to time, we get the + * missing linear equations (recall that the state vector @f$ y @f$ is fixed). The resulting system + * has a similar structure as the system Jacobian. + * + * The right hand side of the linear system is given by the negative residual without contribution + * of @f$ \dot{y} @f$ for differential equations and 0 for algebraic equations + * (@f$ -\frac{\partial F}{\partial t}@f$, to be more precise). + * + * The linear system is solved by backsubstitution. First, the diagonal blocks are solved in parallel. + * Then, the equations for the fluxes @f$ j_f @f$ are solved by substituting in the solution of the + * diagonal blocks.
  4. + *
+ * This function performs step 1. See consistentInitialTimeDerivative() for step 2. + * + * This function is to be used with consistentInitialTimeDerivative(). Do not mix normal and lean + * consistent initialization! + * + * @param [in] simTime Simulation time information (time point, section index, pre-factor of time derivatives) + * @param [in,out] vecStateY State vector with initial values that are to be updated for consistency + * @param [in,out] adJac Jacobian information for AD (AD vectors for residual and state, direction offset) + * @param [in] errorTol Error tolerance for algebraic equations + * @todo Decrease amount of allocated memory by partially using temporary vectors (state and Schur complement) + */ +void LumpedRateModelWithPoresDG2D::consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerConsistentInit); + + Indexer idxr(_disc); + + // Step 1: Solve algebraic equations + + // Step 1a: Compute quasi-stationary binding model state + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + if (!_binding[type]->hasQuasiStationaryReactions()) + continue; + + // Copy quasi-stationary binding mask to a local array that also includes the mobile phase + std::vector qsMask(_disc.nComp + _disc.strideBound[type], false); + int const* const qsMaskSrc = _binding[type]->reactionQuasiStationarity(); + std::copy_n(qsMaskSrc, _disc.strideBound[type], qsMask.data() + _disc.nComp); + + // Activate mobile phase components that have at least one active bound state + unsigned int bndStartIdx = 0; + unsigned int numActiveComp = 0; + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + { + for (unsigned int bnd = 0; bnd < _disc.nBound[_disc.nComp * type + comp]; ++bnd) + { + if (qsMaskSrc[bndStartIdx + bnd]) + { + ++numActiveComp; + qsMask[comp] = true; + break; + } + } + + bndStartIdx += _disc.nBound[_disc.nComp * type + comp]; + } + + const linalg::ConstMaskArray mask{ qsMask.data(), static_cast(_disc.nComp + _disc.strideBound[type]) }; + const int probSize = linalg::numMaskActive(mask); + + //Problem capturing variables here + for (unsigned int pblk = 0; pblk < _disc.nBulkPoints; ++pblk) + { + LinearBufferAllocator tlmAlloc = threadLocalMem.get(); + + // Reuse memory of sparse matrix for dense matrix + linalg::DenseMatrixView fullJacobianMatrix(_globalJacDisc.valuePtr() + _globalJacDisc.outerIndexPtr()[idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }) - idxr.offsetC()], nullptr, mask.len, mask.len); + + // z and r coordinates (column length/radius normed to 1) of current node - needed in externally dependent adsorption kinetic + const int axNodeIdx = std::floor(pblk / _disc.radNPoints); + const int radNodeIdx = pblk % _disc.radNPoints; + const double z = _convDispOp.relativeAxialCoordinate(axNodeIdx); + const double r = _convDispOp.relativeRadialCoordinate(radNodeIdx); + + // Get workspace memory + BufferedArray nonlinMemBuffer = tlmAlloc.array(_nonlinearSolver->workspaceSize(probSize)); + double* const nonlinMem = static_cast(nonlinMemBuffer); + + BufferedArray solutionBuffer = tlmAlloc.array(probSize); + double* const solution = static_cast(solutionBuffer); + + BufferedArray fullResidualBuffer = tlmAlloc.array(mask.len); + double* const fullResidual = static_cast(fullResidualBuffer); + + BufferedArray fullXBuffer = tlmAlloc.array(mask.len); + double* const fullX = static_cast(fullXBuffer); + + BufferedArray jacobianMemBuffer = tlmAlloc.array(probSize * probSize); + linalg::DenseMatrixView jacobianMatrix(static_cast(jacobianMemBuffer), _globalJacDisc.outerIndexPtr() + pblk * probSize, probSize, probSize); + + BufferedArray conservedQuantsBuffer = tlmAlloc.array(numActiveComp); + double* const conservedQuants = static_cast(conservedQuantsBuffer); + + const parts::cell::CellParameters cellResParams + { + _disc.nComp, + _disc.nBound + _disc.nComp * type, + _disc.boundOffset + _disc.nComp * type, + _disc.strideBound[type], + _binding[type]->reactionQuasiStationarity(), + _parPorosity[type], + _poreAccessFactor.data() + _disc.nComp * type, + _binding[type], + (_dynReaction[type] && (_dynReaction[type]->numReactionsCombined() > 0)) ? _dynReaction[type] : nullptr + }; + + const int localOffsetToParticle = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }); + const int localOffsetInParticle = idxr.strideParLiquid(); + + // Get pointer to q variables in a shell of particle pblk + double* const qShell = vecStateY + localOffsetToParticle + localOffsetInParticle; + active* const localAdRes = adJac.adRes ? adJac.adRes + localOffsetToParticle : nullptr; + active* const localAdY = adJac.adY ? adJac.adY + localOffsetToParticle : nullptr; + + const ColumnPosition colPos{ z, r, static_cast(_parRadius[type]) * 0.5 }; + + // Determine whether nonlinear solver is required + if (!_binding[type]->preConsistentInitialState(simTime.t, simTime.secIdx, colPos, qShell, qShell - idxr.strideParLiquid(), tlmAlloc)) + CADET_PAR_CONTINUE; + + // Extract initial values from current state + linalg::selectVectorSubset(qShell - _disc.nComp, mask, solution); + + // Save values of conserved moieties + const double epsQ = 1.0 - static_cast(_parPorosity[type]); + linalg::conservedMoietiesFromPartitionedMask(mask, _disc.nBound + type * _disc.nComp, _disc.nComp, qShell - _disc.nComp, conservedQuants, static_cast(_parPorosity[type]), epsQ); + + std::function jacFunc; + if (localAdY && localAdRes) + { + jacFunc = [&](double const* const x, linalg::detail::DenseMatrixBase& mat) + { + // Copy over state vector to AD state vector (without changing directional values to keep seed vectors) + // and initialize residuals with zero (also resetting directional values) + ad::copyToAd(qShell - _disc.nComp, localAdY, mask.len); + // @todo Check if this is necessary + ad::resetAd(localAdRes, mask.len); + + // Prepare input vector by overwriting masked items + linalg::applyVectorSubset(x, mask, localAdY); + + // Call residual function + parts::cell::residualKernel( + simTime.t, simTime.secIdx, colPos, localAdY, nullptr, localAdRes, fullJacobianMatrix.row(0), cellResParams, tlmAlloc + ); + + // Extract Jacobian from AD + // Read particle Jacobian entries from dedicated AD directions + int offsetParticleTypeDirs = adJac.adDirOffset + requiredADdirs(); + const active* const adRes = adJac.adRes; + + for (unsigned int type = 0; type < _disc.nParType; type++) + { + for (unsigned int par = 0; par < _disc.nBulkPoints; par++) + { + const int eqOffset_res = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ par }); + const int eqOffset_mat = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ par }) - idxr.offsetC(); + for (unsigned int phase = 0; phase < idxr.strideParBlock(type); phase++) + { + for (unsigned int phaseTo = 0; phaseTo < idxr.strideParBlock(type); phaseTo++) + { + _globalJac.coeffRef(eqOffset_mat + phase, eqOffset_mat + phaseTo) = adRes[eqOffset_res + phase].getADValue(offsetParticleTypeDirs + phaseTo); + } + } + } + offsetParticleTypeDirs += idxr.strideParBlock(type); + } + + // Extract Jacobian from full Jacobian + mat.setAll(0.0); + linalg::copyMatrixSubset(fullJacobianMatrix, mask, mask, mat); + + // Replace upper part with conservation relations + mat.submatrixSetAll(0.0, 0, 0, numActiveComp, probSize); + + unsigned int bndIdx = 0; + unsigned int rIdx = 0; + unsigned int bIdx = 0; + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + { + if (!mask.mask[comp]) + { + bndIdx += _disc.nBound[_disc.nComp * type + comp]; + continue; + } + + mat.native(rIdx, rIdx) = static_cast(_parPorosity[type]); + + for (unsigned int bnd = 0; bnd < _disc.nBound[_disc.nComp * type + comp]; ++bnd, ++bndIdx) + { + if (mask.mask[bndIdx]) + { + mat.native(rIdx, bIdx + numActiveComp) = epsQ; + ++bIdx; + } + } + + ++rIdx; + } + + return true; + }; + } + else + { + jacFunc = [&](double const* const x, linalg::detail::DenseMatrixBase& mat) + { + // Prepare input vector by overwriting masked items + std::copy_n(qShell - _disc.nComp, mask.len, fullX); + linalg::applyVectorSubset(x, mask, fullX); + + // Call residual function + parts::cell::residualKernel( + simTime.t, simTime.secIdx, colPos, fullX, nullptr, fullResidual, fullJacobianMatrix.row(0), cellResParams, tlmAlloc + ); + + // Extract Jacobian from full Jacobian + mat.setAll(0.0); + linalg::copyMatrixSubset(fullJacobianMatrix, mask, mask, mat); + + // Replace upper part with conservation relations + mat.submatrixSetAll(0.0, 0, 0, numActiveComp, probSize); + + unsigned int bndIdx = 0; + unsigned int rIdx = 0; + unsigned int bIdx = 0; + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + { + if (!mask.mask[comp]) + { + bndIdx += _disc.nBound[_disc.nComp * type + comp]; + continue; + } + + mat.native(rIdx, rIdx) = static_cast(_parPorosity[type]); + + for (unsigned int bnd = 0; bnd < _disc.nBound[_disc.nComp * type + comp]; ++bnd, ++bndIdx) + { + if (mask.mask[bndIdx]) + { + mat.native(rIdx, bIdx + numActiveComp) = epsQ; + ++bIdx; + } + } + + ++rIdx; + } + + return true; + }; + } + + // Apply nonlinear solver + _nonlinearSolver->solve( + [&](double const* const x, double* const r) + { + // Prepare input vector by overwriting masked items + std::copy_n(qShell - _disc.nComp, mask.len, fullX); + linalg::applyVectorSubset(x, mask, fullX); + + // Call residual function + parts::cell::residualKernel( + simTime.t, simTime.secIdx, colPos, fullX, nullptr, fullResidual, fullJacobianMatrix.row(0), cellResParams, tlmAlloc + ); + + // Extract values from residual + linalg::selectVectorSubset(fullResidual, mask, r); + + // Calculate residual of conserved moieties + std::fill_n(r, numActiveComp, 0.0); + unsigned int bndIdx = _disc.nComp; + unsigned int rIdx = 0; + unsigned int bIdx = 0; + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + { + if (!mask.mask[comp]) + { + bndIdx += _disc.nBound[_disc.nComp * type + comp]; + continue; + } + + r[rIdx] = static_cast(_parPorosity[type]) * x[rIdx] - conservedQuants[rIdx]; + + for (unsigned int bnd = 0; bnd < _disc.nBound[_disc.nComp * type + comp]; ++bnd, ++bndIdx) + { + if (mask.mask[bndIdx]) + { + r[rIdx] += epsQ * x[bIdx + numActiveComp]; + ++bIdx; + } + } + + ++rIdx; + } + + return true; + }, + jacFunc, errorTol, solution, nonlinMem, jacobianMatrix, probSize); + + // Apply solution + linalg::applyVectorSubset(solution, mask, qShell - idxr.strideParLiquid()); + + // Refine / correct solution + _binding[type]->postConsistentInitialState(simTime.t, simTime.secIdx, colPos, qShell, qShell - idxr.strideParLiquid(), tlmAlloc); + + } CADET_PARFOR_END; + } + + // reset jacobian pattern //@todo can this be avoided? + setGlobalJacPattern(_globalJacDisc); +} + +/** + * @brief Computes consistent initial time derivatives + * @details Given the DAE \f[ F(t, y, \dot{y}) = 0, \f] the initial values \f$ y_0 \f$ and \f$ \dot{y}_0 \f$ have + * to be consistent. This functions updates the initial state \f$ y_0 \f$ and overwrites the time + * derivative \f$ \dot{y}_0 \f$ such that they are consistent. + * + * The process works in two steps: + *
    + *
  1. Solve all algebraic equations in the model (e.g., quasi-stationary isotherms, reaction equilibria). + * Once all @f$ c_i @f$, @f$ c_{p,i} @f$, and @f$ q_i^{(j)} @f$ have been computed, solve for the + * fluxes @f$ j_{f,i} @f$ (only linear equations).
  2. + *
  3. Compute the time derivatives of the state @f$ \dot{y} @f$ such that the residual is 0. + * However, because of the algebraic equations, we need additional conditions to fully determine + * @f$ \dot{y}@f$. By differentiating the algebraic equations with respect to time, we get the + * missing linear equations (recall that the state vector @f$ y @f$ is fixed). The resulting system + * has a similar structure as the system Jacobian. + * + * The right hand side of the linear system is given by the negative residual without contribution + * of @f$ \dot{y} @f$ for differential equations and 0 for algebraic equations + * (@f$ -\frac{\partial F}{\partial t}@f$, to be more precise). + * + * The linear system is solved by backsubstitution. First, the diagonal blocks are solved in parallel. + * Then, the equations for the fluxes @f$ j_f @f$ are solved by substituting in the solution of the + * diagonal blocks.
  4. + *
+ * This function performs step 2. See consistentInitialState() for step 1. + * + * This function is to be used with consistentInitialState(). Do not mix normal and lean + * consistent initialization! + * + * @param [in] simTime Simulation time information (time point, section index, pre-factor of time derivatives) + * @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 LumpedRateModelWithPoresDG2D::consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerConsistentInit); + + Indexer idxr(_disc); + + // Step 2: Compute the correct time derivative of the state vector + + // Step 2a: Assemble, factorize, and solve diagonal blocks of linear system + + // Note that the residual has not been negated, yet. We will do that now. + for (unsigned int i = 0; i < numDofs(); ++i) + vecStateYdot[i] = -vecStateYdot[i]; + + std::fill_n(_globalJacDisc.valuePtr(), _globalJacDisc.nonZeros(), 0.0); + + // Handle bulk column block + _convDispOp.addTimeDerivativeToJacobian(1.0, _globalJacDisc); + + // Process the particle blocks + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + for (unsigned int blk = 0; blk < _disc.axNPoints * _disc.radNPoints; ++blk) + { + // Midpoint of current column cell (z, rho coordinate) - needed in externally dependent adsorption kinetic + const unsigned int axialNode = blk / _disc.radNPoints; + const unsigned int radialNode = blk % _disc.radNPoints; + + const double z = static_cast(_convDispOp.relativeAxialCoordinate(axialNode)) * static_cast(_convDispOp.columnLength()); + const double r = static_cast(_convDispOp.relativeRadialCoordinate(radialNode)) * static_cast(_convDispOp.columnRadius()); + + linalg::BandedEigenSparseRowIterator jac(_globalJacDisc, idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ blk }) - idxr.offsetC()); + + LinearBufferAllocator tlmAlloc = threadLocalMem.get(); + + double* const dFluxDt = _tempState + idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ blk }); + + addTimeDerivativeToJacobianParticleBlock(jac, idxr, 1.0, type); // Mobile and solid phase equations (advances jac accordingly) + + // Iterator jac has already been advanced to next shell + + if (!_binding[type]->hasQuasiStationaryReactions()) + continue; + + // Get iterators to beginning of solid phase + linalg::BandedEigenSparseRowIterator jacSolidOrig(_globalJac, idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ blk }) - idxr.offsetC() + static_cast(idxr.strideParLiquid())); + linalg::BandedEigenSparseRowIterator jacSolid(_globalJacDisc, idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ blk }) - idxr.offsetC() + static_cast(idxr.strideParLiquid())); + + int const* const mask = _binding[type]->reactionQuasiStationarity(); + double* const qShellDot = vecStateYdot + idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ blk }) + idxr.strideParLiquid(); + + // Obtain derivative of fluxes wrt. time + std::fill_n(dFluxDt, _disc.strideBound[type], 0.0); + if (_binding[type]->dependsOnTime()) + { + _binding[type]->timeDerivativeQuasiStationaryFluxes(simTime.t, simTime.secIdx, + ColumnPosition{ z, r, 0.5 * static_cast(_parRadius[type]) }, + qShellDot - _disc.nComp, qShellDot, dFluxDt, tlmAlloc); + } + + // Copy row from original Jacobian and set right hand side + for (int i = 0; i < idxr.strideParBound(type); ++i, ++jacSolid, ++jacSolidOrig) + { + if (!mask[i]) + continue; + + jacSolid.copyRowFrom(jacSolidOrig); + qShellDot[i] = -dFluxDt[i]; + } + } + } + + _globalSolver.factorize(_globalJacDisc); + + if (cadet_unlikely(_globalSolver.info() != Eigen::Success)) { + LOG(Error) << "Factorize() failed"; + } + + Eigen::Map yDot(vecStateYdot + idxr.offsetC(), numPureDofs()); + + yDot = _globalSolver.solve(yDot); + + if (cadet_unlikely(_globalSolver.info() != Eigen::Success)) + { + LOG(Error) << "Solve() failed"; + } +} + +/** + * @brief Computes approximately / partially consistent initial values (state variables without their time derivatives) + * @details Given the DAE \f[ F(t, y, \dot{y}) = 0, \f] the initial values \f$ y_0 \f$ and \f$ \dot{y}_0 \f$ have + * to be consistent. This functions updates the initial state \f$ y_0 \f$ and overwrites the time + * derivative \f$ \dot{y}_0 \f$ such that they are consistent. + * + * This function performs a relaxed consistent initialization: Only parts of the vectors are updated + * and, hence, consistency is not guaranteed. Since there is less work to do, it is probably faster than + * the standard process represented by consistentInitialState(). + * + * The process works in two steps: + *
    + *
  1. Keep state and time derivative vectors as they are (i.e., do not solve algebraic equations). + * Only solve for the fluxes @f$ j_{f,i} @f$ (only linear equations).
  2. + *
  3. Compute the time derivatives of the state @f$ \dot{y} @f$ such that the residual is 0 in the column + * bulk and flux blocks. + * + * The right hand side of the linear system is given by the negative residual without contribution + * of @f$ \dot{y} @f$ for the bulk block and 0 for the flux block. + * + * The linear system is solved by backsubstitution. First, the bulk block is solved. + * Then, the equations for the fluxes @f$ j_f @f$ are solved by substituting in the solution of the + * bulk block and the unchanged particle block time derivative vectors.
  4. + *
+ * This function performs step 1. See leanConsistentInitialTimeDerivative() for step 2. + * + * This function is to be used with leanConsistentInitialTimeDerivative(). Do not mix normal and lean + * consistent initialization! + * + * @param [in] simTime Simulation time information (time point, section index, pre-factor of time derivatives) + * @param [in,out] vecStateY State vector with initial values that are to be updated for consistency + * @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 LumpedRateModelWithPoresDG2D::leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerConsistentInit); + + Indexer idxr(_disc); + + // Step 1: Nothing to do here as there are no flux state variables +} + +/** + * @brief Computes approximately / partially consistent initial time derivatives + * @details Given the DAE \f[ F(t, y, \dot{y}) = 0, \f] the initial values \f$ y_0 \f$ and \f$ \dot{y}_0 \f$ have + * to be consistent. This functions updates the initial state \f$ y_0 \f$ and overwrites the time + * derivative \f$ \dot{y}_0 \f$ such that they are consistent. + * + * This function performs a relaxed consistent initialization: Only parts of the vectors are updated + * and, hence, consistency is not guaranteed. Since there is less work to do, it is probably faster than + * the standard process represented by consistentInitialTimeDerivative(). + * + * The process works in two steps: + *
    + *
  1. Keep state and time derivative vectors as they are (i.e., do not solve algebraic equations). + * Only solve for the fluxes @f$ j_{f,i} @f$ (only linear equations).
  2. + *
  3. Compute the time derivatives of the state @f$ \dot{y} @f$ such that the residual is 0 in the column + * bulk and flux blocks. + * + * The right hand side of the linear system is given by the negative residual without contribution + * of @f$ \dot{y} @f$ for the bulk block and 0 for the flux block. + * + * The linear system is solved by backsubstitution. First, the bulk block is solved. + * Then, the equations for the fluxes @f$ j_f @f$ are solved by substituting in the solution of the + * bulk block and the unchanged particle block time derivative vectors.
  4. + *
+ * This function performs step 2. See leanConsistentInitialState() for step 1. + * + * This function is to be used with leanConsistentInitialState(). Do not mix normal and lean + * consistent initialization! + * + * @param [in] t Current time point + * @param [in] vecStateY (Lean) consistently initialized state vector + * @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 LumpedRateModelWithPoresDG2D::leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerConsistentInit); + + Indexer idxr(_disc); + + // Step 2: Compute the correct time derivative of the state vector + + // Step 2a: Assemble, factorize, and solve column bulk block of linear system + + // Note that the residual is not negated as required at this point. We will fix that later. + + double* const resSlice = res + idxr.offsetC(); + + // Nothing to do for the bulk block (Identity block) + + // Note that we have solved with the *positive* residual as right hand side + // instead of the *negative* one. Fortunately, we are dealing with linear systems, + // which means that we can just negate the solution. + double* const yDotSlice = vecStateYdot + idxr.offsetC(); + for (unsigned int i = 0; i < _disc.axNPoints * _disc.radNPoints * _disc.nComp; ++i) + yDotSlice[i] = -resSlice[i]; +} + +void LumpedRateModelWithPoresDG2D::initializeSensitivityStates(const std::vector& vecSensY) const +{ + Indexer idxr(_disc); + for (std::size_t param = 0; param < vecSensY.size(); ++param) + { + double* const stateYbulk = vecSensY[param] + idxr.offsetC(); + + // Loop over axial cells + for (unsigned int ax = 0; ax < _disc.axNPoints; ++ax) + { + // Loop over radial cells + for (unsigned int rad = 0; rad < _disc.radNPoints; ++rad) + { + // Loop over components in cell + for (unsigned comp = 0; comp < _disc.nComp; ++comp) + stateYbulk[ax * idxr.strideColAxialNode() + rad * idxr.strideColRadialNode() + comp * idxr.strideColComp()] = _initC[comp + rad * _disc.nComp].getADValue(param); + } + } + + // Loop over particles + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + for (unsigned int col = 0; col < _disc.axNPoints * _disc.radNPoints; ++col) + { + const unsigned int rad = col % _disc.radNPoints; + const unsigned int offset = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ col }); + + double* const stateYparticle = vecSensY[param] + offset; + double* const stateYparticleSolid = stateYparticle + idxr.strideParLiquid(); + + // Initialize c_p + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + stateYparticle[comp] = _initCp[comp + type * _disc.nComp + rad * _disc.nComp * _disc.nParType].getADValue(param); + + // Initialize q + for (unsigned int bnd = 0; bnd < _disc.strideBound[type]; ++bnd) + stateYparticleSolid[bnd] = _initQ[bnd + _disc.nBoundBeforeType[type] + _disc.strideBound[_disc.nParType] * rad].getADValue(param); + } + } + } +} + +/** + * @brief Computes consistent initial values and time derivatives of sensitivity subsystems + * @details Given the DAE \f[ F(t, y, \dot{y}) = 0, \f] and initial values \f$ y_0 \f$ and \f$ \dot{y}_0 \f$, + * the sensitivity system for a parameter @f$ p @f$ reads + * \f[ \frac{\partial F}{\partial y}(t, y, \dot{y}) s + \frac{\partial F}{\partial \dot{y}}(t, y, \dot{y}) \dot{s} + \frac{\partial F}{\partial p}(t, y, \dot{y}) = 0. \f] + * The initial values of this linear DAE, @f$ s_0 = \frac{\partial y_0}{\partial p} @f$ and @f$ \dot{s}_0 = \frac{\partial \dot{y}_0}{\partial p} @f$ + * have to be consistent with the sensitivity DAE. This functions updates the initial sensitivity\f$ s_0 \f$ and overwrites the time + * derivative \f$ \dot{s}_0 \f$ such that they are consistent. + * + * The process follows closely the one of consistentInitialConditions() and, in fact, is a linearized version of it. + * This is necessary because the initial conditions of the sensitivity system \f$ s_0 \f$ and \f$ \dot{s}_0 \f$ are + * related to the initial conditions \f$ y_0 \f$ and \f$ \dot{y}_0 \f$ of the original DAE by differentiating them + * with respect to @f$ p @f$: @f$ s_0 = \frac{\partial y_0}{\partial p} @f$ and @f$ \dot{s}_0 = \frac{\partial \dot{y}_0}{\partial p}. @f$ + *
    + *
  1. Solve all algebraic equations in the model (e.g., quasi-stationary isotherms, reaction equilibria). + * Once all @f$ c_i @f$, @f$ c_{p,i} @f$, and @f$ q_i^{(j)} @f$ have been computed, solve for the + * fluxes @f$ j_{f,i} @f$. Let @f$ \mathcal{I}_a @f$ be the index set of algebraic equations, then, at this point, we have + * \f[ \left( \frac{\partial F}{\partial y}(t, y_0, \dot{y}_0) s + \frac{\partial F}{\partial p}(t, y_0, \dot{y}_0) \right)_{\mathcal{I}_a} = 0. \f]
  2. + *
  3. Compute the time derivatives of the sensitivity @f$ \dot{s} @f$ such that the differential equations hold. + * However, because of the algebraic equations, we need additional conditions to fully determine + * @f$ \dot{s}@f$. By differentiating the algebraic equations with respect to time, we get the + * missing linear equations (recall that the sensitivity vector @f$ s @f$ is fixed). The resulting system + * has a similar structure as the system Jacobian. + * + * Let @f$ \mathcal{I}_d @f$ denote the index set of differential equations. + * The right hand side of the linear system is given by @f[ -\frac{\partial F}{\partial y}(t, y, \dot{y}) s - \frac{\partial F}{\partial p}(t, y, \dot{y}), @f] + * which is 0 for algebraic equations (@f$ -\frac{\partial^2 F}{\partial t \partial p}@f$, to be more precise). + * + * The linear system is solved by backsubstitution. First, the diagonal blocks are solved in parallel. + * Then, the equations for the fluxes @f$ j_f @f$ are solved by substituting in the solution of the + * diagonal blocks.
  4. + *
+ * This function requires the parameter sensitivities to be computed beforehand and up-to-date Jacobians. + * @param [in] simTime Simulation time information (time point, section index, pre-factor of time derivatives) + * @param [in] simState Consistent state of the simulation (state vector and its time derivative) + * @param [in,out] vecSensY Sensitivity subsystem state vectors + * @param [in,out] vecSensYdot Time derivative state vectors of the sensitivity subsystems to be initialized + * @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 LumpedRateModelWithPoresDG2D::consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, + std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerConsistentInit); + + Indexer idxr(_disc); + + for (std::size_t param = 0; param < vecSensY.size(); ++param) + { + double* const sensY = vecSensY[param]; + double* const sensYdot = vecSensYdot[param]; + + // Copy parameter derivative dF / dp from AD and negate it + for (unsigned int i = _disc.nComp * _disc.radNPoints; i < numDofs(); ++i) + sensYdot[i] = -adRes[i].getADValue(param); + + // Step 1: Solve algebraic equations + + // Step 1a: Compute quasi-stationary binding model state + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + if (!_binding[type]->hasQuasiStationaryReactions()) + continue; + + int const* const qsMask = _binding[type]->reactionQuasiStationarity(); + const linalg::ConstMaskArray mask{ qsMask, static_cast(_disc.strideBound[type]) }; + const int probSize = linalg::numMaskActive(mask); + + for (unsigned int pblk = 0; pblk < _disc.axNPoints * _disc.radNPoints; ++pblk) + { + // Reuse memory of sparse matrix for dense matrix + linalg::DenseMatrixView jacobianMatrix(_globalJacDisc.valuePtr() + _globalJacDisc.outerIndexPtr()[idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }) - idxr.offsetC()], nullptr, mask.len, mask.len); + + // Get workspace memory + LinearBufferAllocator tlmAlloc = threadLocalMem.get(); + + BufferedArray rhsBuffer = tlmAlloc.array(probSize); + double* const rhs = static_cast(rhsBuffer); + + BufferedArray rhsUnmaskedBuffer = tlmAlloc.array(idxr.strideParBound(type)); + double* const rhsUnmasked = static_cast(rhsUnmaskedBuffer); + + double* const maskedMultiplier = _tempState + idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }); + double* const scaleFactors = _tempState + idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }); + + const int jacRowOffset = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }) - idxr.offsetC(); + const int jacColOffset = jacRowOffset; + const int localQOffset = idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ static_cast(pblk) }) + idxr.strideParLiquid(); + + // Extract subproblem Jacobian from full Jacobian + jacobianMatrix.setAll(0.0); + linalg::copyMatrixSubset(_globalJac, mask, mask, jacRowOffset, jacColOffset, jacobianMatrix); + + // Construct right hand side + linalg::selectVectorSubset(sensYdot + localQOffset, mask, rhs); + + // Zero out masked elements + std::copy_n(sensY + localQOffset - idxr.strideParLiquid(), _disc.nComp + _disc.strideBound[type], maskedMultiplier); + linalg::fillVectorSubset(maskedMultiplier + _disc.nComp, mask, 0.0); + + // Assemble right hand side + Eigen::Map maskedMultiplier_eigen(maskedMultiplier, idxr.strideParBlock(type)); + Eigen::Map rhsUnmasked_eigen(rhsUnmasked, idxr.strideParBlock(type)); + rhsUnmasked_eigen = _globalJac.block(jacRowOffset, jacColOffset, idxr.strideParBlock(type), idxr.strideParBlock(type)) * maskedMultiplier_eigen; + linalg::vectorSubsetAdd(rhsUnmasked, mask, -1.0, 1.0, rhs); + + // Precondition + jacobianMatrix.rowScaleFactors(scaleFactors); + jacobianMatrix.scaleRows(scaleFactors); + + // Solve + jacobianMatrix.factorize(); + jacobianMatrix.solve(scaleFactors, rhs); + + // Write back + linalg::applyVectorSubset(rhs, mask, sensY + localQOffset); + } + } + + // Step 2: Compute the correct time derivative of the state vector + + // Step 2a: Assemble, factorize, and solve diagonal blocks of linear system + + // Compute right hand side by adding -dF / dy * s = -J * s to -dF / dp which is already stored in sensYdot + multiplyWithJacobian(simTime, simState, sensY, -1.0, 1.0, sensYdot); + + // Note that we have correctly negated the right hand side + + // Assemble bulk block + std::fill_n(_globalJacDisc.valuePtr(), _globalJacDisc.nonZeros(), 0.0); + _convDispOp.addTimeDerivativeToJacobian(1.0, _globalJacDisc); + + // Process the particle blocks + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + for (unsigned int pblk = 0; pblk < _disc.nBulkPoints; ++pblk) + { + // Assemble + linalg::BandedEigenSparseRowIterator jacPar(_globalJacDisc, idxr.offsetCp(ParticleTypeIndex{ static_cast(type) }, ParticleIndex{ pblk }) - idxr.offsetC()); + + // Mobile and solid phase + addTimeDerivativeToJacobianParticleBlock(jacPar, idxr, 1.0, type); + // Iterator jac has already been advanced to next shell + + // Overwrite rows corresponding to algebraic equations with the Jacobian and set right hand side to 0 + if (_binding[type]->hasQuasiStationaryReactions()) + { + // Get iterators to beginning of solid phase + linalg::BandedEigenSparseRowIterator jacSolidOrig(_globalJac, idxr.offsetCp(ParticleTypeIndex{ static_cast(type) }, ParticleIndex{ pblk }) - idxr.offsetC() + idxr.strideParLiquid()); + linalg::BandedEigenSparseRowIterator jacSolid(_globalJacDisc, idxr.offsetCp(ParticleTypeIndex{ static_cast(type) }, ParticleIndex{ pblk }) - idxr.offsetC() + idxr.strideParLiquid()); + + int const* const mask = _binding[type]->reactionQuasiStationarity(); + double* const qShellDot = sensYdot + idxr.offsetCp(ParticleTypeIndex{ static_cast(type) }, ParticleIndex{ pblk }) + idxr.strideParLiquid(); + + // Copy row from original Jacobian and set right hand side + for (int i = 0; i < idxr.strideParBound(type); ++i, ++jacSolid, ++jacSolidOrig) + { + if (!mask[i]) + continue; + + jacSolid.copyRowFrom(jacSolidOrig); + + // Right hand side is -\frac{\partial^2 res(t, y, \dot{y})}{\partial p \partial t} + // If the residual is not explicitly depending on time, this expression is 0 + // @todo This is wrong if external functions are used. Take that into account! + qShellDot[i] = 0.0; + } + } + } + + } CADET_PARFOR_END; + + Eigen::Map yDot(sensYdot, numPureDofs()); + + // Factorize + _globalSolver.factorize(_globalJacDisc); + + if (cadet_unlikely(_globalSolver.info() != Eigen::Success)) + { + LOG(Error) << "Factorize() failed"; + } + // Solve + yDot.segment(0, numPureDofs()) = _globalSolver.solve(yDot.segment(0, numPureDofs())); + + if (cadet_unlikely(_globalSolver.info() != Eigen::Success)) + { + LOG(Error) << "Solve() failed"; + } + + // TODO: Right hand side for fluxes should be -d^2res/(dp dy) * \dot{y} + // If parameters depend on time, then it should be + // -d^2res/(dp dy) * \dot{y} - d^2res/(dt dy) * s - d^2res/(dp dt) + } +} + +/** + * @brief Computes approximately / partially consistent initial values and time derivatives of sensitivity subsystems + * @details Given the DAE \f[ F(t, y, \dot{y}) = 0, \f] and initial values \f$ y_0 \f$ and \f$ \dot{y}_0 \f$, + * the sensitivity system for a parameter @f$ p @f$ reads + * \f[ \frac{\partial F}{\partial y}(t, y, \dot{y}) s + \frac{\partial F}{\partial \dot{y}}(t, y, \dot{y}) \dot{s} + \frac{\partial F}{\partial p}(t, y, \dot{y}) = 0. \f] + * The initial values of this linear DAE, @f$ s_0 = \frac{\partial y_0}{\partial p} @f$ and @f$ \dot{s}_0 = \frac{\partial \dot{y}_0}{\partial p} @f$ + * have to be consistent with the sensitivity DAE. This functions updates the initial sensitivity\f$ s_0 \f$ and overwrites the time + * derivative \f$ \dot{s}_0 \f$ such that they are consistent. + * + * The process follows closely the one of leanConsistentInitialConditions() and, in fact, is a linearized version of it. + * This is necessary because the initial conditions of the sensitivity system \f$ s_0 \f$ and \f$ \dot{s}_0 \f$ are + * related to the initial conditions \f$ y_0 \f$ and \f$ \dot{y}_0 \f$ of the original DAE by differentiating them + * with respect to @f$ p @f$: @f$ s_0 = \frac{\partial y_0}{\partial p} @f$ and @f$ \dot{s}_0 = \frac{\partial \dot{y}_0}{\partial p}. @f$ + *
    + *
  1. Keep state and time derivative vectors as they are (i.e., do not solve algebraic equations). + * Only solve for the fluxes @f$ j_{f,i} @f$ (only linear equations).
  2. + *
  3. Compute the time derivatives of the sensitivity @f$ \dot{s} @f$ such that the differential equations hold. + * However, because of the algebraic equations, we need additional conditions to fully determine + * @f$ \dot{s}@f$. By differentiating the algebraic equations with respect to time, we get the + * missing linear equations (recall that the sensitivity vector @f$ s @f$ is fixed). + * + * Let @f$ \mathcal{I}_d @f$ denote the index set of differential equations. + * The right hand side of the linear system is given by @f[ -\frac{\partial F}{\partial y}(t, y, \dot{y}) s - \frac{\partial F}{\partial p}(t, y, \dot{y}), @f] + * which is 0 for algebraic equations (@f$ -\frac{\partial^2 F}{\partial t \partial p}@f$, to be more precise). + * + * The linear system is solved by backsubstitution. First, the bulk block is solved. + * Then, the equations for the fluxes @f$ j_f @f$ are solved by substituting in the solution of the + * bulk block and the unchanged particle block time derivative vectors.
  4. + *
+ * This function requires the parameter sensitivities to be computed beforehand and up-to-date Jacobians. + * @param [in] simTime Simulation time information (time point, section index, pre-factor of time derivatives) + * @param [in] simState Consistent state of the simulation (state vector and its time derivative) + * @param [in,out] vecSensY Sensitivity subsystem state vectors + * @param [in,out] vecSensYdot Time derivative state vectors of the sensitivity subsystems to be initialized + * @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 LumpedRateModelWithPoresDG2D::leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, + std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerConsistentInit); + + Indexer idxr(_disc); + + for (std::size_t param = 0; param < vecSensY.size(); ++param) + { + double* const sensY = vecSensY[param]; + double* const sensYdot = vecSensYdot[param]; + + // Copy parameter derivative from AD to tempState and negate it + // We need to use _tempState in order to keep sensYdot unchanged at this point + for (int i = 0; i < idxr.offsetCp(); ++i) + _tempState[i] = -adRes[i].getADValue(param); + + // Compute the correct time derivative of the state vector, i.e. + // assemble, factorize, and solve diagonal blocks of linear system + + // Compute right hand side by adding -dF / dy * s = -J * s to -dF / dp which is already stored in _tempState + multiplyWithJacobian(simTime, simState, sensY, -1.0, 1.0, _tempState); + + // Copy relevant parts to sensYdot for use as right hand sides + std::copy(_tempState + idxr.offsetC(), _tempState + idxr.offsetCp(), sensYdot + idxr.offsetC()); + + // Handle bulk block + // Assemble + double* vals = _globalJacDisc.valuePtr(); + for (int entry = 0; entry < _globalJacDisc.nonZeros(); entry++) + vals[entry] = 0.0; + + double alpha = 1.0; + const int gapCell = idxr.strideColRadialNode() - static_cast(_disc.nComp) * idxr.strideColComp(); + linalg::BandedEigenSparseRowIterator jac(_globalJacDisc, 0); + + for (unsigned int point = 0; point < _disc.nBulkPoints; ++point, jac += gapCell) { + for (unsigned int comp = 0; comp < _disc.nComp; ++comp, ++jac) { + // dc_b / dt in transport equation + jac[0] += alpha; + } + } + + const int bulkRows = idxr.offsetCp() - idxr.offsetC(); + _globalSolver.analyzePattern(_globalJacDisc.block(0, 0, bulkRows, bulkRows)); + _globalSolver.factorize(_globalJacDisc.block(0, 0, bulkRows, bulkRows)); + + if (_globalSolver.info() != Eigen::Success) { + LOG(Error) << "factorization failed in sensitivity initialization"; + } + + Eigen::Map ret_vec(sensYdot + idxr.offsetC(), bulkRows); + ret_vec = _globalSolver.solve(ret_vec); + + // Use the factors to solve the linear system + if (_globalSolver.info() != Eigen::Success) { + LOG(Error) << "solve failed in sensitivity initialization"; + } + + // reset linear solver to global Jacobian + _globalSolver.analyzePattern(_globalJacDisc); + } +} + +} // namespace model + +} // namespace cadet diff --git a/src/libcadet/model/LumpedRateModelWithPoresDG2D-LinearSolver.cpp b/src/libcadet/model/LumpedRateModelWithPoresDG2D-LinearSolver.cpp new file mode 100644 index 000000000..c89f2ce4e --- /dev/null +++ b/src/libcadet/model/LumpedRateModelWithPoresDG2D-LinearSolver.cpp @@ -0,0 +1,231 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-present: The CADET-Core Authors +// Please see the AUTHORS.md file. +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the GNU Public License v3.0 (or, at +// your option, any later version) which accompanies this distribution, and +// is available at http://www.gnu.org/licenses/gpl.html +// ============================================================================= + +#include "model/LumpedRateModelWithPoresDG2D.hpp" +#include "model/BindingModel.hpp" +#include "model/parts/BindingCellKernel.hpp" +#include "linalg/DenseMatrix.hpp" +#include "AdUtils.hpp" + +#include +#include + +#include "LoggingUtils.hpp" +#include "Logging.hpp" + +namespace cadet +{ + +namespace model +{ +/** + * @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. + * + * The full Jacobian @f$ J = \left( \frac{\partial F}{\partial y} + \alpha \frac{\partial F}{\partial \dot{y}} \right) @f$ is given by + * @f[ \begin{align} + J = + \left[\begin{array}{c|ccc|c} + J_0 & & & & J_{0,f} \\ + \hline + & J_1 & & & J_{1,f} \\ + & & \ddots & & \vdots \\ + & & & J_{N_z} & J_{N_z,f} \\ + \hline + J_{f,0} & J_{f,1} & \dots & J_{f,N_z} & J_f + \end{array}\right]. + \end{align} @f] + * By decomposing the Jacobian @f$ J @f$ into @f$ J = LU @f$, we get + * @f[ \begin{align} + L &= \left[\begin{array}{c|ccc|c} + J_0 & & & & \\ + \hline + & J_1 & & & \\ + & & \ddots & & \\ + & & & J_{N_z} & \\ + \hline + J_{f,0} & J_{f,1} & \dots & J_{f,N_z} & I + \end{array}\right], \\ + U &= \left[\begin{array}{c|ccc|c} + I & & & & J_0^{-1} \, J_{0,f} \\ + \hline + & I & & & J_1^{-1} \, J_{1,f} \\ + & & \ddots & & \vdots \\ + & & & I & J_{N_z}^{-1} \, J_{N_z,f} \\ + \hline + & & & & S + \end{array}\right]. + \end{align} @f] + * Here, the Schur-complement @f$ S @f$ is given by + * @f[ \begin{align} + S = J_f - J_{f,0} \, J_0^{-1} \, J_{0,f} - \sum_{p=1}^{N_z}{J_{f,p} \, J_p^{-1} \, J_{p,f}}. + \end{align} @f] + * Note that @f$ J_f = I @f$ is the identity matrix and that the off-diagonal blocks @f$ J_{i,f} @f$ + * and @f$ J_{f,i} @f$ for @f$ i = 0, \dots, N_{z} @f$ are sparse. + * + * Exploiting the decomposition, the solution procedure @f$ x = J^{-1}b = \left( LU \right)^{-1}b = U^{-1} L^{-1} b @f$ + * works as follows: + * -# Factorize the diagonal blocks @f$ J_0, \dots, J_{N_z} @f$ + * -# Solve @f$ y = L^{-1} b @f$ by forward substitution. This is accomplished by first solving the diagonal + * blocks independently, that is, + * @f[ y_i = J_{i}^{-1} b_i. @f] + * Then, calculate the flux-part @f$ y_f @f$ by substituting in the already calculated solutions @f$ y_i @f$: + * @f[ y_f = b_f - \sum_{i=0}^{N_z} J_{f,i} y_i. @f] + * -# Solve the Schur-complement @f$ S x_f = y_f @f$ using an iterative method that only requires + * matrix-vector products. The already inverted diagonal blocks @f$ J_i^{-1} @f$ come in handy here. + * -# Solve the rest of the @f$ U x = y @f$ system by backward substitution. To be more precise, compute + * @f[ x_i = y_i - J_i^{-1} J_{i,f} y_f. @f] + * + * + * @param [in] t Current time point + * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) + * @param [in] outerTol 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] 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 LumpedRateModelWithPoresDG2D::linearSolve(double t, double alpha, double outerTol, double* const rhs, double const* const weight, + const ConstSimulationState& simState) +{ + BENCH_SCOPE(_timerLinearSolve); + + Indexer idxr(_disc); + + // ==== Step 1: Factorize global Jacobian (without inlet DOFs) + + // Factorize partial Jacobians only if required + if (_factorizeJacobian) + { + // Assemble and factorize discretized bulk Jacobian + assembleDiscretizedGlobalJacobian(alpha, idxr); + + _globalSolver.factorize(_globalJacDisc); + + if (cadet_unlikely(_globalSolver.info() != Eigen::Success)) { + LOG(Error) << "Factorize() failed"; + } + + // Do not factorize again at next call without changed Jacobians + _factorizeJacobian = false; + } + + // ====== Step 1.5: Solve J c_uo = b_uo - A * c_in = b_uo - A*b_in + + // rhs is passed twice but due to the values in jacA the writes happen to a different area of the rhs than the reads. + + // Handle inlet DOFs: // todo backward flow. + Eigen::Map, 0, Eigen::InnerStride> rInlet(rhs, _disc.radNPoints * _disc.nComp, Eigen::InnerStride(idxr.strideColRadialNode())); + Eigen::Map, 0, Eigen::InnerStride> rInletDep(rhs + idxr.offsetC(), _convDispOp.axNNodes() * _disc.radNPoints * _disc.nComp, Eigen::InnerStride(idxr.strideColRadialNode())); + + rInletDep += _jacInlet * rInlet; + + // ==== Step 2: Solve system of pure DOFs + // The result is stored in rhs (in-place solution) + + Eigen::Map r(rhs, numDofs()); + + r.segment(idxr.offsetC(), numPureDofs()) = _globalSolver.solve(r.segment(idxr.offsetC(), numPureDofs())); + + if (cadet_unlikely(_globalSolver.info() != Eigen::Success)) + { + LOG(Error) << "Solve() failed"; + } + + // The full solution is now stored in rhs + return 0; +} +/** + * @brief Assembles bulk Jacobian @f$ J_i @f$ (@f$ i > 0 @f$) of the time-discretized equations + * @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 system Jacobian of the original equations, + * \f[ \frac{\partial F}{\partial y}, \f] + * is already computed (by AD or manually in residualImpl() with @c wantJac = true). This function is responsible + * for adding + * \f[ \alpha \frac{\partial F}{\partial \dot{y}} \f] + * to the system Jacobian, which yields the Jacobian of the time-discretized equations + * \f[ F\left(t, y_0, \sum_{k=0}^N \alpha_k y_k \right) = 0 \f] + * when a BDF method is used. The time integrator needs to solve this equation for @f$ y_0 @f$, which requires + * the solution of the linear system mentioned above (@f$ \alpha_0 = \alpha @f$ given in @p alpha). + * + * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) + */ +void LumpedRateModelWithPoresDG2D::assembleDiscretizedGlobalJacobian(double alpha, Indexer idxr) { + + // set to static (per section) jacobian + _globalJacDisc = _globalJac; + + // add time derivative to bulk jacobian + _convDispOp.addTimeDerivativeToJacobian(alpha, _globalJacDisc); + + // Add time derivatives to particles + for (unsigned int parType = 0; parType < _disc.nParType; parType++) + { + linalg::BandedEigenSparseRowIterator jac(_globalJacDisc, idxr.offsetCp(ParticleTypeIndex{ parType }) - idxr.offsetC()); + + for (unsigned int j = 0; j < _disc.nBulkPoints; ++j) + { + addTimeDerivativeToJacobianParticleBlock(jac, idxr, alpha, parType); // Mobile and solid phase equations (advances jac accordingly) + } + } +} +/** + * @brief Adds Jacobian @f$ \frac{\partial F}{\partial \dot{y}} @f$ to bead rows of system Jacobian + * @details Actually adds @f$ \alpha \frac{\partial F}{\partial \dot{y}} @f$, which is useful + * for constructing the linear system in BDF time discretization. + * @param [in,out] jac On entry, RowIterator of the particle block pointing to the beginning of a bead shell; + * on exit, the iterator points to the end of the bead shell + * @param [in] idxr Indexer + * @param [in] alpha Value of \f$ \alpha \f$ (arises from BDF time discretization) + * @param [in] parType Index of the particle type + */ +void LumpedRateModelWithPoresDG2D::addTimeDerivativeToJacobianParticleBlock(linalg::BandedEigenSparseRowIterator& jac, const Indexer& idxr, double alpha, unsigned int parType) +{ + // Mobile phase + for (int comp = 0; comp < static_cast(_disc.nComp); ++comp, ++jac) + { + // Add derivative with respect to dc_p / dt to Jacobian + jac[0] += alpha; + + const double invBetaP = (1.0 - static_cast(_parPorosity[parType])) / (static_cast(_poreAccessFactor[parType * _disc.nComp + comp]) * static_cast(_parPorosity[parType])); + + // Add derivative with respect to dq / dt to Jacobian + const int nBound = static_cast(_disc.nBound[parType * _disc.nComp + comp]); + for (int i = 0; i < nBound; ++i) + { + // Index explanation: + // -comp -> go back to beginning of liquid phase + // + strideParLiquid() skip to solid phase + // + offsetBoundComp() jump to component (skips all bound states of previous components) + // + i go to current bound state + jac[idxr.strideParLiquid() - comp + idxr.offsetBoundComp(ParticleTypeIndex{ parType }, ComponentIndex{ static_cast(comp) }) + i] += alpha * invBetaP; + } + } + + // Solid phase + int const* const qsReaction = _binding[parType]->reactionQuasiStationarity(); + for (unsigned int bnd = 0; bnd < _disc.strideBound[parType]; ++bnd, ++jac) + { + // Add derivative with respect to dynamic states to Jacobian + if (qsReaction[bnd]) + continue; + + // Add derivative with respect to dq / dt to Jacobian + jac[0] += alpha; + } +} + +} // namespace model +} // namespace cadet diff --git a/src/libcadet/model/LumpedRateModelWithPoresDG2D.cpp b/src/libcadet/model/LumpedRateModelWithPoresDG2D.cpp new file mode 100644 index 000000000..ecd522a26 --- /dev/null +++ b/src/libcadet/model/LumpedRateModelWithPoresDG2D.cpp @@ -0,0 +1,1773 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-present: The CADET-Core Authors +// Please see the AUTHORS.md file. +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the GNU Public License v3.0 (or, at +// your option, any later version) which accompanies this distribution, and +// is available at http://www.gnu.org/licenses/gpl.html +// ============================================================================= + +#include "model/LumpedRateModelWithPoresDG2D.hpp" +#include "BindingModelFactory.hpp" +#include "ParamReaderHelper.hpp" +#include "ParamReaderScopes.hpp" +#include "cadet/Exceptions.hpp" +#include "cadet/ExternalFunction.hpp" +#include "cadet/SolutionRecorder.hpp" +#include "ConfigurationHelper.hpp" +#include "model/BindingModel.hpp" +#include "model/ReactionModel.hpp" +#include "model/parts/BindingCellKernel.hpp" +#include "SimulationTypes.hpp" +#include "linalg/Norms.hpp" + +#include "AdUtils.hpp" +#include "SensParamUtil.hpp" + +#include "LoggingUtils.hpp" +#include "Logging.hpp" + +#include +#include +#include + +#include "ParallelSupport.hpp" + +namespace +{ + +cadet::model::MultiplexMode readAndRegisterMultiplexParam(cadet::IParameterProvider& paramProvider, std::unordered_map& parameters, std::vector& values, const std::string& name, unsigned int nAxial, unsigned int nRad, unsigned int nParType, cadet::UnitOpIdx uoi) +{ + cadet::model::MultiplexMode mode = cadet::model::MultiplexMode::Independent; + readScalarParameterOrArray(values, paramProvider, name, 1); + if (paramProvider.exists(name + "_MULTIPLEX")) + { + const int modeConfig = paramProvider.getInt(name + "_MULTIPLEX"); + if (modeConfig == 0) + { + mode = cadet::model::MultiplexMode::Independent; + if (values.size() != nParType) + throw cadet::InvalidParameterException("Number of elements in field " + name + " inconsistent with " + name + "_MULTIPLEX (should be " + std::to_string(nParType) + ")"); + } + else if (modeConfig == 1) + { + mode = cadet::model::MultiplexMode::Radial; + if (values.size() != nRad * nParType) + throw cadet::InvalidParameterException("Number of elements in field " + name + " inconsistent with " + name + "_MULTIPLEX (should be " + std::to_string(nRad * nParType) + ")"); + } + else if (modeConfig == 2) + { + mode = cadet::model::MultiplexMode::Axial; + if (values.size() != nAxial * nParType) + throw cadet::InvalidParameterException("Number of elements in field " + name + " inconsistent with " + name + "_MULTIPLEX (should be " + std::to_string(nAxial * nParType) + ")"); + } + else if (modeConfig == 3) + { + mode = cadet::model::MultiplexMode::AxialRadial; + if (values.size() != nAxial * nRad * nParType) + throw cadet::InvalidParameterException("Number of elements in field " + name + " inconsistent with " + name + "_MULTIPLEX (should be " + std::to_string(nAxial * nRad * nParType) + ")"); + } + } + else + { + if (values.size() == nParType) + mode = cadet::model::MultiplexMode::Independent; + else if (values.size() == nRad * nParType) + mode = cadet::model::MultiplexMode::Radial; + else if (values.size() == nAxial * nParType) + mode = cadet::model::MultiplexMode::Axial; + else if (values.size() == nRad * nAxial * nParType) + mode = cadet::model::MultiplexMode::AxialRadial; + else + throw cadet::InvalidParameterException("Could not infer multiplex mode of field " + name + ", set " + name + "_MULTIPLEX or change number of elements"); + } + + const cadet::StringHash nameHash = cadet::hashStringRuntime(name); + switch (mode) + { + case cadet::model::MultiplexMode::Independent: + { + std::vector p(nAxial * nRad * nParType); + for (unsigned int s = 0; s < nAxial * nRad; ++s) + std::copy(values.begin(), values.end(), p.begin() + s * nParType); + + values = std::move(p); + + for (unsigned int s = 0; s < nParType; ++s) + parameters[cadet::makeParamId(nameHash, uoi, cadet::CompIndep, s, cadet::BoundStateIndep, cadet::ReactionIndep, cadet::SectionIndep)] = &values[s]; + } + break; + case cadet::model::MultiplexMode::Radial: + { + std::vector p(nAxial * nRad * nParType); + for (unsigned int s = 0; s < nAxial; ++s) + std::copy(values.begin(), values.end(), p.begin() + s * nParType * nRad); + + values = std::move(p); + + for (unsigned int s = 0; s < nRad; ++s) + { + for (unsigned int i = 0; i < nParType; ++i) + parameters[cadet::makeParamId(nameHash, uoi, cadet::CompIndep, i, cadet::BoundStateIndep, s, cadet::SectionIndep)] = &values[s * nParType + i]; + } + } + break; + case cadet::model::MultiplexMode::Axial: + { + std::vector p(nAxial * nRad * nParType); + for (unsigned int i = 0; i < nAxial; ++i) + { + for (unsigned int j = 0; j < nRad; ++j) + std::copy(values.begin() + i * nParType, values.begin() + (i+1) * nParType, p.begin() + i * nRad * nParType + j * nParType); + } + + values = std::move(p); + + for (unsigned int s = 0; s < nAxial; ++s) + { + for (unsigned int i = 0; i < nParType; ++i) + parameters[cadet::makeParamId(nameHash, uoi, cadet::CompIndep, i, cadet::BoundStateIndep, cadet::ReactionIndep, s)] = &values[s * nParType * nRad + i]; + } + } + break; + case cadet::model::MultiplexMode::AxialRadial: + cadet::registerParam3DArray(parameters, values, [=](bool multi, unsigned int ax, unsigned int rad, unsigned int pt) { return cadet::makeParamId(nameHash, uoi, cadet::CompIndep, pt, cadet::BoundStateIndep, rad, ax); }, nParType, nRad); + break; + case cadet::model::MultiplexMode::RadialSection: + case cadet::model::MultiplexMode::Component: + case cadet::model::MultiplexMode::ComponentRadial: + case cadet::model::MultiplexMode::ComponentRadialSection: + case cadet::model::MultiplexMode::ComponentSection: + case cadet::model::MultiplexMode::Section: + case cadet::model::MultiplexMode::Type: + case cadet::model::MultiplexMode::ComponentType: + case cadet::model::MultiplexMode::ComponentSectionType: + cadet_assert(false); + break; + } + + return mode; +} + +bool multiplexParameterValue(const cadet::ParameterId& pId, cadet::StringHash nameHash, cadet::model::MultiplexMode mode, std::vector& data, unsigned int nAxial, unsigned int nRad, unsigned int nParType, double value, std::unordered_set const* sensParams) +{ + if (pId.name != nameHash) + return false; + + switch (mode) + { + case cadet::model::MultiplexMode::Independent: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction != cadet::ReactionIndep) || (pId.section != cadet::SectionIndep)) + return false; + + if (sensParams && !cadet::contains(*sensParams, &data[pId.particleType])) + return false; + + for (unsigned int i = 0; i < nAxial * nRad; ++i) + data[i * nParType + pId.particleType].setValue(value); + + return true; + } + case cadet::model::MultiplexMode::Radial: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction == cadet::ReactionIndep) || (pId.section != cadet::SectionIndep)) + return false; + + if (sensParams && !cadet::contains(*sensParams, &data[pId.reaction * nParType + pId.particleType])) + return false; + + for (unsigned int i = 0; i < nAxial; ++i) + data[i * nRad * nParType + pId.reaction * nParType + pId.particleType].setValue(value); + + return true; + } + case cadet::model::MultiplexMode::Axial: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction != cadet::ReactionIndep) || (pId.section == cadet::SectionIndep)) + return false; + + if (sensParams && !cadet::contains(*sensParams, &data[pId.section * nParType * nRad + pId.particleType])) + return false; + + for (unsigned int i = 0; i < nRad; ++i) + data[pId.section * nParType * nRad + i * nParType + pId.particleType].setValue(value); + + return true; + } + case cadet::model::MultiplexMode::AxialRadial: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction == cadet::ReactionIndep) || (pId.section == cadet::SectionIndep)) + return false; + + if (sensParams && !cadet::contains(*sensParams, &data[pId.section * nParType * nRad + pId.reaction * nParType + pId.particleType])) + return false; + + data[pId.section * nParType * nRad + pId.reaction * nParType + pId.particleType].setValue(value); + + return true; + } + case cadet::model::MultiplexMode::RadialSection: + case cadet::model::MultiplexMode::Component: + case cadet::model::MultiplexMode::ComponentRadial: + case cadet::model::MultiplexMode::ComponentRadialSection: + case cadet::model::MultiplexMode::ComponentSection: + case cadet::model::MultiplexMode::Section: + case cadet::model::MultiplexMode::Type: + case cadet::model::MultiplexMode::ComponentType: + case cadet::model::MultiplexMode::ComponentSectionType: + cadet_assert(false); + break; + } + + return false; +} + +bool multiplexParameterAD(const cadet::ParameterId& pId, cadet::StringHash nameHash, cadet::model::MultiplexMode mode, std::vector& data, unsigned int nAxial, unsigned int nRad, unsigned int nParType, unsigned int adDirection, double adValue, std::unordered_set& sensParams) +{ + if (pId.name != nameHash) + return false; + + switch (mode) + { + case cadet::model::MultiplexMode::Independent: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction != cadet::ReactionIndep) || (pId.section != cadet::SectionIndep)) + return false; + + sensParams.insert(&data[pId.particleType]); + + for (unsigned int i = 0; i < nAxial * nRad; ++i) + data[i * nParType + pId.particleType].setADValue(adDirection, adValue); + + return true; + } + case cadet::model::MultiplexMode::Radial: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction == cadet::ReactionIndep) || (pId.section != cadet::SectionIndep)) + return false; + + sensParams.insert(&data[pId.reaction * nParType + pId.particleType]); + + for (unsigned int i = 0; i < nAxial; ++i) + data[i * nRad * nParType + pId.reaction * nParType + pId.particleType].setADValue(adDirection, adValue); + + return true; + } + case cadet::model::MultiplexMode::Axial: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction != cadet::ReactionIndep) || (pId.section == cadet::SectionIndep)) + return false; + + sensParams.insert(&data[pId.section * nParType * nRad + pId.particleType]); + + for (unsigned int i = 0; i < nRad; ++i) + data[pId.section * nParType * nRad + i * nParType + pId.particleType].setADValue(adDirection, adValue); + + return true; + } + case cadet::model::MultiplexMode::AxialRadial: + { + if ((pId.component != cadet::CompIndep) || (pId.particleType == cadet::ParTypeIndep) || (pId.boundState != cadet::BoundStateIndep) + || (pId.reaction == cadet::ReactionIndep) || (pId.section == cadet::SectionIndep)) + return false; + + sensParams.insert(&data[pId.section * nParType * nRad + pId.reaction * nParType + pId.particleType]); + data[pId.section * nParType * nRad + pId.reaction * nParType + pId.particleType].setADValue(adDirection, adValue); + + return true; + } + case cadet::model::MultiplexMode::RadialSection: + case cadet::model::MultiplexMode::Component: + case cadet::model::MultiplexMode::ComponentRadial: + case cadet::model::MultiplexMode::ComponentRadialSection: + case cadet::model::MultiplexMode::ComponentSection: + case cadet::model::MultiplexMode::Section: + case cadet::model::MultiplexMode::Type: + case cadet::model::MultiplexMode::ComponentType: + case cadet::model::MultiplexMode::ComponentSectionType: + cadet_assert(false); + break; + } + + return false; +} + + +} // namespace + + +namespace cadet +{ + +namespace model +{ + +constexpr double SurfVolRatioSphere = 3.0; +constexpr double SurfVolRatioCylinder = 2.0; +constexpr double SurfVolRatioSlab = 1.0; + +LumpedRateModelWithPoresDG2D::LumpedRateModelWithPoresDG2D(UnitOpIdx unitOpIdx) : UnitOperationBase(unitOpIdx), + _dynReactionBulk(nullptr), _jacInlet(), _analyticJac(true), _jacobianAdDirs(0), _factorizeJacobian(false), _tempState(nullptr), + _initC(0), _singleRadiusInitC(true), _initCp(0), _singleRadiusInitCp(true), _initQ(0), _singleRadiusInitQ(true), _initState(0), _initStateDot(0) +{ +} + +LumpedRateModelWithPoresDG2D::~LumpedRateModelWithPoresDG2D() CADET_NOEXCEPT +{ + delete[] _tempState; + + delete _dynReactionBulk; + + delete[] _disc.parTypeOffset; + delete[] _disc.nBound; + delete[] _disc.boundOffset; + delete[] _disc.strideBound; + delete[] _disc.nBoundBeforeType; +} + +unsigned int LumpedRateModelWithPoresDG2D::numDofs() const CADET_NOEXCEPT +{ + // Column bulk DOFs: axNPoints * radNPoints * nComp + // Particle DOFs: axNPoints * radNPoints * nParType particles each having nComp (liquid phase) + sum boundStates (solid phase) DOFs + // Inlet DOFs: nComp * nRad + return _disc.axNPoints * _disc.radNPoints * _disc.nComp + _disc.parTypeOffset[_disc.nParType] + _disc.nComp * _disc.radNPoints; +} + +unsigned int LumpedRateModelWithPoresDG2D::numPureDofs() const CADET_NOEXCEPT +{ + // Column bulk DOFs: axNPoints * radNPoints * nComp + // Particle DOFs: axNPoints * radNPoints * nParType particles each having nComp (liquid phase) + sum boundStates (solid phase) DOFs + return _disc.axNPoints * _disc.radNPoints * _disc.nComp + _disc.parTypeOffset[_disc.nParType]; +} + + +bool LumpedRateModelWithPoresDG2D::usesAD() const CADET_NOEXCEPT +{ +#ifdef CADET_CHECK_ANALYTIC_JACOBIAN + // We always need AD if we want to check the analytical Jacobian + return true; +#else + // We only need AD if we are not computing the Jacobian analytically + return !_analyticJac; +#endif +} + +bool LumpedRateModelWithPoresDG2D::configureModelDiscretization(IParameterProvider& paramProvider, const IConfigHelper& helper) +{ + const bool firstConfig = _tempState == nullptr; // used to avoid multiply allocation + + // ==== Read discretization + _disc.nComp = paramProvider.getInt("NCOMP"); + + std::vector nBound; + nBound = paramProvider.getIntArray("NBOUND"); + if (nBound.size() < _disc.nComp) + throw InvalidParameterException("Field NBOUND contains too few elements (NCOMP = " + std::to_string(_disc.nComp) + " required)"); + + if (nBound.size() % _disc.nComp != 0) + throw InvalidParameterException("Field NBOUND must have a size divisible by NCOMP (" + std::to_string(_disc.nComp) + ")"); + + if (paramProvider.exists("NPARTYPE")) + { + _disc.nParType = paramProvider.getInt("NPARTYPE"); + if (firstConfig) + _disc.nBound = new unsigned int[_disc.nComp * _disc.nParType]; + if (nBound.size() < _disc.nComp * _disc.nParType) + { + // Multiplex number of bound states to all particle types + for (unsigned int i = 0; i < _disc.nParType; ++i) + std::copy_n(nBound.begin(), _disc.nComp, _disc.nBound + i * _disc.nComp); + } + else + std::copy_n(nBound.begin(), _disc.nComp * _disc.nParType, _disc.nBound); + } + else + { + // Infer number of particle types + _disc.nParType = nBound.size() / _disc.nComp; + if (firstConfig) + _disc.nBound = new unsigned int[_disc.nComp * _disc.nParType]; + std::copy_n(nBound.begin(), _disc.nComp * _disc.nParType, _disc.nBound); + } + + // Precompute offsets and total number of bound states (DOFs in solid phase) + const unsigned int nTotalBound = std::accumulate(_disc.nBound, _disc.nBound + _disc.nComp * _disc.nParType, 0u); + + // Precompute offsets and total number of bound states (DOFs in solid phase) + if (firstConfig) + { + _disc.boundOffset = new unsigned int[_disc.nComp * _disc.nParType]; + _disc.strideBound = new unsigned int[_disc.nParType + 1]; + _disc.nBoundBeforeType = new unsigned int[_disc.nParType]; + } + _disc.strideBound[_disc.nParType] = nTotalBound; + _disc.nBoundBeforeType[0] = 0; + for (unsigned int j = 0; j < _disc.nParType; ++j) + { + unsigned int* const ptrOffset = _disc.boundOffset + j * _disc.nComp; + unsigned int* const ptrBound = _disc.nBound + j * _disc.nComp; + + ptrOffset[0] = 0; + for (unsigned int i = 1; i < _disc.nComp; ++i) + { + ptrOffset[i] = ptrOffset[i - 1] + ptrBound[i - 1]; + } + _disc.strideBound[j] = ptrOffset[_disc.nComp - 1] + ptrBound[_disc.nComp - 1]; + + if (j != _disc.nParType - 1) + _disc.nBoundBeforeType[j + 1] = _disc.nBoundBeforeType[j] + _disc.strideBound[j]; + } + + paramProvider.pushScope("discretization"); + + // Determine whether analytic Jacobian should be used but don't set it right now. + // We need to setup Jacobian matrices first. +#ifndef CADET_CHECK_ANALYTIC_JACOBIAN + const bool analyticJac = paramProvider.getBool("USE_ANALYTIC_JACOBIAN"); +#else + const bool analyticJac = false; +#endif + + // Create nonlinear solver for consistent initialization + configureNonlinearSolver(paramProvider); + + paramProvider.popScope(); + + const unsigned int strideRadNode = _disc.nComp; + const bool transportSuccess = _convDispOp.configureModelDiscretization(paramProvider, helper, _disc.nComp, strideRadNode); + + _disc.axNPoints = _convDispOp.axNPoints(); + _disc.radNPoints = _convDispOp.radNPoints(); + _disc.nBulkPoints = _disc.axNPoints * _disc.radNPoints; + + // Precompute offsets of particle type DOFs + if (firstConfig) + _disc.parTypeOffset = new unsigned int[_disc.nParType + 1]; + _disc.parTypeOffset[0] = 0; + for (unsigned int j = 1; j < _disc.nParType + 1; ++j) + _disc.parTypeOffset[j] = _disc.parTypeOffset[j - 1] + (_disc.nComp + _disc.strideBound[j - 1]) * _disc.nBulkPoints; + + // Allocate memory + Indexer idxr(_disc); + + _initC.resize(_disc.nComp * _disc.radNPoints); + _initCp.resize(_disc.nComp * _disc.radNPoints * _disc.nParType); + _initQ.resize(nTotalBound * _disc.radNPoints); + + // Set whether analytic Jacobian is used + useAnalyticJacobian(analyticJac); + + // ==== Construct and configure binding model + + clearBindingModels(); + _binding = std::vector(_disc.nParType, nullptr); + + std::vector bindModelNames = { "NONE" }; + if (paramProvider.exists("ADSORPTION_MODEL")) + bindModelNames = paramProvider.getStringArray("ADSORPTION_MODEL"); + + if (paramProvider.exists("ADSORPTION_MODEL_MULTIPLEX")) + _singleBinding = (paramProvider.getInt("ADSORPTION_MODEL_MULTIPLEX") == 1); + else + { + // Infer multiplex mode + _singleBinding = (bindModelNames.size() == 1); + } + + if (!_singleBinding && (bindModelNames.size() < _disc.nParType)) + throw InvalidParameterException("Field ADSORPTION_MODEL contains too few elements (" + std::to_string(_disc.nParType) + " required)"); + else if (_singleBinding && (bindModelNames.size() != 1)) + throw InvalidParameterException("Field ADSORPTION_MODEL requires (only) 1 element"); + + bool bindingConfSuccess = true; + for (unsigned int i = 0; i < _disc.nParType; ++i) + { + if (_singleBinding && (i > 0)) + { + // Reuse first binding model + _binding[i] = _binding[0]; + } + else + { + _binding[i] = helper.createBindingModel(bindModelNames[i]); + if (!_binding[i]) + throw InvalidParameterException("Unknown binding model " + bindModelNames[i]); + + MultiplexedScopeSelector scopeGuard(paramProvider, "adsorption", _singleBinding, i, _disc.nParType == 1, _binding[i]->usesParamProviderInDiscretizationConfig()); + bindingConfSuccess = _binding[i]->configureModelDiscretization(paramProvider, _disc.nComp, _disc.nBound + i * _disc.nComp, _disc.boundOffset + i * _disc.nComp) && bindingConfSuccess; + } + } + + // ==== Construct and configure dynamic reaction model + bool reactionConfSuccess = true; + + _dynReactionBulk = nullptr; + if (paramProvider.exists("REACTION_MODEL")) + { + const std::string dynReactName = paramProvider.getString("REACTION_MODEL"); + _dynReactionBulk = helper.createDynamicReactionModel(dynReactName); + if (!_dynReactionBulk) + throw InvalidParameterException("Unknown dynamic reaction model " + dynReactName); + + if (_dynReactionBulk->usesParamProviderInDiscretizationConfig()) + paramProvider.pushScope("reaction_bulk"); + + reactionConfSuccess = _dynReactionBulk->configureModelDiscretization(paramProvider, _disc.nComp, nullptr, nullptr); + + if (_dynReactionBulk->usesParamProviderInDiscretizationConfig()) + paramProvider.popScope(); + } + + clearDynamicReactionModels(); + _dynReaction = std::vector(_disc.nParType, nullptr); + + if (paramProvider.exists("REACTION_MODEL_PARTICLES")) + { + const std::vector dynReactModelNames = paramProvider.getStringArray("REACTION_MODEL_PARTICLES"); + + if (paramProvider.exists("REACTION_MODEL_PARTICLES_MULTIPLEX")) + _singleDynReaction = (paramProvider.getInt("REACTION_MODEL_PARTICLES_MULTIPLEX") == 1); + else + { + // Infer multiplex mode + _singleDynReaction = (dynReactModelNames.size() == 1); + } + + if (!_singleDynReaction && (dynReactModelNames.size() < _disc.nParType)) + throw InvalidParameterException("Field REACTION_MODEL_PARTICLES contains too few elements (" + std::to_string(_disc.nParType) + " required)"); + else if (_singleDynReaction && (dynReactModelNames.size() != 1)) + throw InvalidParameterException("Field REACTION_MODEL_PARTICLES requires (only) 1 element"); + + for (unsigned int i = 0; i < _disc.nParType; ++i) + { + if (_singleDynReaction && (i > 0)) + { + // Reuse first binding model + _dynReaction[i] = _dynReaction[0]; + } + else + { + _dynReaction[i] = helper.createDynamicReactionModel(dynReactModelNames[i]); + if (!_dynReaction[i]) + throw InvalidParameterException("Unknown dynamic reaction model " + dynReactModelNames[i]); + + MultiplexedScopeSelector scopeGuard(paramProvider, "reaction_particle", _singleDynReaction, i, _disc.nParType == 1, _dynReaction[i]->usesParamProviderInDiscretizationConfig()); + reactionConfSuccess = _dynReaction[i]->configureModelDiscretization(paramProvider, _disc.nComp, _disc.nBound + i * _disc.nComp, _disc.boundOffset + i * _disc.nComp) && reactionConfSuccess; + } + } + } + + // Setup the memory for tempState and Jacobians + if (firstConfig) + { + _tempState = new double[numDofs()]; + _jacInlet.resize(_convDispOp.axNNodes() * _disc.radNPoints * _disc.nComp, _disc.radNPoints * _disc.nComp); + // Allocate Jacobian memory; pattern will be set and analyzed in configure() + _globalJac.resize(numPureDofs(), numPureDofs()); + _globalJacDisc.resize(numPureDofs(), numPureDofs()); + } + + _jacInlet.setZero(); + + return transportSuccess && bindingConfSuccess && reactionConfSuccess; +} + +bool LumpedRateModelWithPoresDG2D::configure(IParameterProvider& paramProvider) +{ + _parameters.clear(); + + const bool transportSuccess = _convDispOp.configure(_unitOpIdx, paramProvider, _parameters); + + // Read column geometry parameters + _singleParRadius = readAndRegisterMultiplexTypeParam(paramProvider, _parameters, _parRadius, "PAR_RADIUS", _disc.nParType, _unitOpIdx); + _singleParPorosity = readAndRegisterMultiplexTypeParam(paramProvider, _parameters, _parPorosity, "PAR_POROSITY", _disc.nParType, _unitOpIdx); + + // Read particle geometry and default to "SPHERICAL" + _parGeomSurfToVol = std::vector(_disc.nParType, SurfVolRatioSphere); + if (paramProvider.exists("PAR_GEOM")) + { + std::vector pg = paramProvider.getStringArray("PAR_GEOM"); + if ((pg.size() == 1) && (_disc.nParType > 1)) + { + // Multiplex using first value + pg.resize(_disc.nParType, pg[0]); + } + else if (pg.size() < _disc.nParType) + throw InvalidParameterException("Field PAR_GEOM contains too few elements (" + std::to_string(_disc.nParType) + " required)"); + + for (unsigned int i = 0; i < _disc.nParType; ++i) + { + if (pg[i] == "SPHERE") + _parGeomSurfToVol[i] = SurfVolRatioSphere; + else if (pg[i] == "CYLINDER") + _parGeomSurfToVol[i] = SurfVolRatioCylinder; + else if (pg[i] == "SLAB") + _parGeomSurfToVol[i] = SurfVolRatioSlab; + else + throw InvalidParameterException("Unknown particle geometry type \"" + pg[i] + "\" at index " + std::to_string(i) + " of field PAR_GEOM"); + } + } + + + // Check whether PAR_TYPE_VOLFRAC is required or not + if ((_disc.nParType > 1) && !paramProvider.exists("PAR_TYPE_VOLFRAC")) + throw InvalidParameterException("The required parameter \"PAR_TYPE_VOLFRAC\" was not found"); + + // Let PAR_TYPE_VOLFRAC default to 1.0 for backwards compatibility + if (paramProvider.exists("PAR_TYPE_VOLFRAC")) + _parTypeVolFracMode = readAndRegisterMultiplexParam(paramProvider, _parameters, _parTypeVolFrac, "PAR_TYPE_VOLFRAC", _disc.axNPoints, _disc.radNPoints, _disc.nParType, _unitOpIdx); + else + { + // Only one particle type present + _parTypeVolFrac.resize(_disc.axNPoints * _disc.radNPoints, 1.0); + _parTypeVolFracMode = MultiplexMode::Independent; + } + + // Check whether all sizes are matched + // Check whether all sizes are matched + if (_disc.nParType != _parRadius.size()) + throw InvalidParameterException("Number of elements in field PAR_RADIUS does not match number of particle types"); + if (_disc.nParType * _disc.nBulkPoints != _parTypeVolFrac.size()) + throw InvalidParameterException("Number of elements in field PAR_TYPE_VOLFRAC does not match number of particle types"); + if (_disc.nParType != _parPorosity.size()) + throw InvalidParameterException("Number of elements in field PAR_POROSITY does not match number of particle types"); + + // Check that particle volume fractions sum to 1.0 + for (unsigned int i = 0; i < _disc.axNPoints * _disc.radNPoints; ++i) + { + const double volFracSum = std::accumulate(_parTypeVolFrac.begin() + i * _disc.nParType, _parTypeVolFrac.begin() + (i+1) * _disc.nParType, 0.0, + [](double a, const active& b) -> double { return a + static_cast(b); }); + if (std::abs(1.0 - volFracSum) > 1e-10) + throw InvalidParameterException("Sum of field PAR_TYPE_VOLFRAC differs from 1.0 (is " + std::to_string(volFracSum) + ") in axial cell " + std::to_string(i / _disc.radNPoints) + " radial cell " + std::to_string(i % _disc.radNPoints)); + } + + // Read vectorial parameters (which may also be section dependent; transport) + _filmDiffusionMode = readAndRegisterMultiplexCompTypeSecParam(paramProvider, _parameters, _filmDiffusion, "FILM_DIFFUSION", _disc.nParType, _disc.nComp, _unitOpIdx); + + if ((_filmDiffusion.size() < _disc.nComp * _disc.nParType) || (_filmDiffusion.size() % (_disc.nComp * _disc.nParType) != 0)) + throw InvalidParameterException("Number of elements in field FILM_DIFFUSION is not a positive multiple of NCOMP * NPARTYPE (" + std::to_string(_disc.nComp * _disc.nParType) + ")"); + + if (paramProvider.exists("PORE_ACCESSIBILITY")) + _poreAccessFactorMode = readAndRegisterMultiplexCompTypeSecParam(paramProvider, _parameters, _poreAccessFactor, "PORE_ACCESSIBILITY", _disc.nParType, _disc.nComp, _unitOpIdx); + else + { + _poreAccessFactorMode = MultiplexMode::ComponentType; + _poreAccessFactor = std::vector(_disc.nComp * _disc.nParType, 1.0); + } + + if (_disc.nComp * _disc.nParType != _poreAccessFactor.size()) + throw InvalidParameterException("Number of elements in field PORE_ACCESSIBILITY differs from NCOMP * NPARTYPE (" + std::to_string(_disc.nComp * _disc.nParType) + ")"); + + // Register initial conditions parameters + registerParam1DArray(_parameters, _initC, [=](bool multi, unsigned int comp) { return makeParamId(hashString("INIT_C"), _unitOpIdx, comp, ParTypeIndep, BoundStateIndep, ReactionIndep, SectionIndep); }); + if (_disc.radNPoints > 1) + registerParam2DArray(_parameters, _initC, [=](bool multi, unsigned int rad, unsigned int comp) { return makeParamId(hashString("INIT_C"), _unitOpIdx, comp, ParTypeIndep, BoundStateIndep, rad, SectionIndep); }, _disc.nComp); + + if (_singleBinding) + { + for (unsigned int c = 0; c < _disc.nComp; ++c) + _parameters[makeParamId(hashString("INIT_CP"), _unitOpIdx, c, ParTypeIndep, BoundStateIndep, ReactionIndep, SectionIndep)] = &_initCp[c]; + + if (_disc.radNPoints > 1) + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int c = 0; c < _disc.nComp; ++c) + _parameters[makeParamId(hashString("INIT_CP"), _unitOpIdx, c, ParTypeIndep, BoundStateIndep, r, SectionIndep)] = &_initCp[r * _disc.nComp * _disc.nParType + c]; + } + } + } + else + { + registerParam2DArray(_parameters, _initCp, [=](bool multi, unsigned int type, unsigned int comp) { return makeParamId(hashString("INIT_CP"), _unitOpIdx, comp, type, BoundStateIndep, ReactionIndep, SectionIndep); }, _disc.nComp); + if (_disc.radNPoints > 1) + registerParam3DArray(_parameters, _initCp, [=](bool multi, unsigned int rad, unsigned int type, unsigned int comp) { return makeParamId(hashString("INIT_CP"), _unitOpIdx, comp, type, BoundStateIndep, rad, SectionIndep); }, _disc.nComp, _disc.nParType); + } + + if (!_binding.empty()) + { + const unsigned int maxBoundStates = *std::max_element(_disc.strideBound, _disc.strideBound + _disc.nParType); + std::vector initParams(maxBoundStates); + + // Register radially independent + if (_singleBinding) + { + _binding[0]->fillBoundPhaseInitialParameters(initParams.data(), _unitOpIdx, ParTypeIndep); + + active* const iq = _initQ.data() + _disc.nBoundBeforeType[0]; + for (unsigned int i = 0; i < _disc.strideBound[0]; ++i) + _parameters[initParams[i]] = iq + i; + } + else + { + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + _binding[type]->fillBoundPhaseInitialParameters(initParams.data(), _unitOpIdx, type); + + active* const iq = _initQ.data() + _disc.nBoundBeforeType[type]; + for (unsigned int i = 0; i < _disc.strideBound[type]; ++i) + _parameters[initParams[i]] = iq + i; + } + } + + // Register radially dependent + if (_disc.radNPoints > 1) + { + if (_singleBinding) + { + _binding[0]->fillBoundPhaseInitialParameters(initParams.data(), _unitOpIdx, ParTypeIndep); + + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (ParameterId& pId : initParams) + pId.reaction = r; + + active* const iq = _initQ.data() + _disc.nBoundBeforeType[0] + r * _disc.strideBound[_disc.nParType]; + for (unsigned int i = 0; i < _disc.strideBound[0]; ++i) + _parameters[initParams[i]] = iq + i; + } + } + else + { + for (unsigned int r = 0; r < _disc.radNPoints; ++r) + { + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + _binding[type]->fillBoundPhaseInitialParameters(initParams.data(), _unitOpIdx, type); + for (ParameterId& pId : initParams) + pId.reaction = r; + + active* const iq = _initQ.data() + _disc.nBoundBeforeType[type] + r * _disc.strideBound[_disc.nParType]; + for (unsigned int i = 0; i < _disc.strideBound[type]; ++i) + _parameters[initParams[i]] = iq + i; + } + } + } + } + } + + // Reconfigure binding model + bool bindingConfSuccess = true; + if (!_binding.empty()) + { + if (_singleBinding) + { + if (_binding[0] && _binding[0]->requiresConfiguration()) + { + MultiplexedScopeSelector scopeGuard(paramProvider, "adsorption", true); + bindingConfSuccess = _binding[0]->configure(paramProvider, _unitOpIdx, ParTypeIndep); + } + } + else + { + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + if (!_binding[type] || !_binding[type]->requiresConfiguration()) + continue; + + // Check whether required = true and no isActive() check should be performed + MultiplexedScopeSelector scopeGuard(paramProvider, "adsorption", type, _disc.nParType == 1, false); + if (!scopeGuard.isActive()) + continue; + + bindingConfSuccess = _binding[type]->configure(paramProvider, _unitOpIdx, type) && bindingConfSuccess; + } + } + } + + // Reconfigure reaction model + bool dynReactionConfSuccess = true; + if (_dynReactionBulk && _dynReactionBulk->requiresConfiguration()) + { + paramProvider.pushScope("reaction_bulk"); + dynReactionConfSuccess = _dynReactionBulk->configure(paramProvider, _unitOpIdx, ParTypeIndep); + paramProvider.popScope(); + } + + if (_singleDynReaction) + { + if (_dynReaction[0] && _dynReaction[0]->requiresConfiguration()) + { + MultiplexedScopeSelector scopeGuard(paramProvider, "reaction_particle", true); + dynReactionConfSuccess = _dynReaction[0]->configure(paramProvider, _unitOpIdx, ParTypeIndep) && dynReactionConfSuccess; + } + } + else + { + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + if (!_dynReaction[type] || !_dynReaction[type]->requiresConfiguration()) + continue; + + MultiplexedScopeSelector scopeGuard(paramProvider, "reaction_particle", type, _disc.nParType == 1, true); + dynReactionConfSuccess = _dynReaction[type]->configure(paramProvider, _unitOpIdx, type) && dynReactionConfSuccess; + } + } + + setGlobalJacPattern(_globalJac); + _globalJacDisc = _globalJac; + + // the solver repetitively solves the linear system with a static pattern of the jacobian (set above). + // The goal of analyzePattern() is to reorder the nonzero elements of the matrix, such that the factorization step creates less fill-in + _globalSolver.analyzePattern(_globalJacDisc); + + + return transportSuccess && bindingConfSuccess && dynReactionConfSuccess; +} + + +unsigned int LumpedRateModelWithPoresDG2D::threadLocalMemorySize() const CADET_NOEXCEPT +{ + LinearMemorySizer lms; + + // Memory for residualImpl() + for (unsigned int i = 0; i < _disc.nParType; ++i) + { + if (_binding[i] && _binding[i]->requiresWorkspace()) + lms.fitBlock(_binding[i]->workspaceSize(_disc.nComp, _disc.strideBound[i], _disc.nBound + i * _disc.nComp)); + + if (_dynReaction[i] && _dynReaction[i]->requiresWorkspace()) + lms.fitBlock(_dynReaction[i]->workspaceSize(_disc.nComp, _disc.strideBound[i], _disc.nBound + i * _disc.nComp)); + } + + if (_dynReactionBulk && _dynReactionBulk->requiresWorkspace()) + lms.fitBlock(_dynReactionBulk->workspaceSize(_disc.nComp, 0, nullptr)); + + const unsigned int maxStrideBound = *std::max_element(_disc.strideBound, _disc.strideBound + _disc.nParType); + lms.add(_disc.nComp + maxStrideBound); + lms.add((maxStrideBound + _disc.nComp) * (maxStrideBound + _disc.nComp)); + + lms.commit(); + const std::size_t resImplSize = lms.bufferSize(); + + // Memory for consistentInitialState() + lms.add(_nonlinearSolver->workspaceSize(_disc.nComp + maxStrideBound) * sizeof(double)); + lms.add(_disc.nComp + maxStrideBound); + lms.add(_disc.nComp + maxStrideBound); + lms.add(_disc.nComp + maxStrideBound); + lms.add((_disc.nComp + maxStrideBound) * (_disc.nComp + maxStrideBound)); + lms.add(_disc.nComp); + + lms.addBlock(resImplSize); + lms.commit(); + + // Memory for consistentInitialSensitivity + lms.add(_disc.nComp + maxStrideBound); + lms.add(maxStrideBound); + lms.commit(); + + return lms.bufferSize(); +} + +unsigned int LumpedRateModelWithPoresDG2D::numAdDirsForJacobian() const CADET_NOEXCEPT +{ + // todo compressed/seeded AD + return numDofs(); +} + +void LumpedRateModelWithPoresDG2D::useAnalyticJacobian(const bool analyticJac) +{ +#ifndef CADET_CHECK_ANALYTIC_JACOBIAN + _analyticJac = analyticJac; + if (!_analyticJac) + _jacobianAdDirs = numAdDirsForJacobian(); + else + _jacobianAdDirs = 0; +#else + // If CADET_CHECK_ANALYTIC_JACOBIAN is active, we always enable AD for comparison and use it in simulation + _analyticJac = false; + _jacobianAdDirs = numAdDirsForJacobian(); +#endif +} + +void LumpedRateModelWithPoresDG2D::notifyDiscontinuousSectionTransition(double t, unsigned int secIdx, const ConstSimulationState& simState, const AdJacobianParams& adJac) +{ + Indexer idxr(_disc); + + _disc.newStaticJac = true; + + // ConvectionDispersionOperator tells us whether flow direction has changed + if (!_convDispOp.notifyDiscontinuousSectionTransition(t, secIdx)) + return; + + // todo backwards flow +} + +void LumpedRateModelWithPoresDG2D::setFlowRates(active const* in, active const* out) CADET_NOEXCEPT +{ + _convDispOp.setFlowRates(in, out); +} + +void LumpedRateModelWithPoresDG2D::reportSolution(ISolutionRecorder& recorder, double const* const solution) const +{ + Exporter expr(_disc, *this, solution); + recorder.beginUnitOperation(_unitOpIdx, *this, expr); + recorder.endUnitOperation(); +} + +void LumpedRateModelWithPoresDG2D::reportSolutionStructure(ISolutionRecorder& recorder) const +{ + Exporter expr(_disc, *this, nullptr); + recorder.unitOperationStructure(_unitOpIdx, *this, expr); +} + + +unsigned int LumpedRateModelWithPoresDG2D::requiredADdirs() const CADET_NOEXCEPT +{ +#ifndef CADET_CHECK_ANALYTIC_JACOBIAN + return _jacobianAdDirs; +#else + // If CADET_CHECK_ANALYTIC_JACOBIAN is active, we always need the AD directions for the Jacobian + return numAdDirsForJacobian(); +#endif +} + +void LumpedRateModelWithPoresDG2D::prepareADvectors(const AdJacobianParams& adJac) const +{ + // Early out if AD is disabled + if (!adJac.adY) + return; + + Indexer idxr(_disc); + + // todo improve AD seed vectors + + const int adDirOffset = adJac.adDirOffset; + active * adVec = adJac.adY; + + // Start with diagonal Jacobian element + for (int eq = 0; eq < numDofs(); ++eq) + { + // Clear previously set directions + adVec[eq].fillADValue(adDirOffset, 0.0); + adVec[eq].setADValue(adDirOffset + eq, 1.0); + } +} + +/** + * @brief Extracts the system Jacobian from AD seed vectors + * @param [in] adRes Residual vector of AD datatypes with AD seed vectors + * @param [in] adDirOffset Number of AD directions used for non-Jacobian purposes (e.g., parameter sensitivities) + */ +void LumpedRateModelWithPoresDG2D::extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset) +{ + Indexer idxr(_disc); + + active const* const adResUnit = adRes + adDirOffset + idxr.offsetC(); + + for (int i = 0; i < _convDispOp.axNNodes() * _disc.radNPoints * _disc.nComp; i++) + { + for (int j = 0; j < _disc.radNPoints * _disc.nComp; j++) + { + _jacInlet(i, j) = adResUnit[i].getADValue(j + adDirOffset); + } + } + + for (int i = 0; i < _globalJac.rows(); i++) + { + for (int j = 0; j < _globalJac.cols(); j++) + { + _globalJac.coeffRef(i, j) = adResUnit[i].getADValue(j + idxr.offsetC() + adDirOffset); + } + } +} + +#ifdef CADET_CHECK_ANALYTIC_JACOBIAN + +/** + * @brief Compares the analytical Jacobian with a Jacobian derived by AD + * @details The analytical Jacobian is assumed to be stored in the corresponding band matrices. + * @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 LumpedRateModelWithPoresDG2D::checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const +{ + // todo implement this function? + //Indexer idxr(_disc); + + //LOG(Debug) << "AD dir offset: " << adDirOffset << " DiagDirPar: " << _jacP[0].lowerBandwidth(); + + //// Particles + //double maxDiffPar = 0.0; + //for (unsigned int type = 0; type < _disc.nParType; ++type) + //{ + // for (unsigned int pblk = 0; pblk < _disc.axNPoints * _disc.radNPoints; ++pblk) + // { + // linalg::BandMatrix& jacMat = _jacP[_disc.axNPoints * _disc.radNPoints * type + pblk]; + // const double localDiff = ad::compareBandedJacobianWithAd(adRes + idxr.offsetCp(ParticleTypeIndex{type}, ParticleIndex{pblk}), adDirOffset, jacMat.lowerBandwidth(), jacMat); + // LOG(Debug) << "-> Par type " << type << " block " << pblk << " diff: " << localDiff; + // maxDiffPar = std::max(maxDiffPar, localDiff); + // } + //} +} + +#endif + +int LumpedRateModelWithPoresDG2D::jacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerResidual); + + _factorizeJacobian = true; + + if (_analyticJac) + return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); + else + return residualWithJacobian(simTime, ConstSimulationState{ simState.vecStateY, nullptr }, nullptr, adJac, threadLocalMem); +} + +int LumpedRateModelWithPoresDG2D::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerResidual); + + // Evaluate residual do not compute Jacobian or parameter sensitivities + return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); +} + +int LumpedRateModelWithPoresDG2D::residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerResidual); + + // Evaluate residual, use AD for Jacobian if required but do not evaluate parameter derivatives + return residual(simTime, simState, res, adJac, threadLocalMem, true, false); +} + +int LumpedRateModelWithPoresDG2D::residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, + const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem, bool updateJacobian, bool paramSensitivity) +{ + if (updateJacobian) + { + _factorizeJacobian = true; + +#ifndef CADET_CHECK_ANALYTIC_JACOBIAN + if (_analyticJac) + { + if (paramSensitivity) + { + const int retCode = residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, adJac.adRes, threadLocalMem); + + // Copy AD residuals to original residuals vector + if (res) + ad::copyFromAd(adJac.adRes, res, numDofs()); + + return retCode; + } + else + return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); + } + else + { + // Compute Jacobian via AD + + // Copy over state vector to AD state vector (without changing directional values to keep seed vectors) + // and initialize residuals with zero (also resetting directional values) + ad::copyToAd(simState.vecStateY, adJac.adY, numDofs()); + // @todo Check if this is necessary + ad::resetAd(adJac.adRes, numDofs()); + + // Evaluate with AD enabled + int retCode = 0; + if (paramSensitivity) + retCode = residualImpl(simTime.t, simTime.secIdx, adJac.adY, simState.vecStateYdot, adJac.adRes, threadLocalMem); + else + retCode = residualImpl(simTime.t, simTime.secIdx, adJac.adY, simState.vecStateYdot, adJac.adRes, threadLocalMem); + + // Copy AD residuals to original residuals vector + if (res) + ad::copyFromAd(adJac.adRes, res, numDofs()); + + // Extract Jacobian + extractJacobianFromAD(adJac.adRes, adJac.adDirOffset); + + return retCode; + } +#else + // Compute Jacobian via AD + + // Copy over state vector to AD state vector (without changing directional values to keep seed vectors) + // and initialize residuals with zero (also resetting directional values) + ad::copyToAd(simState.vecStateY, adJac.adY, numDofs()); + // @todo Check if this is necessary + ad::resetAd(adJac.adRes, numDofs()); + + // Evaluate with AD enabled + int retCode = 0; + if (paramSensitivity) + retCode = residualImpl(simTime.t, simTime.secIdx, adJac.adY, simState.vecStateYdot, adJac.adRes, threadLocalMem); + else + retCode = residualImpl(simTime.t, simTime.secIdx, adJac.adY, simState.vecStateYdot, adJac.adRes, threadLocalMem); + + // Only do comparison if we have a residuals vector (which is not always the case) + if (res) + { + // Evaluate with analytical Jacobian which is stored in the band matrices + retCode = residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); + + // Compare AD with anaytic Jacobian + checkAnalyticJacobianAgainstAd(adJac.adRes, adJac.adDirOffset); + } + + // Extract Jacobian + extractJacobianFromAD(adJac.adRes, adJac.adDirOffset); + + return retCode; +#endif + } + else + { + if (paramSensitivity) + { + // initialize residuals with zero + // @todo Check if this is necessary + ad::resetAd(adJac.adRes, numDofs()); + + const int retCode = residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, adJac.adRes, threadLocalMem); + + // Copy AD residuals to original residuals vector + if (res) + ad::copyFromAd(adJac.adRes, res, numDofs()); + + return retCode; + } + else + return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, res, threadLocalMem); + } +} + +template +int LumpedRateModelWithPoresDG2D::residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem) +{ + // reset Jacobian + if (_disc.newStaticJac) // also reset pattern + { + setGlobalJacPattern(_globalJac); + } + else if (wantJac) + { + std::fill_n(_globalJac.valuePtr(), _globalJac.nonZeros(), 0.0); + } + + for (unsigned int pblk = 0; pblk < _disc.axNPoints * _disc.radNPoints * _disc.nParType + 1; ++pblk) + { + if (cadet_unlikely(pblk == 0)) + { + if (wantJac || _disc.newStaticJac) + { + _convDispOp.assembleConvDispJacobian(_globalJac, _jacInlet); + _disc.newStaticJac = false; + } + + residualBulk(t, secIdx, y, yDot, res, threadLocalMem); + } + else + { + const unsigned int type = (pblk - 1) / (_disc.axNPoints * _disc.radNPoints); + const unsigned int par = (pblk - 1) % (_disc.axNPoints * _disc.radNPoints); + residualParticle(t, type, par, secIdx, y, yDot, res, threadLocalMem); + } + } CADET_PARFOR_END; + + residualFlux(t, secIdx, y, yDot, res); + + // Handle inlet DOFs, which are simply copied to res + for (unsigned int i = 0; i < _disc.nComp * _disc.radNPoints; ++i) + { + res[i] = y[i]; + } + + return 0; +} + +template +int LumpedRateModelWithPoresDG2D::residualBulk(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) +{ + if (wantRes) + _convDispOp.residual(*this, t, secIdx, yBase, yDotBase, resBase, wantJac, typename ParamSens::enabled()); + + if (!_dynReactionBulk || (_dynReactionBulk->numReactionsLiquid() == 0)) + return 0; + + // Get offsets + Indexer idxr(_disc); + StateType const* y = yBase + idxr.offsetC(); + ResidualType* res = resBase + idxr.offsetC(); + LinearBufferAllocator tlmAlloc = threadLocalMem.get(); + + for (unsigned int axNode = 0; axNode < _disc.axNPoints; ++axNode) + { + for (unsigned int radNode = 0; radNode < _disc.radNPoints; ++radNode, y += idxr.strideColRadialNode(), res += idxr.strideColRadialNode()) + { + const double r = _convDispOp.relativeRadialCoordinate(radNode); + const double z = _convDispOp.relativeAxialCoordinate(axNode);; + + const ColumnPosition colPos{ z, r, 0.0 }; + _dynReactionBulk->residualLiquidAdd(t, secIdx, colPos, y, res, -1.0, tlmAlloc); + + if (wantJac) + { + const int rowIdx = axNode * idxr.strideColAxialNode() + radNode * idxr.strideColRadialNode(); + linalg::BandedEigenSparseRowIterator jac(_globalJac, rowIdx); + + // static_cast should be sufficient here, but this statement is also analyzed when wantJac = false + _dynReactionBulk->analyticJacobianLiquidAdd(t, secIdx, colPos, reinterpret_cast(y), -1.0, jac, tlmAlloc); + } + } + } + + return 0; +} + +template +int LumpedRateModelWithPoresDG2D::residualParticle(double t, unsigned int parType, unsigned int colNode, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase, util::ThreadLocalStorage& threadLocalMem) +{ + Indexer idxr(_disc); + + // Go to the particle block of the given type and column cell + StateType const* y = yBase + idxr.offsetCp(ParticleTypeIndex{ parType }, ParticleIndex{ colNode }); + + // Prepare parameters + const ParamType radius = static_cast(_parRadius[parType]); + + const int axNodeIdx = std::floor(colNode / _disc.radNPoints); + const int radNodeIdx = colNode % _disc.radNPoints; + + const double z = _convDispOp.relativeAxialCoordinate(axNodeIdx); + const double r = _convDispOp.relativeRadialCoordinate(radNodeIdx); + + const parts::cell::CellParameters cellResParams + { + _disc.nComp, + _disc.nBound + _disc.nComp * parType, + _disc.boundOffset + _disc.nComp * parType, + _disc.strideBound[parType], + _binding[parType]->reactionQuasiStationarity(), + _parPorosity[parType], + _poreAccessFactor.data() + _disc.nComp * parType, + _binding[parType], + (_dynReaction[parType] && (_dynReaction[parType]->numReactionsCombined() > 0)) ? _dynReaction[parType] : nullptr + }; + + linalg::BandedEigenSparseRowIterator jac(_globalJac, idxr.offsetCp(ParticleTypeIndex{ parType }, ParticleIndex{ colNode }) - idxr.offsetC()); + + // Handle time derivatives, binding, dynamic reactions + if (wantRes) + { + double const* yDot = yDotBase ? yDotBase + idxr.offsetCp(ParticleTypeIndex{ parType }, ParticleIndex{ colNode }) : nullptr; + ResidualType* res = resBase + idxr.offsetCp(ParticleTypeIndex{ parType }, ParticleIndex{ colNode }); + + parts::cell::residualKernel( + t, secIdx, ColumnPosition{ z, r, static_cast(radius) * 0.5 }, y, yDot, res, + jac, cellResParams, threadLocalMem.get() + ); + } + else + { + parts::cell::residualKernel( + t, secIdx, ColumnPosition{ z, r, static_cast(radius) * 0.5 }, y, nullptr, nullptr, + jac, cellResParams, threadLocalMem.get() + ); + } + + return 0; +} + +template +int LumpedRateModelWithPoresDG2D::residualFlux(double t, unsigned int secIdx, StateType const* yBase, double const* yDotBase, ResidualType* resBase) +{ + Indexer idxr(_disc); + + ParamType invBetaC = 0.0; + ParamType jacCF_val = 0.0; + ParamType jacPF_val = 0.0; + + // Get offsets + ResidualType* const resCol = resBase + idxr.offsetC(); + StateType const* const yCol = yBase + idxr.offsetC(); + + for (unsigned int type = 0; type < _disc.nParType; ++type) + { + const ParamType epsP = static_cast(_parPorosity[type]); + const ParamType radius = static_cast(_parRadius[type]); + active const* const filmDiff = getSectionDependentSlice(_filmDiffusion, _disc.nComp * _disc.nParType, secIdx) + type * _disc.nComp; + active const* const poreAccFactor = _poreAccessFactor.data() + type * _disc.nComp; + + for (unsigned int j = 0; j < _disc.radNPoints; ++j) + { + invBetaC = 1.0 / static_cast(_convDispOp.columnPorosity(j)) - 1.0; + jacCF_val = invBetaC * _parGeomSurfToVol[type] / radius; + jacPF_val = -_parGeomSurfToVol[type] / (epsP * radius); + + for (unsigned int i = 0; i < _disc.axNPoints; ++i) + { + unsigned int colNodeIdx = i * _disc.radNPoints + j; + + for (unsigned int comp = 0; comp < _disc.nComp; comp++) + { + const unsigned int ClIdx = colNodeIdx * _disc.nComp + comp; + const unsigned int CpIdx = idxr.offsetCp(ParticleTypeIndex{ type }) - idxr.offsetC() + colNodeIdx * idxr.strideParBlock(type) + comp; + + if (wantRes) + { + // Add flux to column void / bulk volume equations + resCol[ClIdx] += jacCF_val * static_cast(filmDiff[comp]) * static_cast(_parTypeVolFrac[type + i * _disc.nParType * _disc.radNPoints + j * _disc.nParType]) * (yCol[ClIdx] - yCol[CpIdx]); + + // Add flux to particle / bead volume equations + resCol[CpIdx] += jacPF_val / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp]) * (yCol[ClIdx] - yCol[CpIdx]); + } + + if (wantJac) + { + // add Cl on Cl entries (added since entries already set in transport jacobian) + // row: already at current bulk node and component + // col: already at current bulk node and component + _globalJac.coeffRef(ClIdx, ClIdx) += static_cast(jacCF_val) * static_cast(filmDiff[comp]) * static_cast(_parTypeVolFrac[type + i * _disc.nParType * _disc.radNPoints + j * _disc.nParType]); + // add Cl on Cp entries + // row: already at current bulk node and component + // col: jump to particle phase + _globalJac.coeffRef(ClIdx, CpIdx) = -static_cast(jacCF_val) * static_cast(filmDiff[comp]) * static_cast(_parTypeVolFrac[type + i * _disc.nParType * _disc.radNPoints + j * _disc.nParType]); + + // add Cp on Cp entries (added since entries already set in particle jacobian) + // row: already at particle. already at current node and liquid state + // col: already at particle. already at current node and liquid state + _globalJac.coeffRef(CpIdx, CpIdx) += -static_cast(jacPF_val) / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp]); + // add Cp on Cl entries + // row: already at particle. already at current node and liquid state + // col: jump to bulk phase + _globalJac.coeffRef(CpIdx, ClIdx) = static_cast(jacPF_val) / static_cast(poreAccFactor[comp]) * static_cast(filmDiff[comp]); + } + } + } + } + } + + return 0; +} + +int LumpedRateModelWithPoresDG2D::residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerResidualSens); + + // Evaluate residual for all parameters using AD in vector mode and at the same time update the + // Jacobian (in one AD run, if analytic Jacobians are disabled) + return residual(simTime, simState, nullptr, adJac, threadLocalMem, true, true); +} + +int LumpedRateModelWithPoresDG2D::residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem) +{ + BENCH_SCOPE(_timerResidualSens); + + // Evaluate residual for all parameters using AD in vector mode + return residualImpl(simTime.t, simTime.secIdx, simState.vecStateY, simState.vecStateYdot, adRes, threadLocalMem); +} + +int LumpedRateModelWithPoresDG2D::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) +{ + BENCH_SCOPE(_timerResidualSens); + + // tmp1 stores result of (dF / dy) * s + // tmp2 stores result of (dF / dyDot) * sDot + + const SimulationTime cst{simTime.t, simTime.secIdx}; + const ConstSimulationState css{nullptr, nullptr}; + for (std::size_t param = 0; param < yS.size(); ++param) + { + + // Directional derivative (dF / dy) * s + multiplyWithJacobian(cst, css, yS[param], 1.0, 0.0, tmp1); + + // Directional derivative (dF / dyDot) * sDot + multiplyWithDerivativeJacobian(cst, css, ySdot[param], tmp2); + + double* const ptrResS = resS[param]; + + BENCH_START(_timerResidualSensPar); + + // Complete sens residual is the sum: + for (unsigned int i = 0; i < numDofs(); ++i) + { + ptrResS[i] = tmp1[i] + tmp2[i] + adRes[i].getADValue(param); + } CADET_PARFOR_END; + + BENCH_STOP(_timerResidualSensPar); + } + + return 0; +} + +/** + * @brief Multiplies the given vector with the system Jacobian (i.e., @f$ \frac{\partial F}{\partial y}\left(t, y, \dot{y}\right) @f$) + * @details Actually, the operation @f$ z = \alpha \frac{\partial F}{\partial y} x + \beta z @f$ is performed. + * + * Note that residual() or one of its cousins has to be called with the requested point @f$ (t, y, \dot{y}) @f$ once + * before calling multiplyWithJacobian() as this implementation ignores the given @f$ (t, y, \dot{y}) @f$. + * @param [in] simTime Current simulation time point + * @param [in] simState Simulation state vectors + * @param [in] yS Vector @f$ x @f$ that is transformed by the Jacobian @f$ \frac{\partial F}{\partial y} @f$ + * @param [in] alpha Factor @f$ \alpha @f$ in front of @f$ \frac{\partial F}{\partial y} @f$ + * @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 LumpedRateModelWithPoresDG2D::multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret) +{ + Indexer idxr(_disc); + + // Handle identity matrix of inlet DOFs + for (unsigned int i = 0; i < _disc.nComp * _disc.radNPoints; ++i) + { + ret[i] = alpha * yS[i] + beta * ret[i]; + } + + // Main Jacobian + Eigen::Map ret_vec(ret + idxr.offsetC(), numPureDofs()); + Eigen::Map yS_vec(yS + idxr.offsetC(), numPureDofs()); + ret_vec = alpha * _globalJac * yS_vec + beta * ret_vec; + + // Map inlet DOFs to the column inlet (first bulk nodes) + Eigen::Map> yInlet(yS + idxr.offsetC(), _disc.radNPoints * _disc.nComp, Eigen::InnerStride(idxr.strideColRadialNode())); + Eigen::Map> retInlet(ret + idxr.offsetC(), _convDispOp.axNNodes() * _disc.radNPoints * _disc.nComp, Eigen::InnerStride(idxr.strideColRadialNode())); + + retInlet += _jacInlet * yInlet; +} + +/** + * @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). + * @param [in] simTime Current simulation time point + * @param [in] simState Simulation state vectors + * @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 LumpedRateModelWithPoresDG2D::multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret) +{ + Indexer idxr(_disc); + std::fill_n(ret, numDofs(), 0.0); + + for (unsigned int idx = 0; idx < _disc.nBulkPoints * _disc.nParType + 1; ++idx) + { + if (cadet_unlikely(idx == 0)) + { + _convDispOp.multiplyWithDerivativeJacobian(simTime, sDot, ret); + } + else + { + const unsigned int idxParLoop = idx - 1; + const unsigned int pblk = idxParLoop % _disc.nBulkPoints; + const unsigned int type = idxParLoop / _disc.nBulkPoints; + + // Particle + double const* const localSdot = sDot + idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ pblk }); + double* const localRet = ret + idxr.offsetCp(ParticleTypeIndex{ type }, ParticleIndex{ pblk }); + + unsigned int const* const nBound = _disc.nBound + type * _disc.nComp; + unsigned int const* const boundOffset = _disc.boundOffset + type * _disc.nComp; + + // Mobile phase + for (unsigned int comp = 0; comp < _disc.nComp; ++comp) + { + // Add derivative with respect to dc_p / dt to Jacobian + localRet[comp] = localSdot[comp]; + + const double invBetaP = (1.0 - static_cast(_parPorosity[type])) / (static_cast(_poreAccessFactor[type * _disc.nComp + comp]) * static_cast(_parPorosity[type])); + + // Add derivative with respect to dq / dt to Jacobian (normal equations) + for (unsigned int i = 0; i < nBound[comp]; ++i) + { + // Index explanation: + // nComp -> skip mobile phase + // + boundOffset[comp] skip bound states of all previous components + // + i go to current bound state + localRet[comp] += invBetaP * localSdot[_disc.nComp + boundOffset[comp] + i]; + } + } + + // Solid phase + double const* const solidSdot = localSdot + _disc.nComp; + double* const solidRet = localRet + _disc.nComp; + int const* const qsReaction = _binding[type]->reactionQuasiStationarity(); + + for (unsigned int bnd = 0; bnd < _disc.strideBound[type]; ++bnd) + { + // Add derivative with respect to dynamic states to Jacobian + if (qsReaction[bnd]) + solidRet[bnd] = 0.0; + else + solidRet[bnd] = solidSdot[bnd]; + } + } + } CADET_PARFOR_END; + + // Handle inlet DOFs (all algebraic) + std::fill_n(ret, _disc.nComp, 0.0); +} + +void LumpedRateModelWithPoresDG2D::setExternalFunctions(IExternalFunction** extFuns, unsigned int size) +{ + for (IBindingModel* bm : _binding) + { + if (bm) + bm->setExternalFunctions(extFuns, size); + } +} + +unsigned int LumpedRateModelWithPoresDG2D::localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT +{ + // Inlets are duplicated so need to be accounted for + if (static_cast(_convDispOp.currentVelocity(port)) >= 0.0) + // Forward Flow: outlet is last cell + return _disc.nComp * _disc.radNPoints + (_disc.axNPoints - 1) * _disc.nComp * _disc.radNPoints + port * _disc.nComp; + else + // Backward flow: Outlet is first cell + return _disc.nComp * _disc.radNPoints + _disc.nComp * port; +} + +unsigned int LumpedRateModelWithPoresDG2D::localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT +{ + // Always 0 due to dedicated inlet DOFs + return _disc.nComp * port; +} + +unsigned int LumpedRateModelWithPoresDG2D::localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT +{ + return 1; +} + +unsigned int LumpedRateModelWithPoresDG2D::localInletComponentStride(unsigned int port) const CADET_NOEXCEPT +{ + return 1; +} + +void LumpedRateModelWithPoresDG2D::expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut) +{ + // @todo Write this function +} + +bool LumpedRateModelWithPoresDG2D::setParameter(const ParameterId& pId, double value) +{ + if (pId.unitOperation == _unitOpIdx) + { + if (multiplexParameterValue(pId, hashString("PAR_TYPE_VOLFRAC"), _parTypeVolFracMode, _parTypeVolFrac, _disc.axNPoints, _disc.radNPoints, _disc.nParType, value, nullptr)) + return true; + if (multiplexCompTypeSecParameterValue(pId, hashString("PORE_ACCESSIBILITY"), _poreAccessFactorMode, _poreAccessFactor, _disc.nParType, _disc.nComp, value, nullptr)) + return true; + if (multiplexCompTypeSecParameterValue(pId, hashString("FILM_DIFFUSION"), _filmDiffusionMode, _filmDiffusion, _disc.nParType, _disc.nComp, value, nullptr)) + return true; + const int mpIc = multiplexInitialConditions(pId, value, false); + if (mpIc > 0) + return true; + else if (mpIc < 0) + return false; + + if (multiplexTypeParameterValue(pId, hashString("PAR_RADIUS"), _singleParRadius, _parRadius, value, nullptr)) + return true; + if (multiplexTypeParameterValue(pId, hashString("PAR_POROSITY"), _singleParPorosity, _parPorosity, value, nullptr)) + return true; + + if (_convDispOp.setParameter(pId, value)) + return true; + } + + const bool result = UnitOperationBase::setParameter(pId, value); + + return result; +} + +void LumpedRateModelWithPoresDG2D::setSensitiveParameterValue(const ParameterId& pId, double value) +{ + if (pId.unitOperation == _unitOpIdx) + { + if (multiplexParameterValue(pId, hashString("PAR_TYPE_VOLFRAC"), _parTypeVolFracMode, _parTypeVolFrac, _disc.axNPoints, _disc.radNPoints, _disc.nParType, value, &_sensParams)) + return; + if (multiplexCompTypeSecParameterValue(pId, hashString("PORE_ACCESSIBILITY"), _poreAccessFactorMode, _poreAccessFactor, _disc.nParType, _disc.nComp, value, &_sensParams)) + return; + if (multiplexCompTypeSecParameterValue(pId, hashString("FILM_DIFFUSION"), _filmDiffusionMode, _filmDiffusion, _disc.nParType, _disc.nComp, value, &_sensParams)) + return; + if (multiplexInitialConditions(pId, value, true) != 0) + return; + + if (multiplexTypeParameterValue(pId, hashString("PAR_RADIUS"), _singleParRadius, _parRadius, value, &_sensParams)) + return; + if (multiplexTypeParameterValue(pId, hashString("PAR_POROSITY"), _singleParPorosity, _parPorosity, value, &_sensParams)) + return; + + if (_convDispOp.setSensitiveParameterValue(_sensParams, pId, value)) + return; + } + + UnitOperationBase::setSensitiveParameterValue(pId, value); +} + +bool LumpedRateModelWithPoresDG2D::setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue) +{ + if (pId.unitOperation == _unitOpIdx) + { + if (multiplexParameterAD(pId, hashString("PAR_TYPE_VOLFRAC"), _parTypeVolFracMode, _parTypeVolFrac, _disc.axNPoints, _disc.radNPoints, _disc.nParType, adDirection, adValue, _sensParams)) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + + if (multiplexCompTypeSecParameterAD(pId, hashString("PORE_ACCESSIBILITY"), _poreAccessFactorMode, _poreAccessFactor, _disc.nParType, _disc.nComp, adDirection, adValue, _sensParams)) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + + if (multiplexCompTypeSecParameterAD(pId, hashString("FILM_DIFFUSION"), _filmDiffusionMode, _filmDiffusion, _disc.nParType, _disc.nComp, adDirection, adValue, _sensParams)) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + + const int mpIc = multiplexInitialConditions(pId, adDirection, adValue); + if (mpIc > 0) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + else if (mpIc < 0) + return false; + + if (multiplexTypeParameterAD(pId, hashString("PAR_RADIUS"), _singleParRadius, _parRadius, adDirection, adValue, _sensParams)) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + + if (multiplexTypeParameterAD(pId, hashString("PAR_POROSITY"), _singleParPorosity, _parPorosity, adDirection, adValue, _sensParams)) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + + if (_convDispOp.setSensitiveParameter(_sensParams, pId, adDirection, adValue)) + { + LOG(Debug) << "Found parameter " << pId << ": Dir " << adDirection << " is set to " << adValue; + return true; + } + } + + const bool result = UnitOperationBase::setSensitiveParameter(pId, adDirection, adValue); + + return result; +} + + +int LumpedRateModelWithPoresDG2D::Exporter::writeMobilePhase(double* buffer) const +{ + const int blockSize = numMobilePhaseDofs(); + std::copy_n(_idx.c(_data), blockSize, buffer); + return blockSize; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeSolidPhase(double* buffer) const +{ + int numWritten = 0; + for (unsigned int i = 0; i < _disc.nParType; ++i) + { + const int n = writeSolidPhase(i, buffer); + buffer += n; + numWritten += n; + } + return numWritten; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeParticleMobilePhase(double* buffer) const +{ + int numWritten = 0; + for (unsigned int i = 0; i < _disc.nParType; ++i) + { + const int n = writeParticleMobilePhase(i, buffer); + buffer += n; + numWritten += n; + } + return numWritten; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeSolidPhase(unsigned int parType, double* buffer) const +{ + cadet_assert(parType < _disc.nParType); + + const unsigned int stride = _disc.nComp + _disc.strideBound[parType]; + double const* ptr = _data + _idx.offsetCp(ParticleTypeIndex{parType}) + _disc.nComp; + for (unsigned int i = 0; i < _disc.axNPoints * _disc.radNPoints; ++i) + { + std::copy_n(ptr, _disc.strideBound[parType], buffer); + buffer += _disc.strideBound[parType]; + ptr += stride; + } + return _disc.nBulkPoints * _disc.strideBound[parType]; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeParticleMobilePhase(unsigned int parType, double* buffer) const +{ + cadet_assert(parType < _disc.nParType); + + const unsigned int stride = _disc.nComp + _disc.strideBound[parType]; + double const* ptr = _data + _idx.offsetCp(ParticleTypeIndex{parType}); + for (unsigned int i = 0; i < _disc.axNPoints * _disc.radNPoints; ++i) + { + std::copy_n(ptr, _disc.nComp, buffer); + buffer += _disc.nComp; + ptr += stride; + } + return _disc.nBulkPoints * _disc.nComp; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeInlet(unsigned int port, double* buffer) const +{ + cadet_assert(port < _disc.radNPoints); + std::copy_n(_data + port * _disc.nComp, _disc.nComp, buffer); + return _disc.nComp; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeInlet(double* buffer) const +{ + std::copy_n(_data, _disc.nComp * _disc.radNPoints, buffer); + return _disc.nComp * _disc.radNPoints; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeOutlet(unsigned int port, double* buffer) const +{ + cadet_assert(port < _disc.radNPoints); + + if (_model._convDispOp.currentVelocity(port) >= 0) + std::copy_n(&_idx.c(_data, _disc.axNPoints - 1, port, 0), _disc.nComp, buffer); + else + std::copy_n(&_idx.c(_data, 0, port, 0), _disc.nComp, buffer); + + return _disc.nComp; +} + +int LumpedRateModelWithPoresDG2D::Exporter::writeOutlet(double* buffer) const +{ + for (int i = 0; i < _disc.radNPoints; ++i) + { + writeOutlet(i, buffer); + buffer += _disc.nComp; + } + return _disc.nComp * _disc.radNPoints; +} + + +void registerLumpedRateModelWithPoresDG2D(std::unordered_map>& models) +{ + models[LumpedRateModelWithPoresDG2D::identifier()] = [](UnitOpIdx uoId, IParameterProvider&) { return new LumpedRateModelWithPoresDG2D(uoId); }; + models["LRMPDG2D"] = [](UnitOpIdx uoId, IParameterProvider&) { return new LumpedRateModelWithPoresDG2D(uoId); }; +} + +} // namespace model + +} // namespace cadet diff --git a/src/libcadet/model/LumpedRateModelWithPoresDG2D.hpp b/src/libcadet/model/LumpedRateModelWithPoresDG2D.hpp new file mode 100644 index 000000000..6775d3331 --- /dev/null +++ b/src/libcadet/model/LumpedRateModelWithPoresDG2D.hpp @@ -0,0 +1,504 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-present: The CADET-Core Authors +// Please see the AUTHORS.md file. +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the GNU Public License v3.0 (or, at +// your option, any later version) which accompanies this distribution, and +// is available at http://www.gnu.org/licenses/gpl.html +// ============================================================================= + +/** + * @file +* Defines the 2D LRMP model as a unit operation. + */ + +#ifndef LIBCADET_LUMPEDRATEMODELWITHPORESDG2D_HPP_ +#define LIBCADET_LUMPEDRATEMODELWITHPORESDG2D_HPP_ + +#include "model/UnitOperationBase.hpp" +#include "cadet/StrongTypes.hpp" +#include "cadet/SolutionExporter.hpp" +#include "model/parts/TwoDimensionalConvectionDispersionOperatorDG.hpp" +#include "linalg/BandedEigenSparseRowIterator.hpp" +#include "AutoDiff.hpp" +#include "Memory.hpp" +#include "model/ModelUtils.hpp" +#include "model/ParameterMultiplexing.hpp" + +#include +#include + +#include "Benchmark.hpp" + +#include +#include + +namespace cadet +{ + +namespace model +{ + +class IDynamicReactionModel; + +/** + * @brief Lumped rate model of liquid column chromatography with pores and 2D bulk volume (radially symmetric), spatially discretized by a DG scheme + * @details For the 2D flow equations, refer to the 2D General rate model @cite Guiochon2006, @cite Gu1995, @cite Felinger2004 + * + * @f[\begin{align} + \frac{\partial c_i^b}{\partial t} &= - u \frac{\partial c_i^b}{\partial z} + \frac{\partial}{\partial z} \left(D_{\text{ax},i} \frac{\partial c_i^b}{\partial z} \right) + \frac{1}{\rho} \frac{\partial}{\partial \rho} \left( \rho D_{\text{rad},i} \frac{\partial c_i^b}{\partial \rho} \right) + - \frac{1 - \varepsilon_c}{\varepsilon_c} \frac{3 k_{f,i}}{r_p} j_{f,i} \\ + \frac{\partial c_{p,i}}{\partial t} + \frac{1 - \varepsilon_p}{\varepsilon_p} \frac{\partial q_{i}}{\partial t} &= \frac{3 k_{f,i}}{\varepsilon_p r_p} (c_i - c_{p,i}) \\ + a \frac{\partial q_i}{\partial t} &= f_{\text{iso}}(c_p, q) + \end{align} @f] + * Danckwerts boundary conditions (see @cite Danckwerts1953) +@f[ \begin{align} +u c_{\text{in},i}(t) &= u c_i^b(t,0,.) - D_{\text{ax},i} \frac{\partial c_i^b}{\partial z}(t,0,.) \\ +\frac{\partial c_i^b}{\partial z}(t,L,.) &= 0 \\ +\frac{\partial c_i^b}{\partial \rho}(t,.,0) &= 0 \\ +\frac{\partial c_i^b}{\partial \rho}(t,.,R_c) &= 0 \\ +\end{align} @f] + * Methods are described in @cite @TODO (2D DG paper), @cite Puttmann2013 @cite Puttmann2016 (forward sensitivities, AD, band compression) + */ +class LumpedRateModelWithPoresDG2D : public UnitOperationBase +{ +public: + + LumpedRateModelWithPoresDG2D(UnitOpIdx unitOpIdx); + virtual ~LumpedRateModelWithPoresDG2D() CADET_NOEXCEPT; + + virtual unsigned int numDofs() const CADET_NOEXCEPT; + virtual unsigned int numPureDofs() const CADET_NOEXCEPT; + virtual bool usesAD() const CADET_NOEXCEPT; + virtual unsigned int requiredADdirs() const CADET_NOEXCEPT; + + virtual UnitOpIdx unitOperationId() const CADET_NOEXCEPT { return _unitOpIdx; } + virtual unsigned int numComponents() const CADET_NOEXCEPT { return _disc.nComp; } + virtual void setFlowRates(active const* in, active const* out) CADET_NOEXCEPT; + virtual unsigned int numInletPorts() const CADET_NOEXCEPT { return _disc.radNPoints; } + virtual unsigned int numOutletPorts() const CADET_NOEXCEPT { return _disc.radNPoints; } + virtual bool canAccumulate() const CADET_NOEXCEPT { return false; } + + static const char* identifier() { return "LUMPED_RATE_MODEL_WITH_PORES_2D"; } + virtual const char* unitOperationName() const CADET_NOEXCEPT { return identifier(); } + + 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 void useAnalyticJacobian(const bool analyticJac); + + virtual void reportSolution(ISolutionRecorder& recorder, double const* const solution) const; + virtual void reportSolutionStructure(ISolutionRecorder& recorder) const; + + virtual int jacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem); + + virtual int residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, util::ThreadLocalStorage& threadLocalMem); + + virtual int residualWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem); + virtual int residualSensFwdAdOnly(const SimulationTime& simTime, const ConstSimulationState& simState, active* const adRes, util::ThreadLocalStorage& threadLocalMem); + virtual int residualSensFwdWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem); + + virtual int 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); + + virtual int linearSolve(double t, double alpha, double tol, double* const rhs, double const* const weight, + const ConstSimulationState& simState); + + virtual void prepareADvectors(const AdJacobianParams& adJac) const; + + virtual void applyInitialCondition(const SimulationState& simState) const; + virtual void readInitialCondition(IParameterProvider& paramProvider); + + virtual void consistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem); + virtual void consistentInitialTimeDerivative(const SimulationTime& simTime, double const* vecStateY, double* const vecStateYdot, util::ThreadLocalStorage& threadLocalMem); + + virtual void initializeSensitivityStates(const std::vector& vecSensY) const; + virtual void consistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, + std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem); + + virtual void leanConsistentInitialState(const SimulationTime& simTime, double* const vecStateY, const AdJacobianParams& adJac, double errorTol, util::ThreadLocalStorage& threadLocalMem); + virtual void leanConsistentInitialTimeDerivative(double t, double const* const vecStateY, double* const vecStateYdot, double* const res, util::ThreadLocalStorage& threadLocalMem); + + virtual void leanConsistentInitialSensitivity(const SimulationTime& simTime, const ConstSimulationState& simState, + std::vector& vecSensY, std::vector& vecSensYdot, active const* const adRes, util::ThreadLocalStorage& threadLocalMem); + + virtual bool hasInlet() const CADET_NOEXCEPT { return true; } + virtual bool hasOutlet() const CADET_NOEXCEPT { return true; } + + virtual unsigned int localOutletComponentIndex(unsigned int port) const CADET_NOEXCEPT; + virtual unsigned int localOutletComponentStride(unsigned int port) const CADET_NOEXCEPT; + virtual unsigned int localInletComponentIndex(unsigned int port) const CADET_NOEXCEPT; + virtual unsigned int localInletComponentStride(unsigned int port) const CADET_NOEXCEPT; + + virtual void setExternalFunctions(IExternalFunction** extFuns, unsigned int size); + virtual void setSectionTimes(double const* secTimes, bool const* secContinuity, unsigned int nSections) { } + + virtual void expandErrorTol(double const* errorSpec, unsigned int errorSpecSize, double* expandOut); + + virtual void multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double alpha, double beta, double* ret); + virtual void multiplyWithDerivativeJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* sDot, double* ret); + + inline void multiplyWithJacobian(const SimulationTime& simTime, const ConstSimulationState& simState, double const* yS, double* ret) + { + multiplyWithJacobian(simTime, simState, yS, 1.0, 0.0, ret); + } + + virtual bool setParameter(const ParameterId& pId, double value); + virtual bool setSensitiveParameter(const ParameterId& pId, unsigned int adDirection, double adValue); + virtual void setSensitiveParameterValue(const ParameterId& id, double value); + + virtual unsigned int threadLocalMemorySize() const CADET_NOEXCEPT; + +#ifdef CADET_BENCHMARK_MODE + virtual std::vector benchmarkTimings() const + { + return std::vector({ + static_cast(numDofs()), + _timerResidual.totalElapsedTime(), + _timerResidualPar.totalElapsedTime(), + _timerResidualSens.totalElapsedTime(), + _timerResidualSensPar.totalElapsedTime(), + _timerJacobianPar.totalElapsedTime(), + _timerConsistentInit.totalElapsedTime(), + _timerConsistentInitPar.totalElapsedTime(), + _timerLinearSolve.totalElapsedTime(), + _timerFactorize.totalElapsedTime(), + _timerFactorizePar.totalElapsedTime() + }); + } + + virtual char const* const* benchmarkDescriptions() const + { + static const char* const desc[] = { + "DOFs", + "Residual", + "ResidualPar", + "ResidualSens", + "ResidualSensPar", + "JacobianPar", + "ConsistentInit", + "ConsistentInitPar", + "LinearSolve", + "Factorize", + "FactorizePar", + "MatVec", + }; + return desc; + } +#endif + +protected: + + class Indexer; + + int residual(const SimulationTime& simTime, const ConstSimulationState& simState, double* const res, const AdJacobianParams& adJac, util::ThreadLocalStorage& threadLocalMem, bool updateJacobian, bool paramSensitivity); + + template + int residualImpl(double t, unsigned int secIdx, StateType const* const y, double const* const yDot, ResidualType* const res, util::ThreadLocalStorage& threadLocalMem); + + template + int residualBulk(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, util::ThreadLocalStorage& threadLocalMem); + + template + int residualParticle(double t, unsigned int parType, unsigned int colCell, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res, util::ThreadLocalStorage& threadLocalMem); + + template + int residualFlux(double t, unsigned int secIdx, StateType const* y, double const* yDot, ResidualType* res); + + void extractJacobianFromAD(active const* const adRes, unsigned int adDirOffset); + + void assembleDiscretizedGlobalJacobian(double alpha, Indexer idxr); + + void addTimeDerivativeToJacobianParticleBlock(linalg::BandedEigenSparseRowIterator& jac, const Indexer& idxr, double alpha, unsigned int parType); + + unsigned int numAdDirsForJacobian() const CADET_NOEXCEPT; + + int multiplexInitialConditions(const cadet::ParameterId& pId, unsigned int adDirection, double adValue); + int multiplexInitialConditions(const cadet::ParameterId& pId, double val, bool checkSens); + +#ifdef CADET_CHECK_ANALYTIC_JACOBIAN + void checkAnalyticJacobianAgainstAd(active const* const adRes, unsigned int adDirOffset) const; +#endif + + struct Discretization + { + unsigned int nComp; //!< Number of components + unsigned int axNPoints; //!< Number of axial discrete points + unsigned int radNPoints; //!< Number of radial discrete points + unsigned int nBulkPoints; //!< Number of total bulk discrete points + unsigned int nParType; //!< Number of particle types + unsigned int* parTypeOffset; //!< Array with offsets (in particle block) to particle type, additional last element contains total number of particle DOFs + unsigned int* nBound; //!< Array with number of bound states for each component and particle type (particle type major ordering) + unsigned int* boundOffset; //!< Array with offset to the first bound state of each component in the solid phase (particle type major ordering) + unsigned int* strideBound; //!< Total number of bound states for each particle type, additional last element contains total number of bound states for all types + unsigned int* nBoundBeforeType; //!< Array with number of bound states before a particle type (cumulative sum of strideBound) + + bool newStaticJac; //!< determines whether or not a new static transport Jacobian is desired, ie on section switches + }; + + enum class ParticleDiscretizationMode : int + { + /** + * Equidistant distribution of shell edges + */ + Equidistant, + + /** + * Volumes of shells are uniform + */ + Equivolume, + + /** + * Shell edges specified by user + */ + UserDefined + }; + + Discretization _disc; //!< Discretization info +// IExternalFunction* _extFun; //!< External function (owned by library user) + + parts::TwoDimensionalConvectionDispersionOperatorDG _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 + + Eigen::MatrixXd _jacInlet; //!< Jacobian inlet DOF block matrix connects inlet DOFs to first bulk cells + + // for FV the bulk jacobians are defined in the ConvDisp operator. + Eigen::SparseMatrix _globalJac; //!< global Jacobian + Eigen::SparseMatrix _globalJacDisc; //!< global Jacobian with time derivative from BDF method + + std::vector _parRadius; //!< Particle radius \f$ r_p \f$ + bool _singleParRadius; + std::vector _parPorosity; //!< Particle porosity (internal porosity) \f$ \varepsilon_p \f$ + bool _singleParPorosity; + std::vector _parTypeVolFrac; //!< Volume fraction of each particle type + MultiplexMode _parTypeVolFracMode; //!< Multiplexing mode of particle volume fractions + std::vector _parGeomSurfToVol; //!< Particle surface to volume ratio factor (i.e., 3.0 for spherical, 2.0 for cylindrical, 1.0 for hexahedral) + + // Vectorial parameters + std::vector _filmDiffusion; //!< Film diffusion coefficient \f$ k_f \f$ + MultiplexMode _filmDiffusionMode; + std::vector _poreAccessFactor; //!< Pore accessibility factor \f$ F_{\text{acc}} \f$ + MultiplexMode _poreAccessFactorMode; + + bool _analyticJac; //!< Determines whether AD or analytic Jacobians are used + unsigned int _jacobianAdDirs; //!< Number of AD seed vectors required for Jacobian computation + + bool _factorizeJacobian; //!< Determines whether the Jacobian needs to be factorized + double* _tempState; //!< Temporary storage with the size of the state vector or larger if binding models require it + + std::vector _initC; //!< Liquid bulk phase initial conditions + bool _singleRadiusInitC; + std::vector _initCp; //!< Liquid particle phase initial conditions + bool _singleRadiusInitCp; + std::vector _initQ; //!< Solid phase initial conditions + bool _singleRadiusInitQ; + std::vector _initState; //!< Initial conditions for state vector if given + std::vector _initStateDot; //!< Initial conditions for time derivative + + BENCH_TIMER(_timerResidual) + BENCH_TIMER(_timerResidualPar) + BENCH_TIMER(_timerResidualSens) + BENCH_TIMER(_timerResidualSensPar) + BENCH_TIMER(_timerJacobianPar) + BENCH_TIMER(_timerConsistentInit) + BENCH_TIMER(_timerConsistentInitPar) + BENCH_TIMER(_timerLinearSolve) + BENCH_TIMER(_timerFactorize) + BENCH_TIMER(_timerFactorizePar) + BENCH_TIMER(_timerMatVec) + + class Indexer + { + public: + Indexer(const Discretization& disc) : _disc(disc) { } + + // Strides + inline int strideColAxialNode() const CADET_NOEXCEPT { return strideColRadialNode() * static_cast(_disc.radNPoints); } + inline int strideColRadialNode() const CADET_NOEXCEPT { return static_cast(_disc.nComp); } + inline int strideColComp() const CADET_NOEXCEPT { return 1; } + + inline int strideParComp() const CADET_NOEXCEPT { return 1; } + inline int strideParLiquid() const CADET_NOEXCEPT { return static_cast(_disc.nComp); } + inline int strideParBound(int parType) const CADET_NOEXCEPT { return static_cast(_disc.strideBound[parType]); } + inline int strideParBlock(int parType) const CADET_NOEXCEPT { return strideParLiquid() + strideParBound(parType); } + + // Offsets + inline int offsetC() const CADET_NOEXCEPT { return _disc.nComp * _disc.radNPoints; } + inline int offsetCp() const CADET_NOEXCEPT { return offsetC() + _disc.nComp * _disc.nBulkPoints; } + inline int offsetCp(ParticleTypeIndex pti) const CADET_NOEXCEPT { return offsetCp() + _disc.parTypeOffset[pti.value]; } + inline int offsetCp(ParticleTypeIndex pti, ParticleIndex pi) const CADET_NOEXCEPT { return offsetCp() + _disc.parTypeOffset[pti.value] + strideParBlock(pti.value) * pi.value; } + inline int offsetBoundComp(ParticleTypeIndex pti, ComponentIndex comp) const CADET_NOEXCEPT { return _disc.boundOffset[pti.value * _disc.nComp + comp.value]; } + + // Return pointer to first element of state variable in state vector + template inline real_t* c(real_t* const data) const { return data + offsetC(); } + template inline real_t const* c(real_t const* const data) const { return data + offsetC(); } + + template inline real_t* cp(real_t* const data) const { return data + offsetCp(); } + template inline real_t const* cp(real_t const* const data) const { return data + offsetCp(); } + + template inline real_t* q(real_t* const data) const { return data + offsetCp() + strideParLiquid(); } + template inline real_t const* q(real_t const* const data) const { return data + offsetCp() + strideParLiquid(); } + + // Return specific variable in state vector + template inline real_t& c(real_t* const data, unsigned int col, unsigned int rad, unsigned int comp) const { return data[offsetC() + comp + col * strideColAxialNode() + rad * strideColRadialNode()]; } + template inline const real_t& c(real_t const* const data, unsigned int col, unsigned int rad, unsigned int comp) const { return data[offsetC() + comp + col * strideColAxialNode() + rad * strideColRadialNode()]; } + + protected: + const Discretization& _disc; + }; + + class Exporter : public ISolutionExporter + { + public: + + Exporter(const Discretization& disc, const LumpedRateModelWithPoresDG2D& model, double const* data) : _disc(disc), _idx(disc), _model(model), _data(data) { } + Exporter(const Discretization&& disc, const LumpedRateModelWithPoresDG2D& model, double const* data) = delete; + + virtual bool hasParticleFlux() const CADET_NOEXCEPT { return true; } + virtual bool hasParticleMobilePhase() const CADET_NOEXCEPT { return true; } + virtual bool hasSolidPhase() const CADET_NOEXCEPT { return _disc.strideBound[_disc.nParType] > 0; } + virtual bool hasVolume() const CADET_NOEXCEPT { return false; } + virtual bool isParticleLumped() const CADET_NOEXCEPT { return false; } + virtual bool hasPrimaryExtent() const CADET_NOEXCEPT { return true; } + virtual bool hasSmoothnessIndicator() const CADET_NOEXCEPT { return false; } + + virtual unsigned int primaryPolynomialDegree() const CADET_NOEXCEPT { return 0; } + virtual unsigned int secondaryPolynomialDegree() const CADET_NOEXCEPT { return 0; } + virtual unsigned int particlePolynomialDegree(unsigned int parType) const CADET_NOEXCEPT { return 0; } + virtual unsigned int numComponents() const CADET_NOEXCEPT { return _disc.nComp; } + virtual unsigned int numPrimaryCoordinates() const CADET_NOEXCEPT { return _disc.axNPoints; } + virtual unsigned int numSecondaryCoordinates() const CADET_NOEXCEPT { return _disc.radNPoints; } + virtual unsigned int numInletPorts() const CADET_NOEXCEPT { return _disc.radNPoints; } + virtual unsigned int numOutletPorts() const CADET_NOEXCEPT { return _disc.radNPoints; } + virtual unsigned int numParticleTypes() const CADET_NOEXCEPT { return _disc.nParType; } + virtual unsigned int numParticleShells(unsigned int parType) const CADET_NOEXCEPT { return 1; } + virtual unsigned int numBoundStates(unsigned int parType) const CADET_NOEXCEPT { return _disc.strideBound[parType]; } + virtual unsigned int numMobilePhaseDofs() const CADET_NOEXCEPT { return _disc.nComp * _disc.axNPoints * _disc.radNPoints; } + virtual unsigned int numParticleMobilePhaseDofs(unsigned int parType) const CADET_NOEXCEPT { return _disc.nComp * _disc.nBulkPoints; } + virtual unsigned int numParticleMobilePhaseDofs() const CADET_NOEXCEPT { return _disc.nComp * _disc.nBulkPoints * _disc.nParType; } + virtual unsigned int numSolidPhaseDofs(unsigned int parType) const CADET_NOEXCEPT { return _disc.strideBound[parType] * _disc.nBulkPoints; } + virtual unsigned int numSolidPhaseDofs() const CADET_NOEXCEPT { + unsigned int nDofPerParType = 0; + for (unsigned int i = 0; i < _disc.nParType; ++i) + nDofPerParType += _disc.strideBound[i]; + return _disc.nBulkPoints * nDofPerParType; + } + virtual unsigned int numParticleFluxDofs() const CADET_NOEXCEPT { return _disc.nComp * _disc.axNPoints * _disc.radNPoints * _disc.nParType; } + virtual unsigned int numVolumeDofs() const CADET_NOEXCEPT { return 0; } + + virtual int writeMobilePhase(double* buffer) const; + virtual int writeSolidPhase(double* buffer) const; + virtual int writeParticleMobilePhase(double* buffer) const; + virtual int writeSolidPhase(unsigned int parType, double* buffer) const; + virtual int writeParticleMobilePhase(unsigned int parType, double* buffer) const; + virtual int writeParticleFlux(double* buffer) const { return 0; } + virtual int writeParticleFlux(unsigned int parType, double* buffer) const { return 0; } + virtual int writeVolume(double* buffer) const { return 0; } + virtual int writeInlet(unsigned int port, double* buffer) const; + virtual int writeInlet(double* buffer) const; + virtual int writeOutlet(unsigned int port, double* buffer) const; + virtual int writeOutlet(double* buffer) const; + + virtual int writeSmoothnessIndicator(double* indicator) const { return 0; } + + virtual int writePrimaryCoordinates(double* coords) const + { + _model._convDispOp.writeAxialCoordinates(coords); + return _disc.axNPoints; + } + virtual int writeSecondaryCoordinates(double* coords) const + { + _model._convDispOp.writeRadialCoordinates(coords); + return _disc.radNPoints; + } + virtual int writeParticleCoordinates(unsigned int parType, double* coords) const + { + coords[0] = static_cast(_model._parRadius[parType]) * 0.5; + return 1; + } + + protected: + const Discretization& _disc; + const Indexer _idx; + const LumpedRateModelWithPoresDG2D& _model; + double const* const _data; + }; + + typedef Eigen::Triplet T; + + void setFilmDiffFluxPattern(std::vector& tripletList) + { + Indexer idxr(_disc); + + for (unsigned int parType = 0; parType < _disc.nParType; parType++) { + + int offC = 0; // inlet DOFs not included in Jacobian + int offP = idxr.offsetCp(ParticleTypeIndex{ parType }) - idxr.offsetC(); // inlet DOFs not included in Jacobian + + // add dependency of c^b, c^p and flux on another + for (unsigned int nCol = 0; nCol < _disc.nBulkPoints; nCol++) { + for (unsigned int comp = 0; comp < _disc.nComp; comp++) { + // c^b on c^b entry already set + tripletList.push_back(T(offC + nCol * _disc.nComp + comp, offP + nCol * idxr.strideParBlock(parType) + comp, 0.0)); // c^b on c^p + // c^p on c^p entry already set + tripletList.push_back(T(offP + nCol * idxr.strideParBlock(parType) + comp, offC + nCol * _disc.nComp + comp, 0.0)); // c^p on c^b + } + } + } + } + + void setParticlePattern(std::vector& tripletList, Indexer& idxr) + { + for (unsigned int parType = 0; parType < _disc.nParType; parType++) + { + for (unsigned int par = 0; par < _disc.nBulkPoints; par++) + { + const int offPar = idxr.offsetCp(ParticleTypeIndex{ parType }, ParticleIndex{ par }) - idxr.offsetC(); + + for (unsigned int conc = 0; conc < idxr.strideParLiquid() + idxr.strideParBound(parType); conc++) + { + for (unsigned int concDep = 0; concDep < idxr.strideParLiquid() + idxr.strideParBound(parType); concDep++) + tripletList.push_back(T(offPar + conc, offPar + concDep, 0.0)); + } + } + } + } + + /** + * @brief sets the sparsity pattern of the convection dispersion Jacobian + */ + void setGlobalJacPattern(Eigen::SparseMatrix& mat) + { + std::vector tripletList; + + Indexer idxr(_disc); + + int nJacEntries = _convDispOp.nJacEntries() + _disc.nBulkPoints * _disc.nComp + 2 * numPureDofs(); // bulkTransportPattern: convDispOp; bulkReactionPattern: nComp * nBulkPoints; flux pattern: 2*pDOF; particlePattern: nBulkPoints * (nComp+nBound) + for (int parType = 0; parType < _disc.nParType; parType++) + nJacEntries += _disc.nBulkPoints * (_disc.nComp + _disc.nBound[parType]); + + tripletList.reserve(nJacEntries); + + _convDispOp.convDispJacPattern(tripletList); + + // note: bulk reaction pattern already set in convDispJacPattern + + setParticlePattern(tripletList, idxr); + + setFilmDiffFluxPattern(tripletList); + + mat.setFromTriplets(tripletList.begin(), tripletList.end()); + } +}; + +} // namespace model +} // namespace cadet + +#endif // LIBCADET_LUMPEDRATEMODELWITHPORESDG2D_HPP_ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 9f673bf78..5a94e5cf0 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -63,6 +63,9 @@ endif() if (ENABLE_DG) list(APPEND TEST_ADDITIONAL_SOURCES GeneralRateModelDG.cpp LumpedRateModelWithPoresDG.cpp LumpedRateModelWithoutPoresDG.cpp) + if (ENABLE_2D_MODELS) + list(APPEND TEST_ADDITIONAL_SOURCES LumpedRateModelWithPores2D.cpp) + endif() endif() add_executable(testRunner testRunner.cpp JsonTestModels.cpp ColumnTests.cpp UnitOperationTests.cpp SimHelper.cpp ParticleHelper.cpp diff --git a/test/ColumnTests.cpp b/test/ColumnTests.cpp index a316118a4..3dcab63ca 100644 --- a/test/ColumnTests.cpp +++ b/test/ColumnTests.cpp @@ -834,9 +834,9 @@ namespace column } //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - void testJacobianAD(cadet::JsonParameterProvider& jpp, const double absTolFDpattern, const double absTolAD) + void testJacobianAD(cadet::JsonParameterProvider& jpp, const double absTolFDpattern, const double absTolAD, const active* flowRate) { - cadet::ad::setDirections(cadet::ad::getMaxDirections()); // AD directions needed in createAndConfigureUnit but requiredADdirs not know before configureModelDiscretization (which is called in configureUnit) + cadet::ad::setDirections(cadet::ad::getMaxDirections()); // AD directions needed in createAndConfigureUnit but requiredADdirs not known before configureModelDiscretization (which is called in configureUnit) cadet::IModelBuilder* const mb = cadet::createModelBuilder(); REQUIRE(nullptr != mb); @@ -867,6 +867,11 @@ namespace column const AdJacobianParams adParams{adRes, adY, 0u}; unitAD->prepareADvectors(adParams); + if (flowRate) // for 2D units, velocity needs to be determined from flow rates + { + unitAna->setFlowRates(flowRate, flowRate); + unitAD->setFlowRates(flowRate, flowRate); + } const ConstSimulationState simState{y.data(), nullptr}; unitAna->notifyDiscontinuousSectionTransition(0.0, 0u, simState, noAdParams); unitAD->notifyDiscontinuousSectionTransition(0.0, 0u, simState, adParams); @@ -1620,6 +1625,13 @@ namespace column destroyModelBuilder(mb); } + JsonParameterProvider getReferenceFile(const std::string& modelFileRelPath) + { + const std::string setupFile = std::string(getTestDirectory()) + modelFileRelPath; + JsonParameterProvider pp_setup(JsonParameterProvider::fromFile(setupFile)); + return pp_setup; + } + void testReferenceBenchmark(const std::string& modelFileRelPath, const std::string& refFileRelPath, const std::string& unitID, const std::vector absTol, const std::vector relTol, const DiscParams& disc, const bool compare_sens, const int simDataStride) { const int unitOpID = std::stoi(unitID); diff --git a/test/ColumnTests.hpp b/test/ColumnTests.hpp index a39676ed8..c8736ab86 100644 --- a/test/ColumnTests.hpp +++ b/test/ColumnTests.hpp @@ -20,6 +20,7 @@ #include #include +#include "AutoDiff.hpp" namespace cadet { @@ -142,6 +143,12 @@ namespace column */ unsigned int fluxOffsetOfColumnUnitOp(cadet::IUnitOperation* unit); + /** + * @brief Runs a parameterProvider for a model setup provided by a json file + * @param [in] modelFileRelPath relative path to model setup json file + */ + JsonParameterProvider getReferenceFile(const std::string& modelFileRelPath); + /** * @brief Runs a simulation test comparing against (semi-)analytic single component pulse injection reference data * @details Linear binding model is used in the column-like unit operation. @@ -191,8 +198,9 @@ namespace column * @param [in] jpp Configured column model * @param [in] absTolFDpattern absolute tolerance when comparing the sign in the FD Jacobian pattern * @param [in] absTolAD absolute tolerance when comparing the AD Jacobians. Deviation from default only advised when values are numerically challenging, i.e. are at least 1E+10 + * @param [in] flowRate flow rate, needs to be specified for 2D units, where velocity is derived from flow rates. */ - void testJacobianAD(cadet::JsonParameterProvider& jpp, const double absTolFDpattern = 0.0, const double absTolAD = std::numeric_limits::epsilon() * 100.0); + void testJacobianAD(cadet::JsonParameterProvider& jpp, const double absTolFDpattern = 0.0, const double absTolAD = std::numeric_limits::epsilon() * 100.0, const cadet::active* flowRate = nullptr); /** * @brief Checks the full Jacobian against AD and FD pattern switching in case of variable surface diffusion coefficient diff --git a/test/GeneralRateModelDG.cpp b/test/GeneralRateModelDG.cpp index 8d72c5d3e..02b9e242b 100644 --- a/test/GeneralRateModelDG.cpp +++ b/test/GeneralRateModelDG.cpp @@ -23,7 +23,7 @@ #include "Utils.hpp" #include "common/Driver.hpp" -TEST_CASE("GRM_DG LWE forward vs backward flow", "[GRM],[DG],[Simulation],[CI]") +TEST_CASE("GRM_DG LWE forward vs backward flow", "[GRM],[DG],[DG1D],[Simulation],[CI]") { cadet::test::column::DGparams disc; @@ -35,7 +35,7 @@ TEST_CASE("GRM_DG LWE forward vs backward flow", "[GRM],[DG],[Simulation],[CI]") } } -TEST_CASE("GRM_DG linear pulse vs analytic solution", "[GRM],[DG],[Simulation],[Analytic],[CI]") +TEST_CASE("GRM_DG linear pulse vs analytic solution", "[GRM],[DG],[DG1D],[Simulation],[Analytic],[CI]") { cadet::test::column::DGparams disc; cadet::test::column::testAnalyticBenchmark("GENERAL_RATE_MODEL", "/data/grm-pulseBenchmark.data", true, true, disc, 6e-5, 1e-7); @@ -44,7 +44,7 @@ TEST_CASE("GRM_DG linear pulse vs analytic solution", "[GRM],[DG],[Simulation],[ cadet::test::column::testAnalyticBenchmark("GENERAL_RATE_MODEL", "/data/grm-pulseBenchmark.data", false, false, disc, 6e-5, 1e-7); } -TEST_CASE("GRM_DG non-binding linear pulse vs analytic solution", "[GRM],[DG],[Simulation],[Analytic],[NonBinding],[CI]") +TEST_CASE("GRM_DG non-binding linear pulse vs analytic solution", "[GRM],[DG],[DG1D],[Simulation],[Analytic],[NonBinding],[CI]") { cadet::test::column::DGparams disc; cadet::test::column::testAnalyticNonBindingBenchmark("GENERAL_RATE_MODEL", "/data/grm-nonBinding.data", true, disc, 6e-5, 1e-7); @@ -52,7 +52,7 @@ TEST_CASE("GRM_DG non-binding linear pulse vs analytic solution", "[GRM],[DG],[S } // todo FIX (scheitert bei backward flow jacobian vs AD) -//TEST_CASE("GRM_DG Jacobian forward vs backward flow", "[GRM],[DG],[UnitOp],[Residual],[Jacobian],[AD],[todo]") +//TEST_CASE("GRM_DG Jacobian forward vs backward flow", "[GRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[AD],[todo]") //{ // cadet::test::column::DGparams disc; // @@ -64,7 +64,7 @@ TEST_CASE("GRM_DG non-binding linear pulse vs analytic solution", "[GRM],[DG],[S // } //} -TEST_CASE("GRM_DG numerical Benchmark with parameter sensitivities for linear case", "[GRM],[DG],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +TEST_CASE("GRM_DG numerical Benchmark with parameter sensitivities for linear case", "[GRM],[DG],[DG1D],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server { const std::string& modelFilePath = std::string("/data/model_GRM_dynLin_1comp_benchmark1.json"); const std::string& refFilePath = std::string("/data/ref_GRM_dynLin_1comp_sensbenchmark1_cDG_P3Z8_GSM_parP3parZ1.h5"); @@ -75,7 +75,7 @@ TEST_CASE("GRM_DG numerical Benchmark with parameter sensitivities for linear ca cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "001", absTol, relTol, disc, true); } -TEST_CASE("GRM_DG numerical Benchmark with parameter sensitivities for SMA LWE case", "[GRM],[DG],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +TEST_CASE("GRM_DG numerical Benchmark with parameter sensitivities for SMA LWE case", "[GRM],[DG],[DG1D],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server { const std::string& modelFilePath = std::string("/data/model_GRM_reqSMA_4comp_benchmark1.json"); const std::string& refFilePath = std::string("/data/ref_GRM_reqSMA_4comp_sensbenchmark1_cDG_P3Z8_GSM_parP3parZ1.h5"); @@ -86,7 +86,7 @@ TEST_CASE("GRM_DG numerical Benchmark with parameter sensitivities for SMA LWE c cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "000", absTol, relTol, disc, true); } -TEST_CASE("GRM_DG LWE DGSEM and GSM particle discretization yields similar accuracy", "[GRM],[DG],[Simulation],[CI]") +TEST_CASE("GRM_DG LWE DGSEM and GSM particle discretization yields similar accuracy", "[GRM],[DG],[DG1D],[Simulation],[CI]") { cadet::JsonParameterProvider jpp = createLWE("GENERAL_RATE_MODEL", "DG"); cadet::test::column::DGparams disc(0, 4, 2, 3, 1); // Note that we want to employ only a single particle element @@ -128,22 +128,22 @@ TEST_CASE("GRM_DG LWE DGSEM and GSM particle discretization yields similar accur } } -TEST_CASE("GRM_DG time derivative Jacobian vs FD", "[GRM],[DG],[UnitOp],[Residual],[Jacobian],[CI],[FD]") +TEST_CASE("GRM_DG time derivative Jacobian vs FD", "[GRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[CI],[FD]") { cadet::test::column::testTimeDerivativeJacobianFD("GENERAL_RATE_MODEL", "DG", 1e-6, 0.0, 9e-4); } -//TEST_CASE("GRM_DG dynamic binding with surf diff par dep Jacobian vs AD", "[GRM],[DG],[UnitOp],[Residual],[Jacobian],[ParameterDependence],[fix]") +//TEST_CASE("GRM_DG dynamic binding with surf diff par dep Jacobian vs AD", "[GRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[ParameterDependence],[fix]") //{ // cadet::test::column::testJacobianADVariableParSurfDiff("GENERAL_RATE_MODEL", "DG", true); //} -TEST_CASE("GRM_DG rapid-equilibrium binding with surf diff par dep Jacobian vs AD", "[GRM],[DG],[UnitOp],[Residual],[Jacobian],[ParameterDependence]") // todo include in CI (runs locally but fails on server with linux) +TEST_CASE("GRM_DG rapid-equilibrium binding with surf diff par dep Jacobian vs AD", "[GRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[ParameterDependence]") // todo include in CI (runs locally but fails on server with linux) { cadet::test::column::testJacobianADVariableParSurfDiff("GENERAL_RATE_MODEL", "DG", false); } -TEST_CASE("GRM_DG sensitivity Jacobians", "[GRM],[DG],[UnitOp],[Sensitivity]") // todo does not run on CI but locally on windows +TEST_CASE("GRM_DG sensitivity Jacobians", "[GRM],[DG],[DG1D],[UnitOp],[Sensitivity]") // todo does not run on CI but locally on windows { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("GENERAL_RATE_MODEL", "DG"); @@ -151,7 +151,7 @@ TEST_CASE("GRM_DG sensitivity Jacobians", "[GRM],[DG],[UnitOp],[Sensitivity]") / } //// todo fix: not just adjust tolerances as in FV but theres an actual error here: access violation in densematrix -//TEST_CASE("GRM_DG forward sensitivity vs FD", "[GRM],[DG],[Sensitivity],[Simulation],[todo]") +//TEST_CASE("GRM_DG forward sensitivity vs FD", "[GRM],[DG],[DG1D],[Sensitivity],[Simulation],[todo]") //{ // // 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 @@ -164,7 +164,7 @@ TEST_CASE("GRM_DG sensitivity Jacobians", "[GRM],[DG],[UnitOp],[Sensitivity]") / //} //// todo fix: not just adjust tolerances as in FV but theres an actual error here: access violation in densematrix -//TEST_CASE("GRM_DG forward sensitivity forward vs backward flow", "[GRM],[DG],[Sensitivity],[Simulation],[todo]") +//TEST_CASE("GRM_DG forward sensitivity forward vs backward flow", "[GRM],[DG],[DG1D],[Sensitivity],[Simulation],[todo]") //{ // const double absTols[] = { 4e-5, 1e-11, 1e-11, 8e-9 }; // const double relTols[] = { 6e-9, 5e-8, 5e-6, 5e-10 }; @@ -173,7 +173,7 @@ TEST_CASE("GRM_DG sensitivity Jacobians", "[GRM],[DG],[UnitOp],[Sensitivity]") / //} // todo fix consistent initialization for AD with req binding -TEST_CASE("GRM_DG consistent initialization with linear binding", "[GRM],[DG],[ConsistentInit],[CI]") +TEST_CASE("GRM_DG consistent initialization with linear binding", "[GRM],[DG],[DG1D],[ConsistentInit],[CI]") { cadet::test::column::testConsistentInitializationLinearBinding("GENERAL_RATE_MODEL", "DG", 1e-12, 1e-14, 0, 0); cadet::test::column::testConsistentInitializationLinearBinding("GENERAL_RATE_MODEL", "DG", 1e-12, 1e-12, 1, 0); @@ -182,7 +182,7 @@ TEST_CASE("GRM_DG consistent initialization with linear binding", "[GRM],[DG],[C } //// todo fix consistent initialization for SMA (initialization not completely correct; AD gives assertion error) -//TEST_CASE("GRM_DG consistent initialization with SMA binding", "[GRM],[DG],[ConsistentInit],[todo]") +//TEST_CASE("GRM_DG consistent initialization with SMA binding", "[GRM],[DG],[DG1D],[ConsistentInit],[todo]") //{ // std::vector y(4 + 4 * 16 + 16 * 4 * (4 + 4) + 4 * 16, 0.0); // // Optimal values: @@ -198,7 +198,7 @@ TEST_CASE("GRM_DG consistent initialization with linear binding", "[GRM],[DG],[C //} // todo fix kinetic binding sensitivity init -TEST_CASE("GRM_DG consistent sensitivity initialization with linear binding", "[GRM],[DG],[ConsistentInit],[Sensitivity],[CI]") +TEST_CASE("GRM_DG consistent sensitivity initialization with linear binding", "[GRM],[DG],[DG1D],[ConsistentInit],[Sensitivity],[CI]") { // Fill state vector with given initial values const unsigned int numDofs = 4 + 4 * 16 + 16 * 4 * (4 + 4) + 4 * 16; @@ -214,7 +214,7 @@ TEST_CASE("GRM_DG consistent sensitivity initialization with linear binding", "[ } //// todo fix memory stuff (works for FV) -//TEST_CASE("GRM_DG consistent sensitivity initialization with SMA binding", "[GRM],[DG],[ConsistentInit],[Sensitivity],[fffffffiujbnlk]") +//TEST_CASE("GRM_DG consistent sensitivity initialization with SMA binding", "[GRM],[DG],[DG1D],[ConsistentInit],[Sensitivity],[fffffffiujbnlk]") //{ // // Fill state vector with given initial values // const unsigned int numDofs = 4 + 4 * 16 + 16 * 4 * (4 + 4) + 4 * 16; @@ -231,104 +231,104 @@ TEST_CASE("GRM_DG consistent sensitivity initialization with linear binding", "[ // cadet::test::column::testConsistentInitializationSensitivity("GENERAL_RATE_MODEL", "DG", y.data(), yDot.data(), false, 1e-9); //} -TEST_CASE("GRM_DG inlet DOF Jacobian", "[GRM],[DG],[UnitOp],[Jacobian],[Inlet],[CI]") +TEST_CASE("GRM_DG inlet DOF Jacobian", "[GRM],[DG],[DG1D],[UnitOp],[Jacobian],[Inlet],[CI]") { cadet::test::column::testInletDofJacobian("GENERAL_RATE_MODEL", "DG"); } -TEST_CASE("GRM_DG transport Jacobian", "[GRM],[DG],[UnitOp],[Jacobian],[CI]") +TEST_CASE("GRM_DG transport Jacobian", "[GRM],[DG],[DG1D],[UnitOp],[Jacobian],[CI]") { cadet::JsonParameterProvider jpp = createColumnLinearBenchmark(false, true, "GENERAL_RATE_MODEL", "DG"); cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("GRM_DG with two component linear binding Jacobian", "[GRM],[DG],[UnitOp],[Jacobian],[CI]") +TEST_CASE("GRM_DG with two component linear binding Jacobian", "[GRM],[DG],[DG1D],[UnitOp],[Jacobian],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("GENERAL_RATE_MODEL", "DG"); cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("GRM_DG LWE one vs two identical particle types match", "[GRM],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("GRM_DG LWE one vs two identical particle types match", "[GRM],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testOneVsTwoIdenticalParticleTypes("GENERAL_RATE_MODEL", "DG", 2e-8, 5e-5); } -TEST_CASE("GRM_DG LWE separate identical particle types match", "[GRM],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("GRM_DG LWE separate identical particle types match", "[GRM],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testSeparateIdenticalParticleTypes("GENERAL_RATE_MODEL", "DG", 2e-8, 5e-5); } -TEST_CASE("GRM_DG linear binding single particle matches particle distribution", "[GRM],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("GRM_DG linear binding single particle matches particle distribution", "[GRM],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testLinearMixedParticleTypes("GENERAL_RATE_MODEL", "DG", 5e-8, 5e-5); } -TEST_CASE("GRM_DG multiple particle types Jacobian analytic vs AD", "[GRM],[DG],[Jacobian],[AD],[ParticleType],[CI]") +TEST_CASE("GRM_DG multiple particle types Jacobian analytic vs AD", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ParticleType],[CI]") { cadet::test::particle::testJacobianMixedParticleTypes("GENERAL_RATE_MODEL", "DG"); } -TEST_CASE("GRM_DG multiple particle types time derivative Jacobian vs FD", "[GRM],[DG],[UnitOp],[Residual],[Jacobian],[ParticleType],[CI]") +TEST_CASE("GRM_DG multiple particle types time derivative Jacobian vs FD", "[GRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[ParticleType],[CI]") { cadet::test::particle::testTimeDerivativeJacobianMixedParticleTypesFD("GENERAL_RATE_MODEL", "DG", 1e-6, 0.0, 9e-4); } -TEST_CASE("GRM_DG multiple spatially dependent particle types Jacobian analytic vs AD", "[GRM],[DG],[Jacobian],[AD],[ParticleType],[CI]") +TEST_CASE("GRM_DG multiple spatially dependent particle types Jacobian analytic vs AD", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ParticleType],[CI]") { cadet::test::particle::testJacobianSpatiallyMixedParticleTypes("GENERAL_RATE_MODEL", "DG", 1e-11); } -TEST_CASE("GRM_DG linear binding single particle matches spatially dependent particle distribution", "[GRM],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("GRM_DG linear binding single particle matches spatially dependent particle distribution", "[GRM],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testLinearSpatiallyMixedParticleTypes("GENERAL_RATE_MODEL", "DG", 5e-8, 5e-5); } -TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD bulk", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD bulk", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("GENERAL_RATE_MODEL", "DG", true, false, false); } -TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("GENERAL_RATE_MODEL", "DG", false, true, false); } -TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD modified particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD modified particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("GENERAL_RATE_MODEL", "DG", false, true, true); } -TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD bulk and particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD bulk and particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("GENERAL_RATE_MODEL", "DG", true, true, false); } -TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD bulk and modified particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("GRM_DG dynamic reactions Jacobian vs AD bulk and modified particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("GENERAL_RATE_MODEL", "DG", true, true, true); } -TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD bulk", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") +TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD bulk", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("GENERAL_RATE_MODEL", "DG", true, false, false, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") +TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("GENERAL_RATE_MODEL", "DG", false, true, false, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD modified particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") +TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD modified particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("GENERAL_RATE_MODEL", "DG", false, true, true, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD bulk and particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") +TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD bulk and particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("GENERAL_RATE_MODEL", "DG", true, true, false, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") +TEST_CASE("GRM_DG dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI],[FD]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("GENERAL_RATE_MODEL", "DG", true, true, true, 1e-6, 1e-14, 9e-4); } @@ -344,61 +344,61 @@ inline cadet::JsonParameterProvider createColumnWithTwoCompLinearBindingThreePar return jpp; } -TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD bulk", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD bulk", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, false, false); } -TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, false); } -TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD modified particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD modified particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, true); } -TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD bulk and particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD bulk and particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, false); } -TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD bulk and modified particle", "[GRM],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions Jacobian vs AD bulk and modified particle", "[GRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, true); } -TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, false, false, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, false, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, true, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, false, 1e-6, 1e-14, 9e-4); } -TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[GRM],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("GRM_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[GRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, true, 1e-6, 1e-14, 9e-4); diff --git a/test/JsonTestModels.cpp b/test/JsonTestModels.cpp index fae0fa257..9b72095e0 100644 --- a/test/JsonTestModels.cpp +++ b/test/JsonTestModels.cpp @@ -94,23 +94,37 @@ json createColumnWithSMAJson(const std::string& uoType, const std::string& spati weno["WENO_EPS"] = 1e-10; disc["weno"] = weno; } + + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) + { + disc["NCOL"] = 8; + disc["NRAD"] = 3; + disc["NPAR"] = 3; + disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + } } else if (spatialMethod == "DG") { - disc["EXACT_INTEGRATION"] = 0; - disc["POLYDEG"] = 4; - disc["NELEM"] = 2; disc["PAR_EXACT_INTEGRATION"] = 1; - disc["PAR_POLYDEG"] = 3; - disc["PAR_NELEM"] = 1; - } - if (uoType == "GENERAL_RATE_MODEL_2D") - { - disc["NCOL"] = 8; - disc["NRAD"] = 3; - disc["NPAR"] = 3; - disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) + { + disc["AX_POLYDEG"] = 2; + disc["AX_NELEM"] = 2; + disc["RAD_POLYDEG"] = 2; + disc["RAD_NELEM"] = 1; + disc["PAR_POLYDEG"] = 1; + disc["PAR_NELEM"] = 1; + disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + } + else + { + disc["EXACT_INTEGRATION"] = 0; + disc["POLYDEG"] = 4; + disc["NELEM"] = 2; + disc["PAR_POLYDEG"] = 3; + disc["PAR_NELEM"] = 1; + } } if (uoType == "MULTI_CHANNEL_TRANSPORT") @@ -266,12 +280,25 @@ json createColumnWithTwoCompLinearJson(const std::string& uoType, const std::str disc["PAR_NELEM"] = 1; } - if (uoType == "GENERAL_RATE_MODEL_2D") + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) { - disc["NCOL"] = 8; - disc["NRAD"] = 3; - disc["NPAR"] = 3; disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + + if (spatialMethod == "DG") + { + disc["AX_POLYDEG"] = 4; + disc["AX_NELEM"] = 2; + disc["RAD_POLYDEG"] = 2; + disc["RAD_NELEM"] = 1; + disc["PAR_POLYDEG"] = 3; + disc["PAR_NELEM"] = 1; + } + else if (spatialMethod == "FV") + { + disc["NCOL"] = 8; + disc["NRAD"] = 3; + disc["NPAR"] = 3; + } } if (uoType == "MULTI_CHANNEL_TRANSPORT") @@ -357,18 +384,34 @@ json createLWEJson(const std::string& uoType, const std::string& spatialMethod) // This switch occurs at beginning of section 0 (initial configuration) sw["SECTION"] = 0; - if (uoType == "GENERAL_RATE_MODEL_2D") + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) { - // Connection list is 3x7 since we have 1 connection between - // the two unit operations with 3 ports (and we need to have 7 columns) - sw["CONNECTIONS"] = {1.0, 0.0, 0.0, 0.0, -1.0, -1.0, 7.42637597e-09, - 1.0, 0.0, 0.0, 1.0, -1.0, -1.0, 2.22791279e-08, - 1.0, 0.0, 0.0, 2.0, -1.0, -1.0, 3.71318798e-08}; - // Connections: From unit operation 1 port 0 - // to unit operation 0 port 0, - // connect component -1 (i.e., all components) - // to component -1 (i.e., all components) with - // volumetric flow rate 7.42637597e-09 m^3/s + if (uoType.find("GRM") && !uoType.find("DG")) + { + // Connection list is 3x7 since we have 1 connection between + // the two unit operations with 3 ports (and we need to have 7 columns) + sw["CONNECTIONS"] = { 1.0, 0.0, 0.0, 0.0, -1.0, -1.0, 7.42637597e-09, + 1.0, 0.0, 0.0, 1.0, -1.0, -1.0, 2.22791279e-08, + 1.0, 0.0, 0.0, 2.0, -1.0, -1.0, 3.71318798e-08 }; + // Connections: From unit operation 1 port 0 + // to unit operation 0 port 0, + // connect component -1 (i.e., all components) + // to component -1 (i.e., all components) with + // volumetric flow rate 7.42637597e-09 m^3/s + } + else // DG unit -> needs different flow rates for constant velocity + { + // Connection list is 3x7 since we have 1 connection between + // the two unit operations with 3 ports (and we need to have 7 columns) + sw["CONNECTIONS"] = { 1.0, 0.0, 0.0, 0.0, -1.0, -1.0, 2.2743276399659853E-10, + 1.0, 0.0, 0.0, 1.0, -1.0, -1.0, 5.458386335918363E-9, + 1.0, 0.0, 0.0, 2.0, -1.0, -1.0, 2.5017604039625835E-9 }; + // Connections: From unit operation 1 port 0 + // to unit operation 0 port 0, + // connect component -1 (i.e., all components) + // to component -1 (i.e., all components) with + // volumetric flow rate 7.42637597e-09 m^3/s + } } else { @@ -561,6 +604,12 @@ cadet::JsonParameterProvider createPulseInjectionColumn(const std::string& uoTyp if (spatialMethod == "FV") { + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) + { + disc["NRAD"] = 3; + disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + } + disc["NCOL"] = 10; disc["NPAR"] = 4; @@ -578,18 +627,23 @@ cadet::JsonParameterProvider createPulseInjectionColumn(const std::string& uoTyp } else if (spatialMethod == "DG") { - disc["EXACT_INTEGRATION"] = 0; - disc["POLYDEG"] = 3; - disc["NELEM"] = 2; - disc["PAR_EXACT_INTEGRATION"] = 1; + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) + { + disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + disc["AX_POLYDEG"] = 3; + disc["AX_NELEM"] = 2; + disc["RAD_POLYDEG"] = 2; + disc["RAD_NELEM"] = 1; + } + else + { + disc["EXACT_INTEGRATION"] = 0; + disc["POLYDEG"] = 3; + disc["NELEM"] = 2; + } disc["PAR_POLYDEG"] = 3; disc["PAR_NELEM"] = 1; - } - - if (uoType == "GENERAL_RATE_MODEL_2D") - { - disc["NRAD"] = 3; - disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + disc["PAR_EXACT_INTEGRATION"] = 1; } disc["PAR_DISC_TYPE"] = std::string("EQUIDISTANT_PAR"); @@ -647,7 +701,7 @@ cadet::JsonParameterProvider createPulseInjectionColumn(const std::string& uoTyp // This switch occurs at beginning of section 0 (initial configuration) sw["SECTION"] = 0; - if (uoType == "GENERAL_RATE_MODEL_2D") + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) { // Connection list is 3x7 since we have 1 connection between // the two unit operations with 3 ports (and we need to have 7 columns) @@ -768,9 +822,9 @@ json createLinearBenchmarkColumnJson(bool dynamicBinding, bool nonBinding, const grm["COL_DISPERSION"] = 0.002 / (100.0 * 100.0 * 60.0); grm["COL_DISPERSION_MULTIPLEX"] = 0; grm["COL_DISPERSION_RADIAL"] = 1e-6; - grm["FILM_DIFFUSION"] = {0.01 / (100.0 * 60.0)}; - grm["PAR_DIFFUSION"] = {3.003e-6}; - grm["PAR_SURFDIFFUSION"] = {0.0}; + grm["FILM_DIFFUSION"] = { 0.01 / (100.0 * 60.0) }; + grm["PAR_DIFFUSION"] = { 3.003e-6 }; + grm["PAR_SURFDIFFUSION"] = { 0.0 }; if (uoType == "MULTI_CHANNEL_TRANSPORT") grm["NCHANNEL"] = 3; @@ -800,71 +854,80 @@ json createLinearBenchmarkColumnJson(bool dynamicBinding, bool nonBinding, const } // Initial conditions - grm["INIT_C"] = {0.0}; - grm["INIT_Q"] = {0.0}; + grm["INIT_C"] = { 0.0 }; + grm["INIT_Q"] = { 0.0 }; - // Adsorption - if (nonBinding) - { - grm["ADSORPTION_MODEL"] = std::string("NONE"); - grm["NBOUND"] = { 0 }; - } - else - { - grm["ADSORPTION_MODEL"] = std::string("LINEAR"); - grm["NBOUND"] = { 1 }; + // Adsorption + if (nonBinding) + { + grm["ADSORPTION_MODEL"] = std::string("NONE"); + grm["NBOUND"] = { 0 }; + } + else + { + grm["ADSORPTION_MODEL"] = std::string("LINEAR"); + grm["NBOUND"] = { 1 }; json ads; ads["IS_KINETIC"] = (dynamicBinding ? 1 : 0); - ads["LIN_KA"] = {2.5}; - ads["LIN_KD"] = {1.0}; + ads["LIN_KA"] = { 2.5 }; + ads["LIN_KD"] = { 1.0 }; grm["adsorption"] = ads; } - // Discretization - { - json disc; - disc["SPATIAL_METHOD"] = spatialMethod; - - if (spatialMethod == "FV") - { - disc["NCOL"] = 512; - disc["NPAR"] = 4; - - disc["MAX_KRYLOV"] = 0; - disc["GS_TYPE"] = 1; - disc["MAX_RESTARTS"] = 10; - disc["SCHUR_SAFETY"] = 1e-8; - { - json weno; - weno["WENO_ORDER"] = 3; - weno["BOUNDARY_MODEL"] = 0; - weno["WENO_EPS"] = 1e-10; - disc["weno"] = weno; - } - } - else if (spatialMethod == "DG") - { - disc["EXACT_INTEGRATION"] = 0; - disc["POLYDEG"] = 5; - disc["NELEM"] = 15; - disc["PAR_EXACT_INTEGRATION"] = 1; - disc["PAR_POLYDEG"] = 3; - disc["PAR_NELEM"] = 1; - } + // Discretization + { + const bool model2D = uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos; + json disc; + disc["SPATIAL_METHOD"] = spatialMethod; + if (model2D) + disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; - if (uoType == "GENERAL_RATE_MODEL_2D") + if (spatialMethod == "FV") { - disc["NRAD"] = 3; - disc["RADIAL_DISC_TYPE"] = "EQUIDISTANT"; + disc["NCOL"] = 512; + disc["NPAR"] = 4; + if (model2D) + disc["NRAD"] = 3; + + disc["MAX_KRYLOV"] = 0; + disc["GS_TYPE"] = 1; + disc["MAX_RESTARTS"] = 10; + disc["SCHUR_SAFETY"] = 1e-8; + { + json weno; + weno["WENO_ORDER"] = 3; + weno["BOUNDARY_MODEL"] = 0; + weno["WENO_EPS"] = 1e-10; + disc["weno"] = weno; + } + } + else if (spatialMethod == "DG") + { + if (model2D) + { + disc["AX_POLYDEG"] = 1; + disc["AX_NELEM"] = 1; + disc["RAD_POLYDEG"] = 2; + disc["RAD_NELEM"] = 1; + } + else + { + disc["EXACT_INTEGRATION"] = 0; + disc["POLYDEG"] = 5; + disc["NELEM"] = 15; + } + disc["PAR_EXACT_INTEGRATION"] = 1; + disc["PAR_POLYDEG"] = 3; + disc["PAR_NELEM"] = 1; } disc["PAR_DISC_TYPE"] = std::string("EQUIDISTANT_PAR"); - disc["USE_ANALYTIC_JACOBIAN"] = true; + disc["USE_ANALYTIC_JACOBIAN"] = true; - grm["discretization"] = disc; - } + grm["discretization"] = disc; + } return grm; } @@ -930,7 +993,7 @@ cadet::JsonParameterProvider createLinearBenchmark(bool dynamicBinding, bool non // This switch occurs at beginning of section 0 (initial configuration) sw["SECTION"] = 0; - if (uoType == "GENERAL_RATE_MODEL_2D") + if (uoType.find("_2D") != std::string::npos || uoType.find("2D_") != std::string::npos) { // Connection list is 3x7 since we have 1 connection between // the two unit operations with 3 ports (and we need to have 7 columns) diff --git a/test/LumpedRateModelWithPores2D.cpp b/test/LumpedRateModelWithPores2D.cpp new file mode 100644 index 000000000..1b50c31d4 --- /dev/null +++ b/test/LumpedRateModelWithPores2D.cpp @@ -0,0 +1,281 @@ +// ============================================================================= +// CADET +// +// Copyright © 2008-present: The CADET-Core Authors +// Please see the AUTHORS.md file. +// +// All rights reserved. This program and the accompanying materials +// are made available under the terms of the GNU Public License v3.0 (or, at +// your option, any later version) which accompanies this distribution, and +// is available at http://www.gnu.org/licenses/gpl.html +// ============================================================================= + +#include + +#include "ColumnTests.hpp" +#include "ParticleHelper.hpp" +#include "ReactionModelTests.hpp" +#include "Utils.hpp" +#include "JsonTestModels.hpp" + +void test2DLRMPJacobian(const std::string relModelFilePath, const int maxAxElem, const int maxRadElem, const int axPolyDeg = 0, const int radPolyDeg = 0) +{ + cadet::JsonParameterProvider jpp = cadet::test::column::getReferenceFile(relModelFilePath); + + // get the number of radial ports + jpp.pushScope("model"); + const int nUnits = jpp.getInt("NUNITS"); // there is one column and (nUnits-1) inlets, one per radial port + const int columnIdx = 0; + const std::string unitID = "000"; + + // we need to set flowRates for 2D models for the JacobianAD test, since velocity is only set with a call to the setFlowRate function, + // which in turn is only called if we specify flow rates. We get the flow rates from the connections matrix + jpp.pushScope("connections"); + jpp.pushScope("switch_000"); + + const std::vector connections = jpp.getDoubleArray("CONNECTIONS"); + + jpp.popScope(); + jpp.popScope(); + jpp.pushScope("unit_" + unitID); + jpp.pushScope("discretization"); + + const int nRad = (jpp.getInt("RAD_POLYDEG") + 1) * jpp.getInt("RAD_NELEM"); // original number of radial points from file + + // make sure we have enough flow rate entries + if (radPolyDeg == 0) + REQUIRE(maxRadElem <= jpp.getInt("RAD_NELEM")); + else + REQUIRE((radPolyDeg + 1) * maxRadElem <= nRad); + + std::vector flowRate; + for (int i = 1; i <= nRad; i++) + flowRate.push_back(static_cast(connections[i * 7 - 1])); + + if (axPolyDeg > 0) + jpp.set("AX_POLYDEG", axPolyDeg); + if (radPolyDeg > 0) + jpp.set("RAD_POLYDEG", radPolyDeg); + jpp.popScope(); + + // This test might run out of memory due to the required AD directions: + // (axPolyDeg + 1) * axNElem * (radPolyDeg + 1) * radNElem * (nComp + nParType * (nComp + nBound)) + for (int zElem = 1; zElem <= maxAxElem; zElem++) + { + for (int rElem = 1; rElem <= maxRadElem; rElem++) + { + jpp.pushScope("discretization"); + jpp.set("AX_NELEM", zElem); + jpp.set("RAD_NELEM", rElem); + jpp.popScope(); + + cadet::test::column::testJacobianAD(jpp, 1e10, std::numeric_limits::epsilon() * 100.0, &flowRate[0]); // @todo figure out why FD Jacobian pattern comparison doesnt work but AD Jacobian comparison does + } + } +} + +TEST_CASE("LRMP2D inlet DOF Jacobian", "[LRMP2D],[DG],[DG2D],[UnitOp],[Jacobian],[Inlet],[CI]") +{ + cadet::test::column::testInletDofJacobian("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG"); +} + +TEST_CASE("LRMP2D time derivative Jacobian vs FD", "[LRMP2D],[DG],[DG2D],[UnitOp],[Residual],[Jacobian],[CI],[FD]") +{ + cadet::test::column::testTimeDerivativeJacobianFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-6, 0.0, 9e-4); +} + +TEST_CASE("LRMP2D transport Jacobian", "[LRMP2D],[DG],[DG2D],[UnitOp],[Jacobian],[CI]") +{ + const std::string relModelFilePath = std::string("/data/model_LRMP2D_bulkTransport_1comp.json"); + + // This test might run out of memory due to the required AD directions: + // inletDof + (axPolyDeg + 1) * axNElem * (radPolyDeg + 1) * radNElem * (nComp + nParType * (nComp + nBound)) + // result here is 8radPoints + 128 pure dofs (8axPoints*8radPoints) * (1 + 1) + test2DLRMPJacobian(relModelFilePath, 4, 4, 1, 1); +} + +TEST_CASE("LRMP2D transport Jacobian, full test", "[LRMP2D],[DG],[DG2D],[UnitOp],[Jacobian],[ReleaseCI]") +{ + const std::string relModelFilePath = std::string("/data/model_LRMP2D_bulkTransport_1comp.json"); + + // This test might run out of memory due to the required AD directions: + // inletDof + (axPolyDeg + 1) * axNElem * (radPolyDeg + 1) * radNElem * (nComp + nParType * (nComp + nBound)) + // result here is 14radPoints + 588 pure dofs (21axPoints*14radPoints) * (1 + 1) + test2DLRMPJacobian(relModelFilePath, 7, 7, 2, 1); +} + +TEST_CASE("LRMP2D with two component linear binding Jacobian", "[LRMP2D],[DG],[DG2D],[UnitOp],[Jacobian],[ReleaseCI]") +{ + const std::string relModelFilePath = std::string("/data/model_LRMP2D_dynLin_2comp.json"); + + // This test might run out of memory due to the required AD directions: + // inletDof + (axPolyDeg + 1) * axNElem * (radPolyDeg + 1) * radNElem * (nComp + nParType * (nComp + nBound)) + // result here is 2*12radPoints + 1296 pure dofs (18axPoints*12radPoints) * (2 + 4) + test2DLRMPJacobian(relModelFilePath, 6, 6, 2, 1); +} + +TEST_CASE("LRMP2D sensitivity Jacobians", "[LRMP2D],[UnitOp],[Sensitivity],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG"); + + cadet::test::column::testFwdSensJacobians(jpp, 1e-4, 6e-7); +} + +TEST_CASE("LRMP2D consistent initialization with linear binding", "[LRMP2D],[ConsistentInit],[CI]") +{ + cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-12, 1e-12, 0, 0); + cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-12, 1e-12, 1, 0); + cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-12, 1e-12, 0, 1); + //cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-12, 1e-12, 1, 1); // @todo AD with req binding does not work +} + +//TEST_CASE("LRMP2D consistent initialization with SMA binding", "[LRMP2D],[ConsistentInit],[todo]") // todo fix (also doesnt work for other models) +//{ +// std::vector y(4 * 3 + 4 * 8 * 3 + 8 * 3 * 3 * (4 + 4) + 4 * 8 * 3, 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 * 3 + 4 * 8 * 3); +// cadet::test::util::repeat(y.data() + 4 * 3 + 4 * 8 * 3, bindingCell, 16, 3 * 8 * 3 / 2); +// cadet::test::util::populate(y.data() + 4 * 3 + 4 * 8 * 3 + 8 * 3 * 3 * (4 + 4), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 * 8 * 3); +// +// cadet::test::column::testConsistentInitializationSMABinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), 1e-14, 1e-5, 0, 0); +// cadet::test::column::testConsistentInitializationSMABinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), 1e-14, 1e-5, 1, 0); +// //cadet::test::column::testConsistentInitializationSMABinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), 1e-14, 1e-5, 0, 1); +// //cadet::test::column::testConsistentInitializationSMABinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), 1e-14, 1e-5, 1, 1); +//} + +TEST_CASE("LRMP2D consistent sensitivity initialization with linear binding", "[LRMP2D],[ConsistentInit],[Sensitivity],[CI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 2 * 3 + 2 * 8 * 3 + 8 * 3 * 3 * (2 + 2) + 2 * 8 * 3; + 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); + + // todo: kinetic binding doesnt work here + //cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), true, 1e-14, 0, 0); + cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), true, 1e-14, 1, 0); + //cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), true, 1e-14, 0, 1); + cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), true, 1e-14, 1, 1); +} + +TEST_CASE("LRMP2D consistent sensitivity initialization with SMA binding", "[LRMP2D],[ConsistentInit],[Sensitivity],[CI]") +{ + // Fill state vector with given initial values + const unsigned int numDofs = 4 * 3 + 4 * 8 * 3 + 8 * 3 * 3 * (4 + 4) + 4 * 8 * 3; + 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 * 3 + 4 * 8 * 3); + cadet::test::util::repeat(y.data() + 4 * 3 + 4 * 8 * 3, bindingCell, 8, 3 * 8 * 3); + cadet::test::util::populate(y.data() + 4 * 3 + 4 * 8 * 3 + 8 * 3 * 3 * (4 + 4), [](unsigned int idx) { return std::abs(std::sin(idx * 0.13)) + 1e-4; }, 4 * 8 * 3); + + cadet::test::util::populate(yDot.data(), [](unsigned int idx) { return std::abs(std::sin(idx * 0.9)) + 1e-4; }, numDofs); + + // todo: kinetic binding doesnt work here + //cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), false, 1e-9, 0, 0); + cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), false, 1e-9, 1, 0); + //cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), false, 1e-9, 0, 1); + cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", y.data(), yDot.data(), false, 1e-9, 1, 1); +} + +TEST_CASE("LRMP2D LWE one vs two identical particle types match", "[LRMP2D],[Simulation],[ParticleType],[CI]") +{ + cadet::test::particle::testOneVsTwoIdenticalParticleTypes("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-7, 5e-5); +} + +TEST_CASE("LRMP2D LWE separate identical particle types match", "[LRMP2D],[Simulation],[ParticleType],[CI]") +{ + cadet::test::particle::testSeparateIdenticalParticleTypes("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-7, 5e-5); +} + +TEST_CASE("LRMP2D linear binding single particle matches particle distribution", "[LRMP2D],[Simulation],[ParticleType],[CI]") +{ + cadet::test::particle::testLinearMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 5e-8, 5e-5); +} + +TEST_CASE("LRMP2D multiple particle types Jacobian analytic vs AD", "[LRMP2D],[Jacobian],[AD],[ParticleType],[ReleaseCI]") +{ + cadet::test::particle::testJacobianMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e10); // @todo figure out why FD Jacobian pattern comparison doesnt work but AD Jacobian comparison does +} + +TEST_CASE("LRMP2D multiple particle types time derivative Jacobian vs FD", "[LRMP2D],[UnitOp],[Residual],[Jacobian],[ParticleType],[CI]") +{ + cadet::test::particle::testTimeDerivativeJacobianMixedParticleTypesFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 1e-6, 0.0, 5e-3); +} + +TEST_CASE("LRMP2D linear binding single particle matches spatially dependent particle distribution", "[LRMP2D],[Simulation],[ParticleType],[CI]") +{ + cadet::test::particle::testLinearSpatiallyMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", 5e-8, 5e-5); +} + +TEST_CASE("LRMP2D dynamic reactions time derivative Jacobian vs FD bulk", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[CI]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", true, false, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D dynamic reactions time derivative Jacobian vs FD particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[CI]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", false, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D dynamic reactions time derivative Jacobian vs FD modified particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[CI]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", false, true, true, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D dynamic reactions time derivative Jacobian vs FD bulk and particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[CI]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", true, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[CI]") +{ + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG", true, true, true, 1e-6, 1e-14, 8e-4); +} + +inline cadet::JsonParameterProvider createColumnWithTwoCompLinearBindingThreeParticleTypes() +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("LUMPED_RATE_MODEL_WITH_PORES_2D", "DG"); + + 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("LRMP2D multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, false, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, true, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, false, 1e-6, 1e-14, 8e-4); +} + +TEST_CASE("LRMP2D multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[LRMP2D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +{ + cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBindingThreeParticleTypes(); + cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, true, 1e-6, 1e-14, 8e-4); +} \ No newline at end of file diff --git a/test/LumpedRateModelWithPoresDG.cpp b/test/LumpedRateModelWithPoresDG.cpp index c21993e08..07390685c 100644 --- a/test/LumpedRateModelWithPoresDG.cpp +++ b/test/LumpedRateModelWithPoresDG.cpp @@ -18,7 +18,7 @@ #include "JsonTestModels.hpp" #include "Utils.hpp" -TEST_CASE("LRMP_DG LWE forward vs backward flow", "[LRMP],[DG],[Simulation],[CI]") +TEST_CASE("LRMP_DG LWE forward vs backward flow", "[LRMP],[DG],[DG1D],[Simulation],[CI]") { cadet::test::column::DGparams disc; @@ -30,7 +30,7 @@ TEST_CASE("LRMP_DG LWE forward vs backward flow", "[LRMP],[DG],[Simulation],[CI] } } -TEST_CASE("LRMP_DG linear pulse vs analytic solution", "[LRMP],[DG],[Simulation],[Analytic],[CI]") +TEST_CASE("LRMP_DG linear pulse vs analytic solution", "[LRMP],[DG],[DG1D],[Simulation],[Analytic],[CI]") { cadet::test::column::DGparams disc; cadet::test::column::testAnalyticBenchmark("LUMPED_RATE_MODEL_WITH_PORES", "/data/lrmp-pulseBenchmark.data", true, true, disc, 6e-5, 1e-7); @@ -39,14 +39,14 @@ TEST_CASE("LRMP_DG linear pulse vs analytic solution", "[LRMP],[DG],[Simulation] cadet::test::column::testAnalyticBenchmark("LUMPED_RATE_MODEL_WITH_PORES", "/data/lrmp-pulseBenchmark.data", false, false, disc, 6e-5, 1e-7); } -TEST_CASE("LRMP_DG non-binding linear pulse vs analytic solution", "[LRMP],[DG],[Simulation],[Analytic],[NonBinding],[CI]") +TEST_CASE("LRMP_DG non-binding linear pulse vs analytic solution", "[LRMP],[DG],[DG1D],[Simulation],[Analytic],[NonBinding],[CI]") { cadet::test::column::DGparams disc; cadet::test::column::testAnalyticNonBindingBenchmark("LUMPED_RATE_MODEL_WITH_PORES", "/data/lrmp-nonBinding.data", true, disc, 6e-5, 1e-7); cadet::test::column::testAnalyticNonBindingBenchmark("LUMPED_RATE_MODEL_WITH_PORES", "/data/lrmp-nonBinding.data", false, disc, 6e-5, 1e-7); } -//TEST_CASE("LRMP_DG Jacobian forward vs backward flow", "[LRMP],[DG],[UnitOp],[Residual],[Jacobian],[AD],[fix]") +//TEST_CASE("LRMP_DG Jacobian forward vs backward flow", "[LRMP],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[AD],[fix]") //{ // cadet::test::column::DGparams disc; // @@ -58,7 +58,7 @@ TEST_CASE("LRMP_DG non-binding linear pulse vs analytic solution", "[LRMP],[DG], // } //} -TEST_CASE("LRMP_DG numerical Benchmark with parameter sensitivities for linear case", "[LRMP],[DG],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +TEST_CASE("LRMP_DG numerical Benchmark with parameter sensitivities for linear case", "[LRMP],[DG],[DG1D],[Simulation],[Reference],[Sensitivity],[CI]") { const std::string& modelFilePath = std::string("/data/model_LRMP_dynLin_1comp_benchmark1.json"); const std::string& refFilePath = std::string("/data/ref_LRMP_dynLin_1comp_sensbenchmark1_DG_P3Z8.h5"); @@ -69,7 +69,7 @@ TEST_CASE("LRMP_DG numerical Benchmark with parameter sensitivities for linear c cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "001", absTol, relTol, disc, true); } -TEST_CASE("LRMP_DG numerical Benchmark with parameter sensitivities for SMA LWE case", "[LRMP],[DG],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +TEST_CASE("LRMP_DG numerical Benchmark with parameter sensitivities for SMA LWE case", "[LRMP],[DG],[DG1D],[Simulation],[Reference],[Sensitivity],[CI]") { const std::string& modelFilePath = std::string("/data/model_LRMP_reqSMA_4comp_benchmark1.json"); const std::string& refFilePath = std::string("/data/ref_LRMP_reqSMA_4comp_sensbenchmark1_DG_P3Z8.h5"); @@ -80,12 +80,12 @@ TEST_CASE("LRMP_DG numerical Benchmark with parameter sensitivities for SMA LWE cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "000", absTol, relTol, disc, true); } -TEST_CASE("LRMP_DG time derivative Jacobian vs FD", "[LRMP],[DG],[UnitOp],[Residual],[Jacobian],[CI]") +TEST_CASE("LRMP_DG time derivative Jacobian vs FD", "[LRMP],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[CI]") { cadet::test::column::testTimeDerivativeJacobianFD("LUMPED_RATE_MODEL_WITH_PORES", "DG"); } -TEST_CASE("LRMP_DG sensitivity Jacobians", "[LRMP],[DG],[UnitOp],[Sensitivity],[CI]") +TEST_CASE("LRMP_DG sensitivity Jacobians", "[LRMP],[DG],[DG1D],[UnitOp],[Sensitivity],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("LUMPED_RATE_MODEL_WITH_PORES", "DG"); @@ -93,7 +93,7 @@ TEST_CASE("LRMP_DG sensitivity Jacobians", "[LRMP],[DG],[UnitOp],[Sensitivity],[ } //// todo fix: not just adjust tolerances as in FV but theres an actual error here: access violation in densematrix -//TEST_CASE("LRMP_DG forward sensitivity vs FD", "[LRMP],[DG],[Sensitivity],[Simulation],[todo]") +//TEST_CASE("LRMP_DG forward sensitivity vs FD", "[LRMP],[DG],[DG1D],[Sensitivity],[Simulation],[todo]") //{ // // 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 @@ -106,7 +106,7 @@ TEST_CASE("LRMP_DG sensitivity Jacobians", "[LRMP],[DG],[UnitOp],[Sensitivity],[ //} // todo fix: not just adjust tolerances as in FV but theres an actual error here: access violation in densematrix -//TEST_CASE("LRMP_DG forward sensitivity forward vs backward flow", "[LRMP],[DG],[Sensitivity],[Simulation],[todo]") +//TEST_CASE("LRMP_DG forward sensitivity forward vs backward flow", "[LRMP],[DG],[DG1D],[Sensitivity],[Simulation],[todo]") //{ // const double absTols[] = { 50.0, 2e-10, 1.0, 5e-7 }; // const double relTols[] = { 2e-4, 9e-6, 5e-7, 1e-7 }; @@ -115,7 +115,7 @@ TEST_CASE("LRMP_DG sensitivity Jacobians", "[LRMP],[DG],[UnitOp],[Sensitivity],[ //} // todo fix consistent initialization for AD with req binding -TEST_CASE("LRMP_DG consistent initialization with linear binding", "[LRMP],[DG],[ConsistentInit],[CI]") +TEST_CASE("LRMP_DG consistent initialization with linear binding", "[LRMP],[DG],[DG1D],[ConsistentInit],[CI]") { cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITH_PORES", "DG", 1e-12, 1e-12, 0, 0); cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITH_PORES", "DG", 1e-12, 1e-12, 1, 0); @@ -124,7 +124,7 @@ TEST_CASE("LRMP_DG consistent initialization with linear binding", "[LRMP],[DG], } ////// todo fix consistent initialization for SMA (initialization not completely correct; AD gives assertion error) -//TEST_CASE("LRMP_DG consistent initialization with SMA binding", "[LRMP],[DG],[ConsistentInit],[todo]") +//TEST_CASE("LRMP_DG consistent initialization with SMA binding", "[LRMP],[DG],[DG1D],[ConsistentInit],[todo]") //{ // std::vector y(4 + 4 * 16 + 16 * (4 + 4) + 4 * 16, 0.0); // const double bindingCell[] = { 1.2, 2.0, 1.0, 1.5, 840.0, 63.0, 3.0, 3.0, @@ -140,7 +140,7 @@ TEST_CASE("LRMP_DG consistent initialization with linear binding", "[LRMP],[DG], //} // todo fix kinetic binding sensitivity init -TEST_CASE("LRMP_DG consistent sensitivity initialization with linear binding", "[LRMP],[DG],[ConsistentInit],[Sensitivity],[CI]") +TEST_CASE("LRMP_DG consistent sensitivity initialization with linear binding", "[LRMP],[DG],[DG1D],[ConsistentInit],[Sensitivity],[CI]") { // Fill state vector with given initial values const unsigned int numDofs = 2 + 2 * 10 + 10 * (2 + 2); @@ -156,7 +156,7 @@ TEST_CASE("LRMP_DG consistent sensitivity initialization with linear binding", " } //// todo fix memory stuff (works for FV) -//TEST_CASE("LRMP_DG consistent sensitivity initialization with SMA binding", "[LRMP],[DG],[ConsistentInit],[Sensitivity],[todo]") +//TEST_CASE("LRMP_DG consistent sensitivity initialization with SMA binding", "[LRMP],[DG],[DG1D],[ConsistentInit],[Sensitivity],[todo]") //{ // // Fill state vector with given initial values // const unsigned int numDofs = 4 + 4 * 10 + 10 * (4 + 4); @@ -176,103 +176,103 @@ TEST_CASE("LRMP_DG consistent sensitivity initialization with linear binding", " // //cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITH_PORES", "DG", y.data(), yDot.data(), false, 1e-10, 1, 1); //} -TEST_CASE("LRMP_DG inlet DOF Jacobian", "[LRMP],[DG],[UnitOp],[Jacobian],[Inlet],[CI]") +TEST_CASE("LRMP_DG inlet DOF Jacobian", "[LRMP],[DG],[DG1D],[UnitOp],[Jacobian],[Inlet],[CI]") { cadet::test::column::testInletDofJacobian("LUMPED_RATE_MODEL_WITH_PORES", "DG"); } -TEST_CASE("LRMP_DG transport Jacobian", "[LRMP],[DG],[UnitOp],[Jacobian],[CI]") +TEST_CASE("LRMP_DG transport Jacobian", "[LRMP],[DG],[DG1D],[UnitOp],[Jacobian],[CI]") { cadet::JsonParameterProvider jpp = createColumnLinearBenchmark(false, true, "LUMPED_RATE_MODEL_WITH_PORES", "DG"); cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG with two component linear binding Jacobian", "[LRMP],[DG],[UnitOp],[Jacobian],[CI]") +TEST_CASE("LRMP_DG with two component linear binding Jacobian", "[LRMP],[DG],[DG1D],[UnitOp],[Jacobian],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("LUMPED_RATE_MODEL_WITH_PORES", "DG"); cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG LWE one vs two identical particle types match", "[LRMP],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("LRMP_DG LWE one vs two identical particle types match", "[LRMP],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testOneVsTwoIdenticalParticleTypes("LUMPED_RATE_MODEL_WITH_PORES", "DG", 2.2e-8, 6e-5); } -TEST_CASE("LRMP_DG LWE separate identical particle types match", "[LRMP],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("LRMP_DG LWE separate identical particle types match", "[LRMP],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testSeparateIdenticalParticleTypes("LUMPED_RATE_MODEL_WITH_PORES", "DG", 1e-12, 1e-12); } -TEST_CASE("LRMP_DG linear binding single particle matches particle distribution", "[LRMP],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("LRMP_DG linear binding single particle matches particle distribution", "[LRMP],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testLinearMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES", "DG", 5e-8, 5e-5); } -TEST_CASE("LRMP_DG multiple particle types Jacobian analytic vs AD", "[LRMP],[DG],[Jacobian],[AD],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multiple particle types Jacobian analytic vs AD", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ParticleType],[CI]") { cadet::test::particle::testJacobianMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES", "DG", 1e-11); } -TEST_CASE("LRMP_DG multiple particle types time derivative Jacobian vs FD", "[LRMP],[DG],[UnitOp],[Residual],[Jacobian],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multiple particle types time derivative Jacobian vs FD", "[LRMP],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[ParticleType],[CI]") { cadet::test::particle::testTimeDerivativeJacobianMixedParticleTypesFD("LUMPED_RATE_MODEL_WITH_PORES", "DG", 1e-6, 0.0, 9e-4); } -TEST_CASE("LRMP_DG multiple spatially dependent particle types Jacobian analytic vs AD", "[LRMP],[DG],[Jacobian],[AD],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multiple spatially dependent particle types Jacobian analytic vs AD", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ParticleType],[CI]") { cadet::test::particle::testJacobianSpatiallyMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES", "DG", std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG linear binding single particle matches spatially dependent particle distribution", "[LRMP],[DG],[Simulation],[ParticleType],[CI]") +TEST_CASE("LRMP_DG linear binding single particle matches spatially dependent particle distribution", "[LRMP],[DG],[DG1D],[Simulation],[ParticleType],[CI]") { cadet::test::particle::testLinearSpatiallyMixedParticleTypes("LUMPED_RATE_MODEL_WITH_PORES", "DG", 5e-8, 5e-5); } -TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD bulk", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD bulk", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITH_PORES", "DG", true, false, false, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITH_PORES", "DG", false, true, false, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD modified particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITH_PORES", "DG", false, true, true, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD bulk and particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD bulk and particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITH_PORES", "DG", true, true, false, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD bulk and modified particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions Jacobian vs AD bulk and modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITH_PORES", "DG", true, true, true, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD bulk", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD bulk", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES", "DG", true, false, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES", "DG", false, true, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD modified particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES", "DG", false, true, true, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD bulk and particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD bulk and particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES", "DG", true, true, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRMP_DG dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITH_PORES", "DG", true, true, true, 1e-6, 1e-14, 8e-4); } @@ -288,61 +288,61 @@ inline cadet::JsonParameterProvider createLRMPColumnWithTwoCompLinearBindingThre return jpp; } -TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD bulk", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD bulk", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, false, false, 1e-11); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, false, 1e-11); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD modified particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, false, true, true, 1e-11); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD bulk and particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD bulk and particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, false, 1e-11); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD bulk and modified particle", "[LRMP],[DG],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions Jacobian vs AD bulk and modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testUnitJacobianDynamicReactionsAD(jpp, true, true, true, 1e-11); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, false, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, false, true, true, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[LRMP],[DG],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") +TEST_CASE("LRMP_DG multi particle types dynamic reactions time derivative Jacobian vs FD bulk and modified particle", "[LRMP],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[ParticleType],[CI]") { cadet::JsonParameterProvider jpp = createLRMPColumnWithTwoCompLinearBindingThreeParticleTypes(); cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD(jpp, true, true, true, 1e-6, 1e-14, 8e-4); diff --git a/test/LumpedRateModelWithoutPoresDG.cpp b/test/LumpedRateModelWithoutPoresDG.cpp index bb5e1d9ea..2e32d742f 100644 --- a/test/LumpedRateModelWithoutPoresDG.cpp +++ b/test/LumpedRateModelWithoutPoresDG.cpp @@ -17,7 +17,7 @@ #include "Utils.hpp" #include "JsonTestModels.hpp" -TEST_CASE("LRM_DG LWE forward vs backward flow", "[LRM],[DG],[Simulation],[CI]") +TEST_CASE("LRM_DG LWE forward vs backward flow", "[LRM],[DG],[DG1D],[Simulation],[CI]") { cadet::test::column::DGparams disc; @@ -29,7 +29,7 @@ TEST_CASE("LRM_DG LWE forward vs backward flow", "[LRM],[DG],[Simulation],[CI]") } } -TEST_CASE("LRM_DG linear pulse vs analytic solution", "[LRM],[DG],[Simulation],[Analytic],[CI]") +TEST_CASE("LRM_DG linear pulse vs analytic solution", "[LRM],[DG],[DG1D],[Simulation],[Analytic],[CI]") { cadet::test::column::DGparams disc; cadet::test::column::testAnalyticBenchmark("LUMPED_RATE_MODEL_WITHOUT_PORES", "/data/lrm-pulseBenchmark.data", true, true, disc, 2e-5, 1e-7); @@ -38,14 +38,14 @@ TEST_CASE("LRM_DG linear pulse vs analytic solution", "[LRM],[DG],[Simulation],[ cadet::test::column::testAnalyticBenchmark("LUMPED_RATE_MODEL_WITHOUT_PORES", "/data/lrm-pulseBenchmark.data", false, false, disc, 2e-5, 1e-7); } -TEST_CASE("LRM_DG non-binding linear pulse vs analytic solution", "[LRM],[DG],[Simulation],[Analytic],[NonBinding],[CI]") +TEST_CASE("LRM_DG non-binding linear pulse vs analytic solution", "[LRM],[DG],[DG1D],[Simulation],[Analytic],[NonBinding],[CI]") { cadet::test::column::DGparams disc; cadet::test::column::testAnalyticNonBindingBenchmark("LUMPED_RATE_MODEL_WITHOUT_PORES", "/data/lrm-nonBinding.data", true, disc, 2e-5, 1e-7); cadet::test::column::testAnalyticNonBindingBenchmark("LUMPED_RATE_MODEL_WITHOUT_PORES", "/data/lrm-nonBinding.data", false, disc, 2e-5, 1e-7); } -//TEST_CASE("LRM_DG Jacobian forward vs backward flow", "[LRM],[DG],[UnitOp],[Residual],[Jacobian],[AD],[fix]") +//TEST_CASE("LRM_DG Jacobian forward vs backward flow", "[LRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[AD],[fix]") //{ // cadet::test::column::DGparams disc; // @@ -57,7 +57,7 @@ TEST_CASE("LRM_DG non-binding linear pulse vs analytic solution", "[LRM],[DG],[S // } //} -TEST_CASE("LRM_DG numerical Benchmark with parameter sensitivities for linear case", "[LRM],[DG],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +TEST_CASE("LRM_DG numerical Benchmark with parameter sensitivities for linear case", "[LRM],[DG],[DG1D],[Simulation],[Reference],[Sensitivity],[CI]") { const std::string& modelFilePath = std::string("/data/model_LRM_dynLin_1comp_benchmark1.json"); const std::string& refFilePath = std::string("/data/ref_LRM_dynLin_1comp_sensbenchmark1_DG_P3Z8.h5"); @@ -68,7 +68,7 @@ TEST_CASE("LRM_DG numerical Benchmark with parameter sensitivities for linear ca cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "001", absTol, relTol, disc, true); } -TEST_CASE("LRM_DG numerical Benchmark with parameter sensitivities for SMA LWE case", "[LRM],[DG],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server +TEST_CASE("LRM_DG numerical Benchmark with parameter sensitivities for SMA LWE case", "[LRM],[DG],[DG1D],[Simulation],[Reference],[Sensitivity]") // todo CI flag: currently only runs locally but fails on server { const std::string& modelFilePath = std::string("/data/model_LRM_reqSMA_4comp_benchmark1.json"); const std::string& refFilePath = std::string("/data/ref_LRM_reqSMA_4comp_sensbenchmark1_DG_P3Z8.h5"); @@ -79,19 +79,19 @@ TEST_CASE("LRM_DG numerical Benchmark with parameter sensitivities for SMA LWE c cadet::test::column::testReferenceBenchmark(modelFilePath, refFilePath, "000", absTol, relTol, disc, true); } -TEST_CASE("LRM_DG time derivative Jacobian vs FD", "[LRM],[DG],[UnitOp],[Residual],[Jacobian],[CI]") +TEST_CASE("LRM_DG time derivative Jacobian vs FD", "[LRM],[DG],[DG1D],[UnitOp],[Residual],[Jacobian],[CI]") { cadet::test::column::testTimeDerivativeJacobianFD("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG"); } -TEST_CASE("LRM_DG sensitivity Jacobians", "[LRM],[DG],[UnitOp],[Sensitivity],[CI]") +TEST_CASE("LRM_DG sensitivity Jacobians", "[LRM],[DG],[DG1D],[UnitOp],[Sensitivity],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG"); cadet::test::column::testFwdSensJacobians(jpp, 1e-4, 3e-7, 5e-4); } -//TEST_CASE("LRM_DG forward sensitivity vs FD", "[LRM],[DG],[Sensitivity],[Simulation]") // todo fix tolerances (also for FV) +//TEST_CASE("LRM_DG forward sensitivity vs FD", "[LRM],[DG],[DG1D],[Sensitivity],[Simulation]") // todo fix tolerances (also for FV) //{ // // 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 @@ -103,7 +103,7 @@ TEST_CASE("LRM_DG sensitivity Jacobians", "[LRM],[DG],[UnitOp],[Sensitivity],[CI // cadet::test::column::testFwdSensSolutionFD("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", false, fdStepSize, absTols, relTols, passRatio); //} -//TEST_CASE("LRM_DG forward sensitivity forward vs backward flow", "[LRM],[DG],[Sensitivity],[Simulation]") // todo fix (also for FV) tolerances? why is there a pass ratio here, shouldnt this be precise? +//TEST_CASE("LRM_DG forward sensitivity forward vs backward flow", "[LRM],[DG],[DG1D],[Sensitivity],[Simulation]") // todo fix (also for FV) 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 }; @@ -111,12 +111,12 @@ TEST_CASE("LRM_DG sensitivity Jacobians", "[LRM],[DG],[UnitOp],[Sensitivity],[CI // cadet::test::column::testFwdSensSolutionForwardBackward("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", absTols, relTols, passRatio); //} -TEST_CASE("LRM_DG consistent initialization with linear binding", "[LRM],[DG],[ConsistentInit],[CI]") +TEST_CASE("LRM_DG consistent initialization with linear binding", "[LRM],[DG],[DG1D],[ConsistentInit],[CI]") { cadet::test::column::testConsistentInitializationLinearBinding("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", 1e-12, 1e-12); } -//TEST_CASE("LRM_DG consistent initialization with SMA binding", "[LRM],[DG],[ConsistentInit]") // todo fix (also for FV) +//TEST_CASE("LRM_DG consistent initialization with SMA binding", "[LRM],[DG],[DG1D],[ConsistentInit]") // todo fix (also for FV) //{ // std::vector y(4 + 16 * (4 + 4), 0.0); // // Optimal values: @@ -130,7 +130,7 @@ TEST_CASE("LRM_DG consistent initialization with linear binding", "[LRM],[DG],[C // cadet::test::column::testConsistentInitializationSMABinding("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", y.data(), 1e-14, 1e-5); //} -TEST_CASE("LRM_DG consistent sensitivity initialization with linear binding", "[LRM],[DG],[ConsistentInit],[Sensitivity],[CI]") +TEST_CASE("LRM_DG consistent sensitivity initialization with linear binding", "[LRM],[DG],[DG1D],[ConsistentInit],[Sensitivity],[CI]") { // Fill state vector with given initial values const unsigned int numDofs = 4 + 10 * (4 + 4); @@ -143,7 +143,7 @@ TEST_CASE("LRM_DG consistent sensitivity initialization with linear binding", "[ } //// todo fix: sigsev read access violation when allocating _tempState = new double[numDofs()] in configure model discretization -//TEST_CASE("LRM_DG consistent sensitivity initialization with SMA binding", "[LRM],[DG],[ConsistentInit],[Sensitivity],[todo]") +//TEST_CASE("LRM_DG consistent sensitivity initialization with SMA binding", "[LRM],[DG],[DG1D],[ConsistentInit],[Sensitivity],[todo]") //{ // // Fill state vector with given initial values // const unsigned int numDofs = 4 + 10 * (4 + 4); @@ -159,39 +159,39 @@ TEST_CASE("LRM_DG consistent sensitivity initialization with linear binding", "[ // cadet::test::column::testConsistentInitializationSensitivity("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", y.data(), yDot.data(), false, 1e-10); //} -TEST_CASE("LRM_DG inlet DOF Jacobian", "[LRM],[DG],[UnitOp],[Jacobian],[Inlet],[CI]") +TEST_CASE("LRM_DG inlet DOF Jacobian", "[LRM],[DG],[DG1D],[UnitOp],[Jacobian],[Inlet],[CI]") { cadet::test::column::testInletDofJacobian("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG"); } -TEST_CASE("LRM_DG transport Jacobian", "[LRM],[DG],[UnitOp],[Jacobian],[CI]") +TEST_CASE("LRM_DG transport Jacobian", "[LRM],[DG],[DG1D],[UnitOp],[Jacobian],[CI]") { cadet::JsonParameterProvider jpp = createColumnLinearBenchmark(false, true, "LUMPED_RATE_MODEL_WITHOUT_PORES", "DG"); cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRM_DG with two component linear binding Jacobian", "[LRM],[DG],[UnitOp],[Jacobian],[CI]") +TEST_CASE("LRM_DG with two component linear binding Jacobian", "[LRM],[DG],[DG1D],[UnitOp],[Jacobian],[CI]") { cadet::JsonParameterProvider jpp = createColumnWithTwoCompLinearBinding("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG"); cadet::test::column::testJacobianAD(jpp, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRM_DG dynamic reactions Jacobian vs AD bulk", "[LRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRM_DG dynamic reactions Jacobian vs AD bulk", "[LRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", true, false, false, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRM_DG dynamic reactions Jacobian vs AD modified bulk", "[LRM],[DG],[Jacobian],[AD],[ReactionModel],[CI]") +TEST_CASE("LRM_DG dynamic reactions Jacobian vs AD modified bulk", "[LRM],[DG],[DG1D],[Jacobian],[AD],[ReactionModel],[CI]") { cadet::test::reaction::testUnitJacobianDynamicReactionsAD("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", true, false, true, std::numeric_limits::epsilon() * 100.0); } -TEST_CASE("LRM_DG dynamic reactions time derivative Jacobian vs FD bulk", "[LRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRM_DG dynamic reactions time derivative Jacobian vs FD bulk", "[LRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", true, false, false, 1e-6, 1e-14, 8e-4); } -TEST_CASE("LRM_DG dynamic reactions time derivative Jacobian vs FD modified bulk", "[LRM],[DG],[Jacobian],[Residual],[ReactionModel],[CI]") +TEST_CASE("LRM_DG dynamic reactions time derivative Jacobian vs FD modified bulk", "[LRM],[DG],[DG1D],[Jacobian],[Residual],[ReactionModel],[CI]") { cadet::test::reaction::testTimeDerivativeJacobianDynamicReactionsFD("LUMPED_RATE_MODEL_WITHOUT_PORES", "DG", true, false, true, 1e-6, 1e-14, 8e-4); } diff --git a/test/ParticleHelper.cpp b/test/ParticleHelper.cpp index 6daa8d697..9f05d375d 100644 --- a/test/ParticleHelper.cpp +++ b/test/ParticleHelper.cpp @@ -205,13 +205,27 @@ namespace particle unsigned int nRad = 1; { auto ds = cadet::test::util::makeOptionalGroupScope(jpp, "discretization"); + + bool DGmethod = false; if (jpp.exists("SPATIAL_METHOD")) - nCol = jpp.getString("SPATIAL_METHOD") == "DG" ? (jpp.getInt("POLYDEG") + 1) * jpp.getInt("NELEM") : jpp.getInt("NCOL"); + DGmethod = jpp.getString("SPATIAL_METHOD") == "DG"; + + if (DGmethod) + { + if (jpp.exists("AX_POLYDEG")) + { + nCol = (jpp.getInt("AX_POLYDEG") + 1) * jpp.getInt("AX_NELEM"); + nRad = (jpp.getInt("RAD_POLYDEG") + 1) * jpp.getInt("RAD_NELEM"); + } + else + nCol = (jpp.getInt("POLYDEG") + 1) * jpp.getInt("NELEM"); + } else + { nCol = jpp.getInt("NCOL"); - - if (jpp.exists("NRAD")) - nRad = jpp.getInt("NRAD"); + if (jpp.exists("NRAD")) + nRad = jpp.getInt("NRAD"); + } } const double baseFrac[] = {0.2, 0.45, 0.35}; diff --git a/test/data/model_LRMP2D_bulkTransport_1comp.json b/test/data/model_LRMP2D_bulkTransport_1comp.json new file mode 100644 index 000000000..0c1d28d64 --- /dev/null +++ b/test/data/model_LRMP2D_bulkTransport_1comp.json @@ -0,0 +1,1709 @@ +{ + "model": { + "NUNITS": 2, + "connections": { + "CONNECTIONS_INCLUDE_PORTS": 1, + "NSWITCHES": 1, + "switch_000": { + "CONNECTIONS": [ + 1.0, + 0.0, + 0.0, + 0.0, + -1.0, + -1.0, + 4.1773364815701774e-11, + 1.0, + 0.0, + 0.0, + 1.0, + -1.0, + -1.0, + 1.2532009444710534e-10, + 1.0, + 0.0, + 0.0, + 2.0, + -1.0, + -1.0, + 2.088668240785089e-10, + 1.0, + 0.0, + 0.0, + 3.0, + -1.0, + -1.0, + 2.9241355370991246e-10, + 1.0, + 0.0, + 0.0, + 4.0, + -1.0, + -1.0, + 3.759602833413161e-10, + 1.0, + 0.0, + 0.0, + 5.0, + -1.0, + -1.0, + 4.5950701297271953e-10, + 1.0, + 0.0, + 0.0, + 6.0, + -1.0, + -1.0, + 5.430537426041233e-10, + 1.0, + 0.0, + 0.0, + 7.0, + -1.0, + -1.0, + 6.266004722355263e-10, + 1.0, + 0.0, + 0.0, + 8.0, + -1.0, + -1.0, + 7.101472018669311e-10, + 1.0, + 0.0, + 0.0, + 9.0, + -1.0, + -1.0, + 7.936939314983349e-10, + 1.0, + 0.0, + 0.0, + 10.0, + -1.0, + -1.0, + 8.772406611297365e-10, + 1.0, + 0.0, + 0.0, + 11.0, + -1.0, + -1.0, + 9.607873907611417e-10, + 1.0, + 0.0, + 0.0, + 12.0, + -1.0, + -1.0, + 1.044334120392546e-09, + 1.0, + 0.0, + 0.0, + 13.0, + -1.0, + -1.0, + 1.1278808500239482e-09 + ], + "SECTION": 0 + } + }, + "solver": { + "GS_TYPE": 1, + "MAX_KRYLOV": 0, + "MAX_RESTARTS": 10, + "SCHUR_SAFETY": 1e-08 + }, + "unit_000": { + "ADSORPTION_MODEL": "NONE", + "COL_DISPERSION": 5.75e-08, + "COL_DISPERSION_RADIAL": 5e-08, + "COL_LENGTH": 0.014, + "COL_POROSITY": 0.37, + "COL_RADIUS": 0.0035, + "CROSS_SECTION_AREA": 3.848451000647497e-05, + "FILM_DIFFUSION": 0.0, + "INIT_C": [ + 0 + ], + "INIT_CP": [ + 0 + ], + "INIT_Q": [ + 0 + ], + "NBOUND": 0, + "NCOMP": 1, + "NPARTYPE": 1, + "PAR_POROSITY": 0.75, + "PAR_RADIUS": 4.5e-05, + "PAR_TYPE_VOLFRAC": 1.0, + "PORTS": 14, + "UNIT_TYPE": "LUMPED_RATE_MODEL_WITH_PORES_2D", + "VELOCITY": 0.000575, + "discretization": { + "AX_NELEM": 7, + "AX_POLYDEG": 2, + "RADIAL_DISC_TYPE": "EQUIDISTANT", + "RAD_NELEM": 7, + "RAD_POLYDEG": 1, + "SPATIAL_METHOD": "DG", + "USE_ANALYTIC_JACOBIAN": true + } + }, + "unit_001": { + "INLET_TYPE": "PIECEWISE_CUBIC_POLY", + "NCOMP": 1, + "PORTS": 1, + "UNIT_TYPE": "INLET", + "sec_000": { + "CONST_COEFF": 1.0 + }, + "sec_001": { + "CONST_COEFF": [ + 0.0 + ] + } + } + }, + "return": { + "SPLIT_COMPONENTS_DATA": 0, + "SPLIT_PORTS_DATA": 1, + "unit_000": { + "WRITE_COORDINATES": 1, + "WRITE_SENS_OUTLET": 0, + "WRITE_SOLUTION_BULK": 1, + "WRITE_SOLUTION_FLUX": 0, + "WRITE_SOLUTION_INLET": 0, + "WRITE_SOLUTION_OUTLET": 1, + "WRITE_SOLUTION_PARTICLE": 0, + "WRITE_SOLUTION_SOLID": 0 + } + }, + "solver": { + "CONSISTENT_INIT_MODE": 3, + "NTHREADS": -1, + "USER_SOLUTION_TIMES": [ + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + 26.0, + 27.0, + 28.0, + 29.0, + 30.0, + 31.0, + 32.0, + 33.0, + 34.0, + 35.0, + 36.0, + 37.0, + 38.0, + 39.0, + 40.0, + 41.0, + 42.0, + 43.0, + 44.0, + 45.0, + 46.0, + 47.0, + 48.0, + 49.0, + 50.0, + 51.0, + 52.0, + 53.0, + 54.0, + 55.0, + 56.0, + 57.0, + 58.0, + 59.0, + 60.0, + 61.0, + 62.0, + 63.0, + 64.0, + 65.0, + 66.0, + 67.0, + 68.0, + 69.0, + 70.0, + 71.0, + 72.0, + 73.0, + 74.0, + 75.0, + 76.0, + 77.0, + 78.0, + 79.0, + 80.0, + 81.0, + 82.0, + 83.0, + 84.0, + 85.0, + 86.0, + 87.0, + 88.0, + 89.0, + 90.0, + 91.0, + 92.0, + 93.0, + 94.0, + 95.0, + 96.0, + 97.0, + 98.0, + 99.0, + 100.0, + 101.0, + 102.0, + 103.0, + 104.0, + 105.0, + 106.0, + 107.0, + 108.0, + 109.0, + 110.0, + 111.0, + 112.0, + 113.0, + 114.0, + 115.0, + 116.0, + 117.0, + 118.0, + 119.0, + 120.0, + 121.0, + 122.0, + 123.0, + 124.0, + 125.0, + 126.0, + 127.0, + 128.0, + 129.0, + 130.0, + 131.0, + 132.0, + 133.0, + 134.0, + 135.0, + 136.0, + 137.0, + 138.0, + 139.0, + 140.0, + 141.0, + 142.0, + 143.0, + 144.0, + 145.0, + 146.0, + 147.0, + 148.0, + 149.0, + 150.0, + 151.0, + 152.0, + 153.0, + 154.0, + 155.0, + 156.0, + 157.0, + 158.0, + 159.0, + 160.0, + 161.0, + 162.0, + 163.0, + 164.0, + 165.0, + 166.0, + 167.0, + 168.0, + 169.0, + 170.0, + 171.0, + 172.0, + 173.0, + 174.0, + 175.0, + 176.0, + 177.0, + 178.0, + 179.0, + 180.0, + 181.0, + 182.0, + 183.0, + 184.0, + 185.0, + 186.0, + 187.0, + 188.0, + 189.0, + 190.0, + 191.0, + 192.0, + 193.0, + 194.0, + 195.0, + 196.0, + 197.0, + 198.0, + 199.0, + 200.0, + 201.0, + 202.0, + 203.0, + 204.0, + 205.0, + 206.0, + 207.0, + 208.0, + 209.0, + 210.0, + 211.0, + 212.0, + 213.0, + 214.0, + 215.0, + 216.0, + 217.0, + 218.0, + 219.0, + 220.0, + 221.0, + 222.0, + 223.0, + 224.0, + 225.0, + 226.0, + 227.0, + 228.0, + 229.0, + 230.0, + 231.0, + 232.0, + 233.0, + 234.0, + 235.0, + 236.0, + 237.0, + 238.0, + 239.0, + 240.0, + 241.0, + 242.0, + 243.0, + 244.0, + 245.0, + 246.0, + 247.0, + 248.0, + 249.0, + 250.0, + 251.0, + 252.0, + 253.0, + 254.0, + 255.0, + 256.0, + 257.0, + 258.0, + 259.0, + 260.0, + 261.0, + 262.0, + 263.0, + 264.0, + 265.0, + 266.0, + 267.0, + 268.0, + 269.0, + 270.0, + 271.0, + 272.0, + 273.0, + 274.0, + 275.0, + 276.0, + 277.0, + 278.0, + 279.0, + 280.0, + 281.0, + 282.0, + 283.0, + 284.0, + 285.0, + 286.0, + 287.0, + 288.0, + 289.0, + 290.0, + 291.0, + 292.0, + 293.0, + 294.0, + 295.0, + 296.0, + 297.0, + 298.0, + 299.0, + 300.0, + 301.0, + 302.0, + 303.0, + 304.0, + 305.0, + 306.0, + 307.0, + 308.0, + 309.0, + 310.0, + 311.0, + 312.0, + 313.0, + 314.0, + 315.0, + 316.0, + 317.0, + 318.0, + 319.0, + 320.0, + 321.0, + 322.0, + 323.0, + 324.0, + 325.0, + 326.0, + 327.0, + 328.0, + 329.0, + 330.0, + 331.0, + 332.0, + 333.0, + 334.0, + 335.0, + 336.0, + 337.0, + 338.0, + 339.0, + 340.0, + 341.0, + 342.0, + 343.0, + 344.0, + 345.0, + 346.0, + 347.0, + 348.0, + 349.0, + 350.0, + 351.0, + 352.0, + 353.0, + 354.0, + 355.0, + 356.0, + 357.0, + 358.0, + 359.0, + 360.0, + 361.0, + 362.0, + 363.0, + 364.0, + 365.0, + 366.0, + 367.0, + 368.0, + 369.0, + 370.0, + 371.0, + 372.0, + 373.0, + 374.0, + 375.0, + 376.0, + 377.0, + 378.0, + 379.0, + 380.0, + 381.0, + 382.0, + 383.0, + 384.0, + 385.0, + 386.0, + 387.0, + 388.0, + 389.0, + 390.0, + 391.0, + 392.0, + 393.0, + 394.0, + 395.0, + 396.0, + 397.0, + 398.0, + 399.0, + 400.0, + 401.0, + 402.0, + 403.0, + 404.0, + 405.0, + 406.0, + 407.0, + 408.0, + 409.0, + 410.0, + 411.0, + 412.0, + 413.0, + 414.0, + 415.0, + 416.0, + 417.0, + 418.0, + 419.0, + 420.0, + 421.0, + 422.0, + 423.0, + 424.0, + 425.0, + 426.0, + 427.0, + 428.0, + 429.0, + 430.0, + 431.0, + 432.0, + 433.0, + 434.0, + 435.0, + 436.0, + 437.0, + 438.0, + 439.0, + 440.0, + 441.0, + 442.0, + 443.0, + 444.0, + 445.0, + 446.0, + 447.0, + 448.0, + 449.0, + 450.0, + 451.0, + 452.0, + 453.0, + 454.0, + 455.0, + 456.0, + 457.0, + 458.0, + 459.0, + 460.0, + 461.0, + 462.0, + 463.0, + 464.0, + 465.0, + 466.0, + 467.0, + 468.0, + 469.0, + 470.0, + 471.0, + 472.0, + 473.0, + 474.0, + 475.0, + 476.0, + 477.0, + 478.0, + 479.0, + 480.0, + 481.0, + 482.0, + 483.0, + 484.0, + 485.0, + 486.0, + 487.0, + 488.0, + 489.0, + 490.0, + 491.0, + 492.0, + 493.0, + 494.0, + 495.0, + 496.0, + 497.0, + 498.0, + 499.0, + 500.0, + 501.0, + 502.0, + 503.0, + 504.0, + 505.0, + 506.0, + 507.0, + 508.0, + 509.0, + 510.0, + 511.0, + 512.0, + 513.0, + 514.0, + 515.0, + 516.0, + 517.0, + 518.0, + 519.0, + 520.0, + 521.0, + 522.0, + 523.0, + 524.0, + 525.0, + 526.0, + 527.0, + 528.0, + 529.0, + 530.0, + 531.0, + 532.0, + 533.0, + 534.0, + 535.0, + 536.0, + 537.0, + 538.0, + 539.0, + 540.0, + 541.0, + 542.0, + 543.0, + 544.0, + 545.0, + 546.0, + 547.0, + 548.0, + 549.0, + 550.0, + 551.0, + 552.0, + 553.0, + 554.0, + 555.0, + 556.0, + 557.0, + 558.0, + 559.0, + 560.0, + 561.0, + 562.0, + 563.0, + 564.0, + 565.0, + 566.0, + 567.0, + 568.0, + 569.0, + 570.0, + 571.0, + 572.0, + 573.0, + 574.0, + 575.0, + 576.0, + 577.0, + 578.0, + 579.0, + 580.0, + 581.0, + 582.0, + 583.0, + 584.0, + 585.0, + 586.0, + 587.0, + 588.0, + 589.0, + 590.0, + 591.0, + 592.0, + 593.0, + 594.0, + 595.0, + 596.0, + 597.0, + 598.0, + 599.0, + 600.0, + 601.0, + 602.0, + 603.0, + 604.0, + 605.0, + 606.0, + 607.0, + 608.0, + 609.0, + 610.0, + 611.0, + 612.0, + 613.0, + 614.0, + 615.0, + 616.0, + 617.0, + 618.0, + 619.0, + 620.0, + 621.0, + 622.0, + 623.0, + 624.0, + 625.0, + 626.0, + 627.0, + 628.0, + 629.0, + 630.0, + 631.0, + 632.0, + 633.0, + 634.0, + 635.0, + 636.0, + 637.0, + 638.0, + 639.0, + 640.0, + 641.0, + 642.0, + 643.0, + 644.0, + 645.0, + 646.0, + 647.0, + 648.0, + 649.0, + 650.0, + 651.0, + 652.0, + 653.0, + 654.0, + 655.0, + 656.0, + 657.0, + 658.0, + 659.0, + 660.0, + 661.0, + 662.0, + 663.0, + 664.0, + 665.0, + 666.0, + 667.0, + 668.0, + 669.0, + 670.0, + 671.0, + 672.0, + 673.0, + 674.0, + 675.0, + 676.0, + 677.0, + 678.0, + 679.0, + 680.0, + 681.0, + 682.0, + 683.0, + 684.0, + 685.0, + 686.0, + 687.0, + 688.0, + 689.0, + 690.0, + 691.0, + 692.0, + 693.0, + 694.0, + 695.0, + 696.0, + 697.0, + 698.0, + 699.0, + 700.0, + 701.0, + 702.0, + 703.0, + 704.0, + 705.0, + 706.0, + 707.0, + 708.0, + 709.0, + 710.0, + 711.0, + 712.0, + 713.0, + 714.0, + 715.0, + 716.0, + 717.0, + 718.0, + 719.0, + 720.0, + 721.0, + 722.0, + 723.0, + 724.0, + 725.0, + 726.0, + 727.0, + 728.0, + 729.0, + 730.0, + 731.0, + 732.0, + 733.0, + 734.0, + 735.0, + 736.0, + 737.0, + 738.0, + 739.0, + 740.0, + 741.0, + 742.0, + 743.0, + 744.0, + 745.0, + 746.0, + 747.0, + 748.0, + 749.0, + 750.0, + 751.0, + 752.0, + 753.0, + 754.0, + 755.0, + 756.0, + 757.0, + 758.0, + 759.0, + 760.0, + 761.0, + 762.0, + 763.0, + 764.0, + 765.0, + 766.0, + 767.0, + 768.0, + 769.0, + 770.0, + 771.0, + 772.0, + 773.0, + 774.0, + 775.0, + 776.0, + 777.0, + 778.0, + 779.0, + 780.0, + 781.0, + 782.0, + 783.0, + 784.0, + 785.0, + 786.0, + 787.0, + 788.0, + 789.0, + 790.0, + 791.0, + 792.0, + 793.0, + 794.0, + 795.0, + 796.0, + 797.0, + 798.0, + 799.0, + 800.0, + 801.0, + 802.0, + 803.0, + 804.0, + 805.0, + 806.0, + 807.0, + 808.0, + 809.0, + 810.0, + 811.0, + 812.0, + 813.0, + 814.0, + 815.0, + 816.0, + 817.0, + 818.0, + 819.0, + 820.0, + 821.0, + 822.0, + 823.0, + 824.0, + 825.0, + 826.0, + 827.0, + 828.0, + 829.0, + 830.0, + 831.0, + 832.0, + 833.0, + 834.0, + 835.0, + 836.0, + 837.0, + 838.0, + 839.0, + 840.0, + 841.0, + 842.0, + 843.0, + 844.0, + 845.0, + 846.0, + 847.0, + 848.0, + 849.0, + 850.0, + 851.0, + 852.0, + 853.0, + 854.0, + 855.0, + 856.0, + 857.0, + 858.0, + 859.0, + 860.0, + 861.0, + 862.0, + 863.0, + 864.0, + 865.0, + 866.0, + 867.0, + 868.0, + 869.0, + 870.0, + 871.0, + 872.0, + 873.0, + 874.0, + 875.0, + 876.0, + 877.0, + 878.0, + 879.0, + 880.0, + 881.0, + 882.0, + 883.0, + 884.0, + 885.0, + 886.0, + 887.0, + 888.0, + 889.0, + 890.0, + 891.0, + 892.0, + 893.0, + 894.0, + 895.0, + 896.0, + 897.0, + 898.0, + 899.0, + 900.0, + 901.0, + 902.0, + 903.0, + 904.0, + 905.0, + 906.0, + 907.0, + 908.0, + 909.0, + 910.0, + 911.0, + 912.0, + 913.0, + 914.0, + 915.0, + 916.0, + 917.0, + 918.0, + 919.0, + 920.0, + 921.0, + 922.0, + 923.0, + 924.0, + 925.0, + 926.0, + 927.0, + 928.0, + 929.0, + 930.0, + 931.0, + 932.0, + 933.0, + 934.0, + 935.0, + 936.0, + 937.0, + 938.0, + 939.0, + 940.0, + 941.0, + 942.0, + 943.0, + 944.0, + 945.0, + 946.0, + 947.0, + 948.0, + 949.0, + 950.0, + 951.0, + 952.0, + 953.0, + 954.0, + 955.0, + 956.0, + 957.0, + 958.0, + 959.0, + 960.0, + 961.0, + 962.0, + 963.0, + 964.0, + 965.0, + 966.0, + 967.0, + 968.0, + 969.0, + 970.0, + 971.0, + 972.0, + 973.0, + 974.0, + 975.0, + 976.0, + 977.0, + 978.0, + 979.0, + 980.0, + 981.0, + 982.0, + 983.0, + 984.0, + 985.0, + 986.0, + 987.0, + 988.0, + 989.0, + 990.0, + 991.0, + 992.0, + 993.0, + 994.0, + 995.0, + 996.0, + 997.0, + 998.0, + 999.0, + 1000.0, + 1001.0, + 1002.0, + 1003.0, + 1004.0, + 1005.0, + 1006.0, + 1007.0, + 1008.0, + 1009.0, + 1010.0, + 1011.0, + 1012.0, + 1013.0, + 1014.0, + 1015.0, + 1016.0, + 1017.0, + 1018.0, + 1019.0, + 1020.0, + 1021.0, + 1022.0, + 1023.0, + 1024.0, + 1025.0, + 1026.0, + 1027.0, + 1028.0, + 1029.0, + 1030.0, + 1031.0, + 1032.0, + 1033.0, + 1034.0, + 1035.0, + 1036.0, + 1037.0, + 1038.0, + 1039.0, + 1040.0, + 1041.0, + 1042.0, + 1043.0, + 1044.0, + 1045.0, + 1046.0, + 1047.0, + 1048.0, + 1049.0, + 1050.0, + 1051.0, + 1052.0, + 1053.0, + 1054.0, + 1055.0, + 1056.0, + 1057.0, + 1058.0, + 1059.0, + 1060.0, + 1061.0, + 1062.0, + 1063.0, + 1064.0, + 1065.0, + 1066.0, + 1067.0, + 1068.0, + 1069.0, + 1070.0, + 1071.0, + 1072.0, + 1073.0, + 1074.0, + 1075.0, + 1076.0, + 1077.0, + 1078.0, + 1079.0, + 1080.0, + 1081.0, + 1082.0, + 1083.0, + 1084.0, + 1085.0, + 1086.0, + 1087.0, + 1088.0, + 1089.0, + 1090.0, + 1091.0, + 1092.0, + 1093.0, + 1094.0, + 1095.0, + 1096.0, + 1097.0, + 1098.0, + 1099.0, + 1100.0, + 1101.0, + 1102.0, + 1103.0, + 1104.0, + 1105.0, + 1106.0, + 1107.0, + 1108.0, + 1109.0, + 1110.0, + 1111.0, + 1112.0, + 1113.0, + 1114.0, + 1115.0, + 1116.0, + 1117.0, + 1118.0, + 1119.0, + 1120.0, + 1121.0, + 1122.0, + 1123.0, + 1124.0, + 1125.0, + 1126.0, + 1127.0, + 1128.0, + 1129.0, + 1130.0, + 1131.0, + 1132.0, + 1133.0, + 1134.0, + 1135.0, + 1136.0, + 1137.0, + 1138.0, + 1139.0, + 1140.0, + 1141.0, + 1142.0, + 1143.0, + 1144.0, + 1145.0, + 1146.0, + 1147.0, + 1148.0, + 1149.0, + 1150.0, + 1151.0, + 1152.0, + 1153.0, + 1154.0, + 1155.0, + 1156.0, + 1157.0, + 1158.0, + 1159.0, + 1160.0, + 1161.0, + 1162.0, + 1163.0, + 1164.0, + 1165.0, + 1166.0, + 1167.0, + 1168.0, + 1169.0, + 1170.0, + 1171.0, + 1172.0, + 1173.0, + 1174.0, + 1175.0, + 1176.0, + 1177.0, + 1178.0, + 1179.0, + 1180.0, + 1181.0, + 1182.0, + 1183.0, + 1184.0, + 1185.0, + 1186.0, + 1187.0, + 1188.0, + 1189.0, + 1190.0, + 1191.0, + 1192.0, + 1193.0, + 1194.0, + 1195.0, + 1196.0, + 1197.0, + 1198.0, + 1199.0, + 1200.0, + 1201.0, + 1202.0, + 1203.0, + 1204.0, + 1205.0, + 1206.0, + 1207.0, + 1208.0, + 1209.0, + 1210.0, + 1211.0, + 1212.0, + 1213.0, + 1214.0, + 1215.0, + 1216.0, + 1217.0, + 1218.0, + 1219.0, + 1220.0, + 1221.0, + 1222.0, + 1223.0, + 1224.0, + 1225.0, + 1226.0, + 1227.0, + 1228.0, + 1229.0, + 1230.0, + 1231.0, + 1232.0, + 1233.0, + 1234.0, + 1235.0, + 1236.0, + 1237.0, + 1238.0, + 1239.0, + 1240.0, + 1241.0, + 1242.0, + 1243.0, + 1244.0, + 1245.0, + 1246.0, + 1247.0, + 1248.0, + 1249.0, + 1250.0, + 1251.0, + 1252.0, + 1253.0, + 1254.0, + 1255.0, + 1256.0, + 1257.0, + 1258.0, + 1259.0, + 1260.0, + 1261.0, + 1262.0, + 1263.0, + 1264.0, + 1265.0, + 1266.0, + 1267.0, + 1268.0, + 1269.0, + 1270.0, + 1271.0, + 1272.0, + 1273.0, + 1274.0, + 1275.0, + 1276.0, + 1277.0, + 1278.0, + 1279.0, + 1280.0, + 1281.0, + 1282.0, + 1283.0, + 1284.0, + 1285.0, + 1286.0, + 1287.0, + 1288.0, + 1289.0, + 1290.0, + 1291.0, + 1292.0, + 1293.0, + 1294.0, + 1295.0, + 1296.0, + 1297.0, + 1298.0, + 1299.0, + 1300.0, + 1301.0, + 1302.0, + 1303.0, + 1304.0, + 1305.0, + 1306.0, + 1307.0, + 1308.0, + 1309.0, + 1310.0, + 1311.0, + 1312.0, + 1313.0, + 1314.0, + 1315.0, + 1316.0, + 1317.0, + 1318.0, + 1319.0, + 1320.0, + 1321.0, + 1322.0, + 1323.0, + 1324.0, + 1325.0, + 1326.0, + 1327.0, + 1328.0, + 1329.0, + 1330.0, + 1331.0, + 1332.0, + 1333.0, + 1334.0, + 1335.0, + 1336.0, + 1337.0, + 1338.0, + 1339.0, + 1340.0, + 1341.0, + 1342.0, + 1343.0, + 1344.0, + 1345.0, + 1346.0, + 1347.0, + 1348.0, + 1349.0, + 1350.0, + 1351.0, + 1352.0, + 1353.0, + 1354.0, + 1355.0, + 1356.0, + 1357.0, + 1358.0, + 1359.0, + 1360.0, + 1361.0, + 1362.0, + 1363.0, + 1364.0, + 1365.0, + 1366.0, + 1367.0, + 1368.0, + 1369.0, + 1370.0, + 1371.0, + 1372.0, + 1373.0, + 1374.0, + 1375.0, + 1376.0, + 1377.0, + 1378.0, + 1379.0, + 1380.0, + 1381.0, + 1382.0, + 1383.0, + 1384.0, + 1385.0, + 1386.0, + 1387.0, + 1388.0, + 1389.0, + 1390.0, + 1391.0, + 1392.0, + 1393.0, + 1394.0, + 1395.0, + 1396.0, + 1397.0, + 1398.0, + 1399.0, + 1400.0, + 1401.0, + 1402.0, + 1403.0, + 1404.0, + 1405.0, + 1406.0, + 1407.0, + 1408.0, + 1409.0, + 1410.0, + 1411.0, + 1412.0, + 1413.0, + 1414.0, + 1415.0, + 1416.0, + 1417.0, + 1418.0, + 1419.0, + 1420.0, + 1421.0, + 1422.0, + 1423.0, + 1424.0, + 1425.0, + 1426.0, + 1427.0, + 1428.0, + 1429.0, + 1430.0, + 1431.0, + 1432.0, + 1433.0, + 1434.0, + 1435.0, + 1436.0, + 1437.0, + 1438.0, + 1439.0, + 1440.0, + 1441.0, + 1442.0, + 1443.0, + 1444.0, + 1445.0, + 1446.0, + 1447.0, + 1448.0, + 1449.0, + 1450.0, + 1451.0, + 1452.0, + 1453.0, + 1454.0, + 1455.0, + 1456.0, + 1457.0, + 1458.0, + 1459.0, + 1460.0, + 1461.0, + 1462.0, + 1463.0, + 1464.0, + 1465.0, + 1466.0, + 1467.0, + 1468.0, + 1469.0, + 1470.0, + 1471.0, + 1472.0, + 1473.0, + 1474.0, + 1475.0, + 1476.0, + 1477.0, + 1478.0, + 1479.0, + 1480.0, + 1481.0, + 1482.0, + 1483.0, + 1484.0, + 1485.0, + 1486.0, + 1487.0, + 1488.0, + 1489.0, + 1490.0, + 1491.0, + 1492.0, + 1493.0, + 1494.0, + 1495.0, + 1496.0, + 1497.0, + 1498.0, + 1499.0, + 1500.0 + ], + "sections": { + "NSEC": 2, + "SECTION_CONTINUITY": [ + 0 + ], + "SECTION_TIMES": [ + 0.0, + 10.0, + 1500.0 + ] + }, + "time_integrator": { + "ABSTOL": 1e-06, + "ALGTOL": 1e-10, + "INIT_STEP_SIZE": 1e-06, + "MAX_STEPS": 1000000, + "RELTOL": 1e-06, + "USE_MODIFIED_NEWTON": 0 + } + } +} \ No newline at end of file diff --git a/test/data/model_LRMP2D_dynLin_2comp.json b/test/data/model_LRMP2D_dynLin_2comp.json new file mode 100644 index 000000000..3617e2896 --- /dev/null +++ b/test/data/model_LRMP2D_dynLin_2comp.json @@ -0,0 +1,1717 @@ +{ + "model": { + "NUNITS": 2, + "connections": { + "CONNECTIONS_INCLUDE_PORTS": 1, + "NSWITCHES": 1, + "switch_000": { + "CONNECTIONS": [ + 1.0, + 0.0, + 0.0, + 0.0, + -1.0, + -1.0, + 4.1773364815701774e-11, + 1.0, + 0.0, + 0.0, + 1.0, + -1.0, + -1.0, + 1.2532009444710534e-10, + 1.0, + 0.0, + 0.0, + 2.0, + -1.0, + -1.0, + 2.088668240785089e-10, + 1.0, + 0.0, + 0.0, + 3.0, + -1.0, + -1.0, + 2.9241355370991246e-10, + 1.0, + 0.0, + 0.0, + 4.0, + -1.0, + -1.0, + 3.759602833413161e-10, + 1.0, + 0.0, + 0.0, + 5.0, + -1.0, + -1.0, + 4.5950701297271953e-10, + 1.0, + 0.0, + 0.0, + 6.0, + -1.0, + -1.0, + 5.430537426041233e-10, + 1.0, + 0.0, + 0.0, + 7.0, + -1.0, + -1.0, + 6.266004722355263e-10, + 1.0, + 0.0, + 0.0, + 8.0, + -1.0, + -1.0, + 7.101472018669311e-10, + 1.0, + 0.0, + 0.0, + 9.0, + -1.0, + -1.0, + 7.936939314983349e-10, + 1.0, + 0.0, + 0.0, + 10.0, + -1.0, + -1.0, + 8.772406611297365e-10, + 1.0, + 0.0, + 0.0, + 11.0, + -1.0, + -1.0, + 9.607873907611417e-10, + 1.0, + 0.0, + 0.0, + 12.0, + -1.0, + -1.0, + 1.044334120392546e-09, + 1.0, + 0.0, + 0.0, + 13.0, + -1.0, + -1.0, + 1.1278808500239482e-09 + ], + "SECTION": 0 + } + }, + "solver": { + "GS_TYPE": 1, + "MAX_KRYLOV": 0, + "MAX_RESTARTS": 10, + "SCHUR_SAFETY": 1e-08 + }, + "unit_000": { + "ADSORPTION_MODEL": "LINEAR", + "COL_DISPERSION": [ 5.75e-08, 8.0e-8 ], + "COL_DISPERSION_RADIAL": [ 5e-08, 1e-7 ], + "COL_LENGTH": 0.014, + "COL_POROSITY": 0.37, + "COL_RADIUS": 0.0035, + "CROSS_SECTION_AREA": 3.848451000647497e-05, + "FILM_DIFFUSION": [ 6.9e-6, 5e-8 ], + "INIT_C": [ + 0.0, + 0.0 + ], + "INIT_CP": [ + 0.0, + 0.0 + ], + "INIT_Q": [ + 0.0, + 0.0 + ], + "NBOUND": [ 1, 1 ], + "NCOMP": 2, + "NPARTYPE": 1, + "PAR_POROSITY": 0.75, + "PAR_RADIUS": 4.5e-05, + "PAR_TYPE_VOLFRAC": 1.0, + "PORTS": 14, + "UNIT_TYPE": "LUMPED_RATE_MODEL_WITH_PORES_2D", + "VELOCITY": 0.000575, + "adsorption": { + "IS_KINETIC": [ 1, 0 ], + "LIN_KA": [ 35.5, 3.5 ], + "LIN_KD": [ 1.0, 2.0 ] + }, + "discretization": { + "AX_NELEM": 7, + "AX_POLYDEG": 2, + "RADIAL_DISC_TYPE": "EQUIDISTANT", + "RAD_NELEM": 7, + "RAD_POLYDEG": 1, + "SPATIAL_METHOD": "DG", + "USE_ANALYTIC_JACOBIAN": true + } + }, + "unit_001": { + "INLET_TYPE": "PIECEWISE_CUBIC_POLY", + "NCOMP": 2, + "PORTS": 1, + "UNIT_TYPE": "INLET", + "sec_000": { + "CONST_COEFF": [ 1.0, 2.0 ] + }, + "sec_001": { + "CONST_COEFF": [ + 0.0, 0.0 + ] + } + } + }, + "return": { + "SPLIT_COMPONENTS_DATA": 0, + "SPLIT_PORTS_DATA": 1, + "unit_000": { + "WRITE_COORDINATES": 1, + "WRITE_SENS_OUTLET": 0, + "WRITE_SOLUTION_BULK": 1, + "WRITE_SOLUTION_FLUX": 0, + "WRITE_SOLUTION_INLET": 0, + "WRITE_SOLUTION_OUTLET": 1, + "WRITE_SOLUTION_PARTICLE": 0, + "WRITE_SOLUTION_SOLID": 0 + } + }, + "solver": { + "CONSISTENT_INIT_MODE": 3, + "NTHREADS": -1, + "USER_SOLUTION_TIMES": [ + 0.0, + 1.0, + 2.0, + 3.0, + 4.0, + 5.0, + 6.0, + 7.0, + 8.0, + 9.0, + 10.0, + 11.0, + 12.0, + 13.0, + 14.0, + 15.0, + 16.0, + 17.0, + 18.0, + 19.0, + 20.0, + 21.0, + 22.0, + 23.0, + 24.0, + 25.0, + 26.0, + 27.0, + 28.0, + 29.0, + 30.0, + 31.0, + 32.0, + 33.0, + 34.0, + 35.0, + 36.0, + 37.0, + 38.0, + 39.0, + 40.0, + 41.0, + 42.0, + 43.0, + 44.0, + 45.0, + 46.0, + 47.0, + 48.0, + 49.0, + 50.0, + 51.0, + 52.0, + 53.0, + 54.0, + 55.0, + 56.0, + 57.0, + 58.0, + 59.0, + 60.0, + 61.0, + 62.0, + 63.0, + 64.0, + 65.0, + 66.0, + 67.0, + 68.0, + 69.0, + 70.0, + 71.0, + 72.0, + 73.0, + 74.0, + 75.0, + 76.0, + 77.0, + 78.0, + 79.0, + 80.0, + 81.0, + 82.0, + 83.0, + 84.0, + 85.0, + 86.0, + 87.0, + 88.0, + 89.0, + 90.0, + 91.0, + 92.0, + 93.0, + 94.0, + 95.0, + 96.0, + 97.0, + 98.0, + 99.0, + 100.0, + 101.0, + 102.0, + 103.0, + 104.0, + 105.0, + 106.0, + 107.0, + 108.0, + 109.0, + 110.0, + 111.0, + 112.0, + 113.0, + 114.0, + 115.0, + 116.0, + 117.0, + 118.0, + 119.0, + 120.0, + 121.0, + 122.0, + 123.0, + 124.0, + 125.0, + 126.0, + 127.0, + 128.0, + 129.0, + 130.0, + 131.0, + 132.0, + 133.0, + 134.0, + 135.0, + 136.0, + 137.0, + 138.0, + 139.0, + 140.0, + 141.0, + 142.0, + 143.0, + 144.0, + 145.0, + 146.0, + 147.0, + 148.0, + 149.0, + 150.0, + 151.0, + 152.0, + 153.0, + 154.0, + 155.0, + 156.0, + 157.0, + 158.0, + 159.0, + 160.0, + 161.0, + 162.0, + 163.0, + 164.0, + 165.0, + 166.0, + 167.0, + 168.0, + 169.0, + 170.0, + 171.0, + 172.0, + 173.0, + 174.0, + 175.0, + 176.0, + 177.0, + 178.0, + 179.0, + 180.0, + 181.0, + 182.0, + 183.0, + 184.0, + 185.0, + 186.0, + 187.0, + 188.0, + 189.0, + 190.0, + 191.0, + 192.0, + 193.0, + 194.0, + 195.0, + 196.0, + 197.0, + 198.0, + 199.0, + 200.0, + 201.0, + 202.0, + 203.0, + 204.0, + 205.0, + 206.0, + 207.0, + 208.0, + 209.0, + 210.0, + 211.0, + 212.0, + 213.0, + 214.0, + 215.0, + 216.0, + 217.0, + 218.0, + 219.0, + 220.0, + 221.0, + 222.0, + 223.0, + 224.0, + 225.0, + 226.0, + 227.0, + 228.0, + 229.0, + 230.0, + 231.0, + 232.0, + 233.0, + 234.0, + 235.0, + 236.0, + 237.0, + 238.0, + 239.0, + 240.0, + 241.0, + 242.0, + 243.0, + 244.0, + 245.0, + 246.0, + 247.0, + 248.0, + 249.0, + 250.0, + 251.0, + 252.0, + 253.0, + 254.0, + 255.0, + 256.0, + 257.0, + 258.0, + 259.0, + 260.0, + 261.0, + 262.0, + 263.0, + 264.0, + 265.0, + 266.0, + 267.0, + 268.0, + 269.0, + 270.0, + 271.0, + 272.0, + 273.0, + 274.0, + 275.0, + 276.0, + 277.0, + 278.0, + 279.0, + 280.0, + 281.0, + 282.0, + 283.0, + 284.0, + 285.0, + 286.0, + 287.0, + 288.0, + 289.0, + 290.0, + 291.0, + 292.0, + 293.0, + 294.0, + 295.0, + 296.0, + 297.0, + 298.0, + 299.0, + 300.0, + 301.0, + 302.0, + 303.0, + 304.0, + 305.0, + 306.0, + 307.0, + 308.0, + 309.0, + 310.0, + 311.0, + 312.0, + 313.0, + 314.0, + 315.0, + 316.0, + 317.0, + 318.0, + 319.0, + 320.0, + 321.0, + 322.0, + 323.0, + 324.0, + 325.0, + 326.0, + 327.0, + 328.0, + 329.0, + 330.0, + 331.0, + 332.0, + 333.0, + 334.0, + 335.0, + 336.0, + 337.0, + 338.0, + 339.0, + 340.0, + 341.0, + 342.0, + 343.0, + 344.0, + 345.0, + 346.0, + 347.0, + 348.0, + 349.0, + 350.0, + 351.0, + 352.0, + 353.0, + 354.0, + 355.0, + 356.0, + 357.0, + 358.0, + 359.0, + 360.0, + 361.0, + 362.0, + 363.0, + 364.0, + 365.0, + 366.0, + 367.0, + 368.0, + 369.0, + 370.0, + 371.0, + 372.0, + 373.0, + 374.0, + 375.0, + 376.0, + 377.0, + 378.0, + 379.0, + 380.0, + 381.0, + 382.0, + 383.0, + 384.0, + 385.0, + 386.0, + 387.0, + 388.0, + 389.0, + 390.0, + 391.0, + 392.0, + 393.0, + 394.0, + 395.0, + 396.0, + 397.0, + 398.0, + 399.0, + 400.0, + 401.0, + 402.0, + 403.0, + 404.0, + 405.0, + 406.0, + 407.0, + 408.0, + 409.0, + 410.0, + 411.0, + 412.0, + 413.0, + 414.0, + 415.0, + 416.0, + 417.0, + 418.0, + 419.0, + 420.0, + 421.0, + 422.0, + 423.0, + 424.0, + 425.0, + 426.0, + 427.0, + 428.0, + 429.0, + 430.0, + 431.0, + 432.0, + 433.0, + 434.0, + 435.0, + 436.0, + 437.0, + 438.0, + 439.0, + 440.0, + 441.0, + 442.0, + 443.0, + 444.0, + 445.0, + 446.0, + 447.0, + 448.0, + 449.0, + 450.0, + 451.0, + 452.0, + 453.0, + 454.0, + 455.0, + 456.0, + 457.0, + 458.0, + 459.0, + 460.0, + 461.0, + 462.0, + 463.0, + 464.0, + 465.0, + 466.0, + 467.0, + 468.0, + 469.0, + 470.0, + 471.0, + 472.0, + 473.0, + 474.0, + 475.0, + 476.0, + 477.0, + 478.0, + 479.0, + 480.0, + 481.0, + 482.0, + 483.0, + 484.0, + 485.0, + 486.0, + 487.0, + 488.0, + 489.0, + 490.0, + 491.0, + 492.0, + 493.0, + 494.0, + 495.0, + 496.0, + 497.0, + 498.0, + 499.0, + 500.0, + 501.0, + 502.0, + 503.0, + 504.0, + 505.0, + 506.0, + 507.0, + 508.0, + 509.0, + 510.0, + 511.0, + 512.0, + 513.0, + 514.0, + 515.0, + 516.0, + 517.0, + 518.0, + 519.0, + 520.0, + 521.0, + 522.0, + 523.0, + 524.0, + 525.0, + 526.0, + 527.0, + 528.0, + 529.0, + 530.0, + 531.0, + 532.0, + 533.0, + 534.0, + 535.0, + 536.0, + 537.0, + 538.0, + 539.0, + 540.0, + 541.0, + 542.0, + 543.0, + 544.0, + 545.0, + 546.0, + 547.0, + 548.0, + 549.0, + 550.0, + 551.0, + 552.0, + 553.0, + 554.0, + 555.0, + 556.0, + 557.0, + 558.0, + 559.0, + 560.0, + 561.0, + 562.0, + 563.0, + 564.0, + 565.0, + 566.0, + 567.0, + 568.0, + 569.0, + 570.0, + 571.0, + 572.0, + 573.0, + 574.0, + 575.0, + 576.0, + 577.0, + 578.0, + 579.0, + 580.0, + 581.0, + 582.0, + 583.0, + 584.0, + 585.0, + 586.0, + 587.0, + 588.0, + 589.0, + 590.0, + 591.0, + 592.0, + 593.0, + 594.0, + 595.0, + 596.0, + 597.0, + 598.0, + 599.0, + 600.0, + 601.0, + 602.0, + 603.0, + 604.0, + 605.0, + 606.0, + 607.0, + 608.0, + 609.0, + 610.0, + 611.0, + 612.0, + 613.0, + 614.0, + 615.0, + 616.0, + 617.0, + 618.0, + 619.0, + 620.0, + 621.0, + 622.0, + 623.0, + 624.0, + 625.0, + 626.0, + 627.0, + 628.0, + 629.0, + 630.0, + 631.0, + 632.0, + 633.0, + 634.0, + 635.0, + 636.0, + 637.0, + 638.0, + 639.0, + 640.0, + 641.0, + 642.0, + 643.0, + 644.0, + 645.0, + 646.0, + 647.0, + 648.0, + 649.0, + 650.0, + 651.0, + 652.0, + 653.0, + 654.0, + 655.0, + 656.0, + 657.0, + 658.0, + 659.0, + 660.0, + 661.0, + 662.0, + 663.0, + 664.0, + 665.0, + 666.0, + 667.0, + 668.0, + 669.0, + 670.0, + 671.0, + 672.0, + 673.0, + 674.0, + 675.0, + 676.0, + 677.0, + 678.0, + 679.0, + 680.0, + 681.0, + 682.0, + 683.0, + 684.0, + 685.0, + 686.0, + 687.0, + 688.0, + 689.0, + 690.0, + 691.0, + 692.0, + 693.0, + 694.0, + 695.0, + 696.0, + 697.0, + 698.0, + 699.0, + 700.0, + 701.0, + 702.0, + 703.0, + 704.0, + 705.0, + 706.0, + 707.0, + 708.0, + 709.0, + 710.0, + 711.0, + 712.0, + 713.0, + 714.0, + 715.0, + 716.0, + 717.0, + 718.0, + 719.0, + 720.0, + 721.0, + 722.0, + 723.0, + 724.0, + 725.0, + 726.0, + 727.0, + 728.0, + 729.0, + 730.0, + 731.0, + 732.0, + 733.0, + 734.0, + 735.0, + 736.0, + 737.0, + 738.0, + 739.0, + 740.0, + 741.0, + 742.0, + 743.0, + 744.0, + 745.0, + 746.0, + 747.0, + 748.0, + 749.0, + 750.0, + 751.0, + 752.0, + 753.0, + 754.0, + 755.0, + 756.0, + 757.0, + 758.0, + 759.0, + 760.0, + 761.0, + 762.0, + 763.0, + 764.0, + 765.0, + 766.0, + 767.0, + 768.0, + 769.0, + 770.0, + 771.0, + 772.0, + 773.0, + 774.0, + 775.0, + 776.0, + 777.0, + 778.0, + 779.0, + 780.0, + 781.0, + 782.0, + 783.0, + 784.0, + 785.0, + 786.0, + 787.0, + 788.0, + 789.0, + 790.0, + 791.0, + 792.0, + 793.0, + 794.0, + 795.0, + 796.0, + 797.0, + 798.0, + 799.0, + 800.0, + 801.0, + 802.0, + 803.0, + 804.0, + 805.0, + 806.0, + 807.0, + 808.0, + 809.0, + 810.0, + 811.0, + 812.0, + 813.0, + 814.0, + 815.0, + 816.0, + 817.0, + 818.0, + 819.0, + 820.0, + 821.0, + 822.0, + 823.0, + 824.0, + 825.0, + 826.0, + 827.0, + 828.0, + 829.0, + 830.0, + 831.0, + 832.0, + 833.0, + 834.0, + 835.0, + 836.0, + 837.0, + 838.0, + 839.0, + 840.0, + 841.0, + 842.0, + 843.0, + 844.0, + 845.0, + 846.0, + 847.0, + 848.0, + 849.0, + 850.0, + 851.0, + 852.0, + 853.0, + 854.0, + 855.0, + 856.0, + 857.0, + 858.0, + 859.0, + 860.0, + 861.0, + 862.0, + 863.0, + 864.0, + 865.0, + 866.0, + 867.0, + 868.0, + 869.0, + 870.0, + 871.0, + 872.0, + 873.0, + 874.0, + 875.0, + 876.0, + 877.0, + 878.0, + 879.0, + 880.0, + 881.0, + 882.0, + 883.0, + 884.0, + 885.0, + 886.0, + 887.0, + 888.0, + 889.0, + 890.0, + 891.0, + 892.0, + 893.0, + 894.0, + 895.0, + 896.0, + 897.0, + 898.0, + 899.0, + 900.0, + 901.0, + 902.0, + 903.0, + 904.0, + 905.0, + 906.0, + 907.0, + 908.0, + 909.0, + 910.0, + 911.0, + 912.0, + 913.0, + 914.0, + 915.0, + 916.0, + 917.0, + 918.0, + 919.0, + 920.0, + 921.0, + 922.0, + 923.0, + 924.0, + 925.0, + 926.0, + 927.0, + 928.0, + 929.0, + 930.0, + 931.0, + 932.0, + 933.0, + 934.0, + 935.0, + 936.0, + 937.0, + 938.0, + 939.0, + 940.0, + 941.0, + 942.0, + 943.0, + 944.0, + 945.0, + 946.0, + 947.0, + 948.0, + 949.0, + 950.0, + 951.0, + 952.0, + 953.0, + 954.0, + 955.0, + 956.0, + 957.0, + 958.0, + 959.0, + 960.0, + 961.0, + 962.0, + 963.0, + 964.0, + 965.0, + 966.0, + 967.0, + 968.0, + 969.0, + 970.0, + 971.0, + 972.0, + 973.0, + 974.0, + 975.0, + 976.0, + 977.0, + 978.0, + 979.0, + 980.0, + 981.0, + 982.0, + 983.0, + 984.0, + 985.0, + 986.0, + 987.0, + 988.0, + 989.0, + 990.0, + 991.0, + 992.0, + 993.0, + 994.0, + 995.0, + 996.0, + 997.0, + 998.0, + 999.0, + 1000.0, + 1001.0, + 1002.0, + 1003.0, + 1004.0, + 1005.0, + 1006.0, + 1007.0, + 1008.0, + 1009.0, + 1010.0, + 1011.0, + 1012.0, + 1013.0, + 1014.0, + 1015.0, + 1016.0, + 1017.0, + 1018.0, + 1019.0, + 1020.0, + 1021.0, + 1022.0, + 1023.0, + 1024.0, + 1025.0, + 1026.0, + 1027.0, + 1028.0, + 1029.0, + 1030.0, + 1031.0, + 1032.0, + 1033.0, + 1034.0, + 1035.0, + 1036.0, + 1037.0, + 1038.0, + 1039.0, + 1040.0, + 1041.0, + 1042.0, + 1043.0, + 1044.0, + 1045.0, + 1046.0, + 1047.0, + 1048.0, + 1049.0, + 1050.0, + 1051.0, + 1052.0, + 1053.0, + 1054.0, + 1055.0, + 1056.0, + 1057.0, + 1058.0, + 1059.0, + 1060.0, + 1061.0, + 1062.0, + 1063.0, + 1064.0, + 1065.0, + 1066.0, + 1067.0, + 1068.0, + 1069.0, + 1070.0, + 1071.0, + 1072.0, + 1073.0, + 1074.0, + 1075.0, + 1076.0, + 1077.0, + 1078.0, + 1079.0, + 1080.0, + 1081.0, + 1082.0, + 1083.0, + 1084.0, + 1085.0, + 1086.0, + 1087.0, + 1088.0, + 1089.0, + 1090.0, + 1091.0, + 1092.0, + 1093.0, + 1094.0, + 1095.0, + 1096.0, + 1097.0, + 1098.0, + 1099.0, + 1100.0, + 1101.0, + 1102.0, + 1103.0, + 1104.0, + 1105.0, + 1106.0, + 1107.0, + 1108.0, + 1109.0, + 1110.0, + 1111.0, + 1112.0, + 1113.0, + 1114.0, + 1115.0, + 1116.0, + 1117.0, + 1118.0, + 1119.0, + 1120.0, + 1121.0, + 1122.0, + 1123.0, + 1124.0, + 1125.0, + 1126.0, + 1127.0, + 1128.0, + 1129.0, + 1130.0, + 1131.0, + 1132.0, + 1133.0, + 1134.0, + 1135.0, + 1136.0, + 1137.0, + 1138.0, + 1139.0, + 1140.0, + 1141.0, + 1142.0, + 1143.0, + 1144.0, + 1145.0, + 1146.0, + 1147.0, + 1148.0, + 1149.0, + 1150.0, + 1151.0, + 1152.0, + 1153.0, + 1154.0, + 1155.0, + 1156.0, + 1157.0, + 1158.0, + 1159.0, + 1160.0, + 1161.0, + 1162.0, + 1163.0, + 1164.0, + 1165.0, + 1166.0, + 1167.0, + 1168.0, + 1169.0, + 1170.0, + 1171.0, + 1172.0, + 1173.0, + 1174.0, + 1175.0, + 1176.0, + 1177.0, + 1178.0, + 1179.0, + 1180.0, + 1181.0, + 1182.0, + 1183.0, + 1184.0, + 1185.0, + 1186.0, + 1187.0, + 1188.0, + 1189.0, + 1190.0, + 1191.0, + 1192.0, + 1193.0, + 1194.0, + 1195.0, + 1196.0, + 1197.0, + 1198.0, + 1199.0, + 1200.0, + 1201.0, + 1202.0, + 1203.0, + 1204.0, + 1205.0, + 1206.0, + 1207.0, + 1208.0, + 1209.0, + 1210.0, + 1211.0, + 1212.0, + 1213.0, + 1214.0, + 1215.0, + 1216.0, + 1217.0, + 1218.0, + 1219.0, + 1220.0, + 1221.0, + 1222.0, + 1223.0, + 1224.0, + 1225.0, + 1226.0, + 1227.0, + 1228.0, + 1229.0, + 1230.0, + 1231.0, + 1232.0, + 1233.0, + 1234.0, + 1235.0, + 1236.0, + 1237.0, + 1238.0, + 1239.0, + 1240.0, + 1241.0, + 1242.0, + 1243.0, + 1244.0, + 1245.0, + 1246.0, + 1247.0, + 1248.0, + 1249.0, + 1250.0, + 1251.0, + 1252.0, + 1253.0, + 1254.0, + 1255.0, + 1256.0, + 1257.0, + 1258.0, + 1259.0, + 1260.0, + 1261.0, + 1262.0, + 1263.0, + 1264.0, + 1265.0, + 1266.0, + 1267.0, + 1268.0, + 1269.0, + 1270.0, + 1271.0, + 1272.0, + 1273.0, + 1274.0, + 1275.0, + 1276.0, + 1277.0, + 1278.0, + 1279.0, + 1280.0, + 1281.0, + 1282.0, + 1283.0, + 1284.0, + 1285.0, + 1286.0, + 1287.0, + 1288.0, + 1289.0, + 1290.0, + 1291.0, + 1292.0, + 1293.0, + 1294.0, + 1295.0, + 1296.0, + 1297.0, + 1298.0, + 1299.0, + 1300.0, + 1301.0, + 1302.0, + 1303.0, + 1304.0, + 1305.0, + 1306.0, + 1307.0, + 1308.0, + 1309.0, + 1310.0, + 1311.0, + 1312.0, + 1313.0, + 1314.0, + 1315.0, + 1316.0, + 1317.0, + 1318.0, + 1319.0, + 1320.0, + 1321.0, + 1322.0, + 1323.0, + 1324.0, + 1325.0, + 1326.0, + 1327.0, + 1328.0, + 1329.0, + 1330.0, + 1331.0, + 1332.0, + 1333.0, + 1334.0, + 1335.0, + 1336.0, + 1337.0, + 1338.0, + 1339.0, + 1340.0, + 1341.0, + 1342.0, + 1343.0, + 1344.0, + 1345.0, + 1346.0, + 1347.0, + 1348.0, + 1349.0, + 1350.0, + 1351.0, + 1352.0, + 1353.0, + 1354.0, + 1355.0, + 1356.0, + 1357.0, + 1358.0, + 1359.0, + 1360.0, + 1361.0, + 1362.0, + 1363.0, + 1364.0, + 1365.0, + 1366.0, + 1367.0, + 1368.0, + 1369.0, + 1370.0, + 1371.0, + 1372.0, + 1373.0, + 1374.0, + 1375.0, + 1376.0, + 1377.0, + 1378.0, + 1379.0, + 1380.0, + 1381.0, + 1382.0, + 1383.0, + 1384.0, + 1385.0, + 1386.0, + 1387.0, + 1388.0, + 1389.0, + 1390.0, + 1391.0, + 1392.0, + 1393.0, + 1394.0, + 1395.0, + 1396.0, + 1397.0, + 1398.0, + 1399.0, + 1400.0, + 1401.0, + 1402.0, + 1403.0, + 1404.0, + 1405.0, + 1406.0, + 1407.0, + 1408.0, + 1409.0, + 1410.0, + 1411.0, + 1412.0, + 1413.0, + 1414.0, + 1415.0, + 1416.0, + 1417.0, + 1418.0, + 1419.0, + 1420.0, + 1421.0, + 1422.0, + 1423.0, + 1424.0, + 1425.0, + 1426.0, + 1427.0, + 1428.0, + 1429.0, + 1430.0, + 1431.0, + 1432.0, + 1433.0, + 1434.0, + 1435.0, + 1436.0, + 1437.0, + 1438.0, + 1439.0, + 1440.0, + 1441.0, + 1442.0, + 1443.0, + 1444.0, + 1445.0, + 1446.0, + 1447.0, + 1448.0, + 1449.0, + 1450.0, + 1451.0, + 1452.0, + 1453.0, + 1454.0, + 1455.0, + 1456.0, + 1457.0, + 1458.0, + 1459.0, + 1460.0, + 1461.0, + 1462.0, + 1463.0, + 1464.0, + 1465.0, + 1466.0, + 1467.0, + 1468.0, + 1469.0, + 1470.0, + 1471.0, + 1472.0, + 1473.0, + 1474.0, + 1475.0, + 1476.0, + 1477.0, + 1478.0, + 1479.0, + 1480.0, + 1481.0, + 1482.0, + 1483.0, + 1484.0, + 1485.0, + 1486.0, + 1487.0, + 1488.0, + 1489.0, + 1490.0, + 1491.0, + 1492.0, + 1493.0, + 1494.0, + 1495.0, + 1496.0, + 1497.0, + 1498.0, + 1499.0, + 1500.0 + ], + "sections": { + "NSEC": 2, + "SECTION_CONTINUITY": [ + 0 + ], + "SECTION_TIMES": [ + 0.0, + 10.0, + 1500.0 + ] + }, + "time_integrator": { + "ABSTOL": 1e-06, + "ALGTOL": 1e-10, + "INIT_STEP_SIZE": 1e-06, + "MAX_STEPS": 1000000, + "RELTOL": 1e-06, + "USE_MODIFIED_NEWTON": 0 + } + } +} \ No newline at end of file