From 2f0816341dd44821f46b8b7402fb07b65beec5fa Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 10:09:17 +0200 Subject: [PATCH 01/32] (WIP) add min/max limit to Wrapper.hpp Will not compile due to std::optional instantiation (used by m_minValue and m_maxVavlue) for abstract types like PartitionBase --- src/coreComponents/dataRepository/Wrapper.hpp | 70 ++++++++++++++++++- 1 file changed, 69 insertions(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 4f23afa084e..de4be8625bf 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -37,8 +37,8 @@ #include "WrapperBase.hpp" // System includes -#include #include +#include #include namespace geos @@ -204,6 +204,8 @@ class Wrapper final : public WrapperBase m_ownsData = castedSource.m_ownsData; m_default = castedSource.m_default; m_dimLabels = castedSource.m_dimLabels; + m_minValue = castedSource.m_minValue; + m_maxValue = castedSource.m_maxValue; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -718,6 +720,66 @@ class Wrapper final : public WrapperBase return ss.str(); } + /** + * @brief Set a minimum bound for this attribute's value. + * @param minValue the minimum allowed value (inclusive) + * @return pointer to Wrapper + */ + template< typename U=T > + std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > + setMinValue( T const & minValue ) + { + m_minValue = minValue; + return *this; + } + + /** + * @brief Set a maximum bound for this attribute's value. + * @param maxValue the maximum allowed value (inclusive) + * @return pointer to Wrapper + */ + template< typename U=T > + std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > + setMaxValue( T const & maxValue ) + { + m_maxValue = maxValue; + return *this; + } + + /** + * @brief Set both bounds for this attribute's value. + * @param minValue the minimum allowed value (inclusive) + * @param maxValue the maximum allowed value (inclusive) + * @return pointer to Wrapper + */ + template< typename U=T > + std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > + setLimits( T const & minValue, + T const & maxValue ) + { + m_minValue = minValue; + m_maxValue = maxValue; + return *this; + } + + /** + * @brief Accessor for the minimum bound of this attribute's value. + * @return optional containing the typed minimum value, empty if not set + */ + std::optional< T > const & getMinValue() const + { + return m_minValue; + } + + /** + * @brief Accessor for the maximum bound of this attribute's value. + * @return optional containing the typed maximum value, empty if not set + */ + std::optional< T > const & getMaxValue() const + { + return m_maxValue; + } + virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) override { @@ -1100,6 +1162,12 @@ class Wrapper final : public WrapperBase /// stores dimension labels (used mainly for plotting) for multidimensional arrays, empty member otherwise wrapperHelpers::ArrayDimLabels< T > m_dimLabels; + + /// + std::optional< T > m_minValue; + + /// + std::optional< T > m_maxValue; }; } From 45713eada24d47037a45e0ec54dd94c58e6a8e62 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 10:47:35 +0200 Subject: [PATCH 02/32] move min/max optionals in a struct instanciated only when T is limitable --- .../dataRepository/AttributeLimits.hpp | 52 +++++++++++++++++++ .../dataRepository/CMakeLists.txt | 1 + src/coreComponents/dataRepository/Wrapper.hpp | 34 ++++++------ 3 files changed, 71 insertions(+), 16 deletions(-) create mode 100644 src/coreComponents/dataRepository/AttributeLimits.hpp diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp new file mode 100644 index 00000000000..3aa5b1c0587 --- /dev/null +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -0,0 +1,52 @@ +/* + * ------------------------------------------------------------------------------------------------------------ + * SPDX-License-Identifier: LGPL-2.1-only + * + * Copyright (c) 2016-2024 Lawrence Livermore National Security LLC + * Copyright (c) 2018-2024 TotalEnergies + * Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University + * Copyright (c) 2023-2024 Chevron + * Copyright (c) 2019- GEOS/GEOSX Contributors + * All rights reserved + * + * See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details. + * ------------------------------------------------------------------------------------------------------------ + */ + + +#ifndef GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ +#define GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ + +#include +#include + +namespace geos +{ + +namespace dataRepository +{ + +/** + * @struct Limits + * @brief Storage for the optional min/max bounds of a wrapped value. + * + * Specialized so that the members (std::optional< T >) are only instanciated + * for arithmetic types. Preventing instantiation non-limitable types, especially + * abstract types that can't be instantiated with std::optional< absT >. + */ +template< typename T, bool = std::is_arithmetic< T >::value > +struct Limits +{}; + +template< typename T > +struct Limits< T, true > +{ + std::optional< T > minValue; + std::optional< T > maxValue; +}; + +} /* namespace dataRepository */ + +} /* namespace geos */ + +#endif /* GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ */ diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index 8d0fc0e09ce..f75d8e70ca7 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -22,6 +22,7 @@ Also contains a wrapper to process entries from an xml file into data types. # Specify all headers # set( dataRepository_headers + AttributeLimits.hpp BufferOps.hpp BufferOpsDevice.hpp BufferOps_inline.hpp diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index de4be8625bf..4b01ecc3ab4 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -21,6 +21,7 @@ #define GEOS_DATAREPOSITORY_WRAPPER_HPP_ // Source inclues +#include "dataRepository/AttributeLimits.hpp" #include "wrapperHelpers.hpp" #include "KeyNames.hpp" #include "LvArray/src/limits.hpp" @@ -38,7 +39,6 @@ // System includes #include -#include #include namespace geos @@ -204,8 +204,7 @@ class Wrapper final : public WrapperBase m_ownsData = castedSource.m_ownsData; m_default = castedSource.m_default; m_dimLabels = castedSource.m_dimLabels; - m_minValue = castedSource.m_minValue; - m_maxValue = castedSource.m_maxValue; + m_limits = castedSource.m_limits; } /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -729,7 +728,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > setMinValue( T const & minValue ) { - m_minValue = minValue; + m_limits.minValue = minValue; return *this; } @@ -742,7 +741,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > setMaxValue( T const & maxValue ) { - m_maxValue = maxValue; + m_limits.maxValue = maxValue; return *this; } @@ -757,27 +756,33 @@ class Wrapper final : public WrapperBase setLimits( T const & minValue, T const & maxValue ) { - m_minValue = minValue; - m_maxValue = maxValue; + m_limits.minValue = minValue; + m_limits.maxValue = maxValue; return *this; } /** * @brief Accessor for the minimum bound of this attribute's value. * @return optional containing the typed minimum value, empty if not set + * @note Only available when T is a limitable type */ - std::optional< T > const & getMinValue() const + template< typename U=T > + std::enable_if_t< std::is_arithmetic< U >::value, std::optional< T > const & > + getMinValue() const { - return m_minValue; + return m_limits.minValue; } /** * @brief Accessor for the maximum bound of this attribute's value. * @return optional containing the typed maximum value, empty if not set + * @note Only available when T is a limitable type */ - std::optional< T > const & getMaxValue() const + template< typename U=T > + std::enable_if_t< std::is_arithmetic< U >::value, std::optional< T > const & > + getMaxValue() const { - return m_maxValue; + return m_limits.maxValue; } virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, @@ -1163,11 +1168,8 @@ class Wrapper final : public WrapperBase /// stores dimension labels (used mainly for plotting) for multidimensional arrays, empty member otherwise wrapperHelpers::ArrayDimLabels< T > m_dimLabels; - /// - std::optional< T > m_minValue; - - /// - std::optional< T > m_maxValue; + /// stores the (optional) min/max bounds for the wrapped value. + Limits< T > m_limits; }; } From c07a75abcb0df726efef3f7d0e76763078d6bd37 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 11:26:28 +0200 Subject: [PATCH 03/32] create is_limitable trait --- .../dataRepository/AttributeLimits.hpp | 19 ++++++++++++++++--- src/coreComponents/dataRepository/Wrapper.hpp | 10 +++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index 3aa5b1c0587..8cc1dd6a61d 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -26,15 +26,28 @@ namespace geos namespace dataRepository { +/** + * @struct is_limitable + * @tparam T type to check + * @brief Trait determining whether attribute limits can be applied to type @p T + * + * Limits apply to scalar numeric types (integer, real32, real64, etc.) + */ +template< typename T > +struct is_limitable +{ + static constexpr bool value = std::is_arithmetic< T >::value; +}; + /** * @struct Limits * @brief Storage for the optional min/max bounds of a wrapped value. * * Specialized so that the members (std::optional< T >) are only instanciated - * for arithmetic types. Preventing instantiation non-limitable types, especially - * abstract types that can't be instantiated with std::optional< absT >. + * for limitable types. Preventing instantiation non-limitable types, especially + * abstract types that can't be instantiated with std::optional< absT >. */ -template< typename T, bool = std::is_arithmetic< T >::value > +template< typename T, bool = is_limitable< T >::value > struct Limits {}; diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 4b01ecc3ab4..2d63595511d 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -725,7 +725,7 @@ class Wrapper final : public WrapperBase * @return pointer to Wrapper */ template< typename U=T > - std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > + std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > setMinValue( T const & minValue ) { m_limits.minValue = minValue; @@ -738,7 +738,7 @@ class Wrapper final : public WrapperBase * @return pointer to Wrapper */ template< typename U=T > - std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > + std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > setMaxValue( T const & maxValue ) { m_limits.maxValue = maxValue; @@ -752,7 +752,7 @@ class Wrapper final : public WrapperBase * @return pointer to Wrapper */ template< typename U=T > - std::enable_if_t< std::is_arithmetic< U >::value, Wrapper< T > & > + std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > setLimits( T const & minValue, T const & maxValue ) { @@ -767,7 +767,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< std::is_arithmetic< U >::value, std::optional< T > const & > + std::enable_if_t< is_limitable< U >::value, std::optional< T > const & > getMinValue() const { return m_limits.minValue; @@ -779,7 +779,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< std::is_arithmetic< U >::value, std::optional< T > const & > + std::enable_if_t< is_limitable< U >::value, std::optional< T > const & > getMaxValue() const { return m_limits.maxValue; From 4c493186beb0a7ae4c99bfb8c8ada943fc5678e3 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 11:44:37 +0200 Subject: [PATCH 04/32] add limits validation for input values --- src/coreComponents/dataRepository/Wrapper.hpp | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 2d63595511d..5deaf5a4389 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -21,6 +21,8 @@ #define GEOS_DATAREPOSITORY_WRAPPER_HPP_ // Source inclues +#include "common/format/Format.hpp" +#include "common/logger/Logger.hpp" #include "dataRepository/AttributeLimits.hpp" #include "wrapperHelpers.hpp" #include "KeyNames.hpp" @@ -816,6 +818,11 @@ class Wrapper final : public WrapperBase targetNode, getDefaultValueStruct() ); } + + if( m_successfulReadFromInput ) + { + validateLimits(); + } } catch( std::exception const & ex ) { @@ -1153,6 +1160,38 @@ class Wrapper final : public WrapperBase return this->packByIndexImpl< false >( dummy, packList, withMetadata, onDevice, events ); } + template< typename U=T > + std::enable_if_t< is_limitable< U >::value, void > + validateLimits() + { + if( !m_limits.minValue.has_value() && !m_limits.maxValue.has_value() ) + { + return; + } + + T const & value = reference(); + bool const belowMin = m_limits.minValue.has_value() && ( value < *m_limits.minValue ); + bool const aboveMax = m_limits.maxValue.has_value() && ( value > *m_limits.maxValue ); + if( !belowMin && !aboveMax ) + { + return; + } + + // TODO: show the allowed range in the message + string const msg = GEOS_FMT( "Attribute '{}' has value '{}' outside of the allowed range.", + getName(), value ); + + // TODO: set different output strategies (log, warn, error) + GEOS_LOG_RANK_0( msg ); + } + + template< typename U=T > + std::enable_if_t< !is_limitable< U >::value, void > + validateLimits() + { + /* no-op */ + } + /// flag to indicate whether or not this wrapper is responsible for allocation/deallocation of the object at the /// address of m_data bool m_ownsData; From 19d2ae890b5b1fda79f7241d841ed4d00f30a76c Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 16:00:58 +0200 Subject: [PATCH 05/32] add limits modes to limits --- .../dataRepository/AttributeLimits.hpp | 22 +++++++++++++++++++ .../dataRepository/WrapperBase.cpp | 2 ++ .../dataRepository/WrapperBase.hpp | 13 +++++++++++ 3 files changed, 37 insertions(+) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index 8cc1dd6a61d..0cfef2ba425 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -17,6 +17,8 @@ #ifndef GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ #define GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ +#include "common/DataTypes.hpp" +#include "common/format/EnumStrings.hpp" #include #include @@ -26,6 +28,26 @@ namespace geos namespace dataRepository { +/** + * @enum LimitsMode + * @brief Enforcement mode associated with the limits of an attribute + * + * - Indicative: the limits are documentation only, no runtime check is performed. + * - Warning: a value outside the limits emits a runtime warning. + * - Error: a value outside the limits throws. + */ +enum class LimitsMode : integer +{ + Indicative, + Warning, + Error +}; + +ENUM_STRINGS( LimitsMode, + "Indicative", + "Warning", + "Error" ); + /** * @struct is_limitable * @tparam T type to check diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 69d8575b422..684679b459f 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -39,6 +39,7 @@ WrapperBase::WrapperBase( string const & name, m_inputFlag( InputFlags::INVALID ), m_successfulReadFromInput( false ), m_description(), + m_limitsMode( LimitsMode::Indicative ), m_rtTypeName( rtTypeName ), m_registeringObjects(), m_conduitNode( parent.getConduitNode()[ name ] ), @@ -61,6 +62,7 @@ void WrapperBase::copyWrapperAttributes( WrapperBase const & source ) m_plotLevel = source.m_plotLevel; m_inputFlag = source.m_inputFlag; m_description = source.m_description; + m_limitsMode = source.m_limitsMode; m_rtTypeName = source.m_rtTypeName; } diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 8a278649ae2..5ebfc57471b 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -21,6 +21,7 @@ #include "common/DataTypes.hpp" #include "common/GEOS_RAJA_Interface.hpp" #include "common/Span.hpp" +#include "dataRepository/AttributeLimits.hpp" #include "InputFlags.hpp" #include "xmlWrapper.hpp" #include "RestartFlags.hpp" @@ -529,6 +530,15 @@ class WrapperBase return m_description; } + /** + * @brief Get the enforcement mode of the (optional) attribute limits + * @return the LimitsMode of the wrapper + */ + LimitsMode getLimitsMode() const + { + return m_limitsMode; + } + /** * @brief Get the list of names of groups that registered this wrapper. * @return vector of object names @@ -699,6 +709,9 @@ class WrapperBase /// A string description of the wrapped object string m_description; + /// Enforcement mode of the (optional) attribute limits + LimitsMode m_limitsMode; + /// A string regex to validate the input values string to parse for the wrapped object string m_rtTypeName; From fd14de46ff3b456ad2f2b68a93184ccf76db9873 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 16:01:49 +0200 Subject: [PATCH 06/32] implement limits modes in validateLimits() --- src/coreComponents/dataRepository/Wrapper.hpp | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 5deaf5a4389..dbfd06f9178 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -756,10 +756,12 @@ class Wrapper final : public WrapperBase template< typename U=T > std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > setLimits( T const & minValue, - T const & maxValue ) + T const & maxValue, + LimitsMode mode = LimitsMode::Warning ) { m_limits.minValue = minValue; m_limits.maxValue = maxValue; + m_limitsMode = mode; return *this; } @@ -1164,7 +1166,8 @@ class Wrapper final : public WrapperBase std::enable_if_t< is_limitable< U >::value, void > validateLimits() { - if( !m_limits.minValue.has_value() && !m_limits.maxValue.has_value() ) + if( (!m_limits.minValue.has_value() && !m_limits.maxValue.has_value()) || + m_limitsMode == LimitsMode::Indicative ) { return; } @@ -1181,8 +1184,20 @@ class Wrapper final : public WrapperBase string const msg = GEOS_FMT( "Attribute '{}' has value '{}' outside of the allowed range.", getName(), value ); - // TODO: set different output strategies (log, warn, error) - GEOS_LOG_RANK_0( msg ); + switch( m_limitsMode ) + { + case LimitsMode::Warning: + GEOS_WARNING( msg ); + break; + + case LimitsMode::Error: + GEOS_THROW( msg, InputError ); + break; + + default: + GEOS_LOG_RANK_0( "Unimplemented LimitsMode" ); + break; + } } template< typename U=T > From 32f158602308e66dc2222a834e170a159bb417f9 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 16:35:03 +0200 Subject: [PATCH 07/32] remove setMinValue() and setMaxValue() setLimits() has the same capabilities and should be the only one kept. --- src/coreComponents/dataRepository/Wrapper.hpp | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index dbfd06f9178..f2e7d249028 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -41,6 +41,7 @@ // System includes #include +#include #include namespace geos @@ -722,46 +723,40 @@ class Wrapper final : public WrapperBase } /** - * @brief Set a minimum bound for this attribute's value. + * @brief Set both bounds for this attribute's value. * @param minValue the minimum allowed value (inclusive) - * @return pointer to Wrapper - */ - template< typename U=T > - std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > - setMinValue( T const & minValue ) - { - m_limits.minValue = minValue; - return *this; - } - - /** - * @brief Set a maximum bound for this attribute's value. * @param maxValue the maximum allowed value (inclusive) + * @param mode the enforcement mode * @return pointer to Wrapper + * + * @note @p minValue and @p maxValue are std::optional(s). + * Set them to std::nullopt to disable a limit. + * + * @code + * registerWrapper( viewKeysStruct::fooString(), &m_foo ) + * .setLimits( 0.0, std::nullopt ) // sets a minimum value of 0.0 and no maximum value + * @endcode */ template< typename U=T > std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > - setMaxValue( T const & maxValue ) + setLimits( std::optional< T > minValue, + std::optional< T > maxValue, + LimitsMode mode = LimitsMode::Warning ) { + m_limits.minValue = minValue; m_limits.maxValue = maxValue; + m_limitsMode = mode; return *this; } - /** - * @brief Set both bounds for this attribute's value. - * @param minValue the minimum allowed value (inclusive) - * @param maxValue the maximum allowed value (inclusive) - * @return pointer to Wrapper - */ template< typename U=T > - std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > - setLimits( T const & minValue, - T const & maxValue, + std::enable_if_t< !is_limitable< U >::value, Wrapper< T > & > + setLimits( std::optional< T >, + std::optional< T >, LimitsMode mode = LimitsMode::Warning ) { - m_limits.minValue = minValue; - m_limits.maxValue = maxValue; - m_limitsMode = mode; + static_assert( is_limitable< U >::value, + "setLimits is only supported on scalar arithmetic types." ); return *this; } From 796bb16be8c28afaa92d81e8abd6911a28a005c2 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 16:41:48 +0200 Subject: [PATCH 08/32] add convenience alias is_limitable_v --- .../dataRepository/AttributeLimits.hpp | 8 +++++++- src/coreComponents/dataRepository/Wrapper.hpp | 14 +++++++------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index 0cfef2ba425..c3324eb2459 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -61,6 +61,12 @@ struct is_limitable static constexpr bool value = std::is_arithmetic< T >::value; }; +/** + * @brief Convenience variable template alias for is_limitable< T >::value + */ +template< typename T > +inline constexpr bool is_limitable_v = is_limitable< T >::value; + /** * @struct Limits * @brief Storage for the optional min/max bounds of a wrapped value. @@ -69,7 +75,7 @@ struct is_limitable * for limitable types. Preventing instantiation non-limitable types, especially * abstract types that can't be instantiated with std::optional< absT >. */ -template< typename T, bool = is_limitable< T >::value > +template< typename T, bool = is_limitable_v< T > > struct Limits {}; diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index f2e7d249028..73b72265056 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -738,7 +738,7 @@ class Wrapper final : public WrapperBase * @endcode */ template< typename U=T > - std::enable_if_t< is_limitable< U >::value, Wrapper< T > & > + std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > setLimits( std::optional< T > minValue, std::optional< T > maxValue, LimitsMode mode = LimitsMode::Warning ) @@ -750,12 +750,12 @@ class Wrapper final : public WrapperBase } template< typename U=T > - std::enable_if_t< !is_limitable< U >::value, Wrapper< T > & > + std::enable_if_t< !is_limitable_v< U >, Wrapper< T > & > setLimits( std::optional< T >, std::optional< T >, LimitsMode mode = LimitsMode::Warning ) { - static_assert( is_limitable< U >::value, + static_assert( is_limitable_v< U >, "setLimits is only supported on scalar arithmetic types." ); return *this; } @@ -766,7 +766,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable< U >::value, std::optional< T > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< T > const & > getMinValue() const { return m_limits.minValue; @@ -778,7 +778,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable< U >::value, std::optional< T > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< T > const & > getMaxValue() const { return m_limits.maxValue; @@ -1158,7 +1158,7 @@ class Wrapper final : public WrapperBase } template< typename U=T > - std::enable_if_t< is_limitable< U >::value, void > + std::enable_if_t< is_limitable_v< U >, void > validateLimits() { if( (!m_limits.minValue.has_value() && !m_limits.maxValue.has_value()) || @@ -1196,7 +1196,7 @@ class Wrapper final : public WrapperBase } template< typename U=T > - std::enable_if_t< !is_limitable< U >::value, void > + std::enable_if_t< !is_limitable_v< U >, void > validateLimits() { /* no-op */ From 14bda7b78c7a21f50293ca484483fdde894b1f36 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 13 May 2026 16:51:58 +0200 Subject: [PATCH 09/32] modify outside range message --- src/coreComponents/dataRepository/Wrapper.hpp | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 73b72265056..4f5aca3a1e8 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -732,10 +732,10 @@ class Wrapper final : public WrapperBase * @note @p minValue and @p maxValue are std::optional(s). * Set them to std::nullopt to disable a limit. * - * @code + * @code * registerWrapper( viewKeysStruct::fooString(), &m_foo ) * .setLimits( 0.0, std::nullopt ) // sets a minimum value of 0.0 and no maximum value - * @endcode + * @endcode */ template< typename U=T > std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > @@ -1162,7 +1162,7 @@ class Wrapper final : public WrapperBase validateLimits() { if( (!m_limits.minValue.has_value() && !m_limits.maxValue.has_value()) || - m_limitsMode == LimitsMode::Indicative ) + m_limitsMode == LimitsMode::Indicative ) { return; } @@ -1175,9 +1175,8 @@ class Wrapper final : public WrapperBase return; } - // TODO: show the allowed range in the message - string const msg = GEOS_FMT( "Attribute '{}' has value '{}' outside of the allowed range.", - getName(), value ); + string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range.", + value, getName() ); switch( m_limitsMode ) { @@ -1188,7 +1187,7 @@ class Wrapper final : public WrapperBase case LimitsMode::Error: GEOS_THROW( msg, InputError ); break; - + default: GEOS_LOG_RANK_0( "Unimplemented LimitsMode" ); break; From 581b6d8d29db200b2b656e7503a1f59e9645d331 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 20 May 2026 10:17:59 +0200 Subject: [PATCH 10/32] add inclusive-exclusive properties using a Bound type --- .../dataRepository/AttributeLimits.hpp | 83 ++++++++++++++++++- src/coreComponents/dataRepository/Wrapper.hpp | 35 ++++---- 2 files changed, 99 insertions(+), 19 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index c3324eb2459..fa60c71599b 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -67,12 +67,58 @@ struct is_limitable template< typename T > inline constexpr bool is_limitable_v = is_limitable< T >::value; +/** + * @struct Bound + * @brief Structure containing informations about an attribute limit. + */ +template< typename T > +struct Bound +{ + T value; + bool isInclusive = true; + + /** + * @brief Bound constructor to write a limit without the "Bound{ ... }" syntax + * @param value The limit value to set + * @param isInclusive Wether the limit should be inclusive or not + * + * @code + * .setLimits( 0.0, 1.0 ) // where setLimits takes `Bound` parameters, those parameters can + * // be written only with the value. The isInclusive property will + * // default to true. + * @endcode + */ + Bound( T v, bool inclusive = true ) + : value( v ), isInclusive( inclusive ) + {} +}; + +/** + * @brief Creates an inclusive limit of @p value + * @param value The inclusive limit value to set + */ +template< typename T > +Bound< T > inclusive( T value ) +{ + return Bound< T >{ value, /*isInclusive*/ true }; +} + +/** + * @brief Creates an exclusive limit of @p value + * @param value The inclusive limit value to set + */ +template< typename T > +Bound< T > exclusive( T value ) +{ + return Bound< T >{ value, /*isInclusive*/ false }; +} + /** * @struct Limits * @brief Storage for the optional min/max bounds of a wrapped value. * * Specialized so that the members (std::optional< T >) are only instanciated - * for limitable types. Preventing instantiation non-limitable types, especially + * for limitable types. Preventing instantiation of non-limitable types, especially * abstract types that can't be instantiated with std::optional< absT >. */ template< typename T, bool = is_limitable_v< T > > @@ -82,10 +128,41 @@ struct Limits template< typename T > struct Limits< T, true > { - std::optional< T > minValue; - std::optional< T > maxValue; + std::optional< Bound< T > > min; + std::optional< Bound< T > > max; }; + +// Helper methods + +/** + * @brief Compare the given value with the min limit, taking account for the inclusive xor exclusive + * property of the limit. + * @param value The value to compare to the limit + * @param minLimit The min limit containing the inclusive + * @return True if the value is below the min limit, false otherwise + */ +template< typename T > +static bool isValueBelowMin( T const & value, Bound< T > const & minLimit ) +{ + return minLimit.isInclusive ? ( value < minLimit.value ) + : ( value <= minLimit.value ); +} + +/** + * @brief Compare the given value with the max limit, taking account for the inclusive xor exclusive + * property of the limit. + * @param value The value to compare to the limit + * @param maxLimit The max limit containing the inclusive + * @return True if the value is above the max limit, false otherwise + */ +template< typename T > +static bool isValueAboveMax( T const & value, Bound< T > const & maxLimit ) +{ + return maxLimit.isInclusive ? ( value > maxLimit.value ) + : ( value >= maxLimit.value ); +} + } /* namespace dataRepository */ } /* namespace geos */ diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 4f5aca3a1e8..81d8b2e54be 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -724,35 +724,38 @@ class Wrapper final : public WrapperBase /** * @brief Set both bounds for this attribute's value. - * @param minValue the minimum allowed value (inclusive) - * @param maxValue the maximum allowed value (inclusive) + * @param min the minimum allowed value (inclusive) + * @param max the maximum allowed value (inclusive) * @param mode the enforcement mode * @return pointer to Wrapper * - * @note @p minValue and @p maxValue are std::optional(s). + * @note @p min and @p max are std::optional(s). * Set them to std::nullopt to disable a limit. * * @code * registerWrapper( viewKeysStruct::fooString(), &m_foo ) + * .setLimits( 0.0, 1.0 ) // sets a minimum value of 0.0 and a maximum value of 1.0 * .setLimits( 0.0, std::nullopt ) // sets a minimum value of 0.0 and no maximum value + * .setLimits( inclusive( 0.0 ), std::nullopt ) // sets an inclusive (default) minimum value + * .setLimits( exclusive( 0.0 ), std::nullopt ) // sets an exclusive maximum value * @endcode */ template< typename U=T > std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > - setLimits( std::optional< T > minValue, - std::optional< T > maxValue, + setLimits( std::optional< Bound< T > > min, + std::optional< Bound< T > > max, LimitsMode mode = LimitsMode::Warning ) { - m_limits.minValue = minValue; - m_limits.maxValue = maxValue; + m_limits.min = min; + m_limits.max = max; m_limitsMode = mode; return *this; } template< typename U=T > std::enable_if_t< !is_limitable_v< U >, Wrapper< T > & > - setLimits( std::optional< T >, - std::optional< T >, + setLimits( std::optional< Bound< T > >, + std::optional< Bound< T > >, LimitsMode mode = LimitsMode::Warning ) { static_assert( is_limitable_v< U >, @@ -766,10 +769,10 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable_v< U >, std::optional< T > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< Bound< T > > const & > getMinValue() const { - return m_limits.minValue; + return m_limits.min; } /** @@ -778,10 +781,10 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable_v< U >, std::optional< T > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< Bound< T > > const & > getMaxValue() const { - return m_limits.maxValue; + return m_limits.max; } virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, @@ -1161,15 +1164,15 @@ class Wrapper final : public WrapperBase std::enable_if_t< is_limitable_v< U >, void > validateLimits() { - if( (!m_limits.minValue.has_value() && !m_limits.maxValue.has_value()) || + if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || m_limitsMode == LimitsMode::Indicative ) { return; } T const & value = reference(); - bool const belowMin = m_limits.minValue.has_value() && ( value < *m_limits.minValue ); - bool const aboveMax = m_limits.maxValue.has_value() && ( value > *m_limits.maxValue ); + bool const belowMin = m_limits.min.has_value() ? isValueBelowMin( value, *m_limits.min ) : false; + bool const aboveMax = m_limits.max.has_value() ? isValueAboveMax( value, *m_limits.max ) : false; if( !belowMin && !aboveMax ) { return; From 7436f3c1eece9c34b1d9a2be8e455454dcc9d32d Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 22 May 2026 15:50:33 +0200 Subject: [PATCH 11/32] support arrays of numbers --- .../dataRepository/AttributeLimits.hpp | 42 +++++++++++++-- src/coreComponents/dataRepository/Wrapper.hpp | 54 +++++++++++++------ 2 files changed, 75 insertions(+), 21 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index fa60c71599b..fdc2a114e1e 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -17,6 +17,7 @@ #ifndef GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ #define GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ +#include "codingUtilities/traits.hpp" #include "common/DataTypes.hpp" #include "common/format/EnumStrings.hpp" #include @@ -48,17 +49,50 @@ ENUM_STRINGS( LimitsMode, "Warning", "Error" ); +/** + * @struct LimitValueType + * @brief Structure giving the underlying value type that limits apply to. + * + * For a scalar T is T itself. For an array T is the type of the values in + * the array (Array::value_type). + */ +template< typename T, bool = traits::is_array_type< T > > +struct LimitValueType +{}; + +template< typename T > +struct LimitValueType< T, true > +{ + using type = typename T::value_type; +}; + +template< typename T > +struct LimitValueType< T, false > +{ + using type = T; +}; + +/** + * @brief Alias resolving to the type that limits apply to + * + * For a scalar value it is the scalar type itself. + * For an array it is the type of the values in the array. + */ +template< typename T > +using limit_value_type_t = typename LimitValueType< T >::type; + /** * @struct is_limitable * @tparam T type to check * @brief Trait determining whether attribute limits can be applied to type @p T * - * Limits apply to scalar numeric types (integer, real32, real64, etc.) + * Limits apply to numeric types (integer, real32, real64, etc.) including arrays + * of numeric types (array1d< integer >, array2d< real64 >, etc.) */ template< typename T > struct is_limitable { - static constexpr bool value = std::is_arithmetic< T >::value; + static constexpr bool value = std::is_arithmetic< limit_value_type_t< T > >::value; }; /** @@ -128,8 +162,8 @@ struct Limits template< typename T > struct Limits< T, true > { - std::optional< Bound< T > > min; - std::optional< Bound< T > > max; + std::optional< Bound< limit_value_type_t< T > > > min; + std::optional< Bound< limit_value_type_t< T > > > max; }; diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 81d8b2e54be..573ffa5acf3 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -742,8 +742,8 @@ class Wrapper final : public WrapperBase */ template< typename U=T > std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > - setLimits( std::optional< Bound< T > > min, - std::optional< Bound< T > > max, + setLimits( std::optional< Bound< limit_value_type_t< T > > > min, + std::optional< Bound< limit_value_type_t< T > > > max, LimitsMode mode = LimitsMode::Warning ) { m_limits.min = min; @@ -753,9 +753,9 @@ class Wrapper final : public WrapperBase } template< typename U=T > - std::enable_if_t< !is_limitable_v< U >, Wrapper< T > & > - setLimits( std::optional< Bound< T > >, - std::optional< Bound< T > >, + std::enable_if_t< !is_limitable_v< U > && !traits::is_array_type< U >, Wrapper< T > & > + setLimits( std::optional< Bound< limit_value_type_t< T > > >, + std::optional< Bound< limit_value_type_t< T > > >, LimitsMode mode = LimitsMode::Warning ) { static_assert( is_limitable_v< U >, @@ -769,7 +769,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable_v< U >, std::optional< Bound< T > > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< Bound< limit_value_type_t< T > > > const & > getMinValue() const { return m_limits.min; @@ -781,7 +781,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable_v< U >, std::optional< Bound< T > > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< Bound< limit_value_type_t< T > > > const & > getMaxValue() const { return m_limits.max; @@ -1160,17 +1160,9 @@ class Wrapper final : public WrapperBase return this->packByIndexImpl< false >( dummy, packList, withMetadata, onDevice, events ); } - template< typename U=T > - std::enable_if_t< is_limitable_v< U >, void > - validateLimits() + template< typename V > + void validateLimitValue( V const & value ) const { - if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || - m_limitsMode == LimitsMode::Indicative ) - { - return; - } - - T const & value = reference(); bool const belowMin = m_limits.min.has_value() ? isValueBelowMin( value, *m_limits.min ) : false; bool const aboveMax = m_limits.max.has_value() ? isValueAboveMax( value, *m_limits.max ) : false; if( !belowMin && !aboveMax ) @@ -1197,6 +1189,34 @@ class Wrapper final : public WrapperBase } } + template< typename U=T > + std::enable_if_t< is_limitable_v< U > && !traits::is_array_type< U >, void > + validateLimits() + { + if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || + m_limitsMode == LimitsMode::Indicative ) + { + return; + } + validateLimitValue( reference() ); + } + + template< typename U=T > + std::enable_if_t< is_limitable_v< U > && traits::is_array_type< U >, void > + validateLimits() + { + if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || + m_limitsMode == LimitsMode::Indicative ) + { + return; + } + auto const values = m_data->toViewConst(); + for( limit_value_type_t< T > value : values ) + { + validateLimitValue( value ); + } + } + template< typename U=T > std::enable_if_t< !is_limitable_v< U >, void > validateLimits() From a456acae4030c84871a42e5cc77508c9e278970d Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 22 May 2026 16:08:09 +0200 Subject: [PATCH 12/32] set validateLimit() to public --- src/coreComponents/dataRepository/Wrapper.hpp | 71 ++++++++++--------- 1 file changed, 36 insertions(+), 35 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 573ffa5acf3..5b12d14ca77 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -787,6 +787,42 @@ class Wrapper final : public WrapperBase return m_limits.max; } + template< typename U=T > + std::enable_if_t< is_limitable_v< U > && !traits::is_array_type< U >, void > + validateLimits() + { + if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || + m_limitsMode == LimitsMode::Indicative ) + { + return; + } + validateLimitValue( reference() ); + } + + template< typename U=T > + std::enable_if_t< is_limitable_v< U > && traits::is_array_type< U >, void > + validateLimits() + { + if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || + m_limitsMode == LimitsMode::Indicative ) + { + return; + } + auto const values = m_data->toViewConst(); + for( limit_value_type_t< T > value : values ) + { + validateLimitValue( value ); + } + } + + template< typename U=T > + std::enable_if_t< !is_limitable_v< U >, void > + validateLimits() + { + /* no-op */ + } + + virtual bool processInputFile( xmlWrapper::xmlNode const & targetNode, xmlWrapper::xmlNodePos const & nodePos ) override { @@ -1189,41 +1225,6 @@ class Wrapper final : public WrapperBase } } - template< typename U=T > - std::enable_if_t< is_limitable_v< U > && !traits::is_array_type< U >, void > - validateLimits() - { - if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || - m_limitsMode == LimitsMode::Indicative ) - { - return; - } - validateLimitValue( reference() ); - } - - template< typename U=T > - std::enable_if_t< is_limitable_v< U > && traits::is_array_type< U >, void > - validateLimits() - { - if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || - m_limitsMode == LimitsMode::Indicative ) - { - return; - } - auto const values = m_data->toViewConst(); - for( limit_value_type_t< T > value : values ) - { - validateLimitValue( value ); - } - } - - template< typename U=T > - std::enable_if_t< !is_limitable_v< U >, void > - validateLimits() - { - /* no-op */ - } - /// flag to indicate whether or not this wrapper is responsible for allocation/deallocation of the object at the /// address of m_data bool m_ownsData; From 80a95418ce9ef5b9ae0c5a1bf7540ca3eb672fae Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 22 May 2026 16:15:28 +0200 Subject: [PATCH 13/32] add tests --- .../dataRepository/unitTests/testWrapper.cpp | 188 ++++++++++++++++++ 1 file changed, 188 insertions(+) diff --git a/src/coreComponents/dataRepository/unitTests/testWrapper.cpp b/src/coreComponents/dataRepository/unitTests/testWrapper.cpp index d04fd2b5440..0dcd64c8018 100644 --- a/src/coreComponents/dataRepository/unitTests/testWrapper.cpp +++ b/src/coreComponents/dataRepository/unitTests/testWrapper.cpp @@ -150,3 +150,191 @@ TYPED_TEST( WrapperSetGet, Description ) this->testDescription( "First description." ); this->testDescription( "Second description." ); } + +class WrapperLimits : public ::testing::Test +{ +protected: + WrapperLimits(): + m_node(), + m_group( "root", m_node ) + {} + + template< typename T > + Wrapper< T > & makeWrapper( string const & name ) + { + return m_group.template registerWrapper< T >( name ); + } + + conduit::Node m_node; + Group m_group; +}; + +TEST_F( WrapperLimits, IsLimitableTrait ) +{ + static_assert( is_limitable_v< integer >, "integer must be limitable" ); + static_assert( is_limitable_v< real64 >, "real64 must be limitable" ); + static_assert( is_limitable_v< array1d< integer > >, "array1d< integer > must be limitable" ); + static_assert( is_limitable_v< array2d< real64 > >, "array2d< real64 > must be limitable" ); + static_assert( is_limitable_v< array3d< integer > >, "array3d< integer > must be limitable" ); + + static_assert( std::is_same< limit_value_type_t< real64 >, real64 >::value, "" ); + static_assert( std::is_same< limit_value_type_t< array1d< real64 > >, real64 >::value, "" ); + static_assert( std::is_same< limit_value_type_t< array2d< integer > >, integer >::value, "" ); +} + +TEST_F( WrapperLimits, ScalarSetGet ) +{ + auto & w = makeWrapper< real64 >( "scalar" ); + w.setLimits( inclusive( 0.0 ), exclusive( 1.0 ), LimitsMode::Error ); + + ASSERT_TRUE( w.getMinValue().has_value() ); + ASSERT_TRUE( w.getMaxValue().has_value() ); + EXPECT_DOUBLE_EQ( w.getMinValue()->value, 0.0 ); + EXPECT_TRUE( w.getMinValue()->isInclusive ); + EXPECT_DOUBLE_EQ( w.getMaxValue()->value, 1.0 ); + EXPECT_FALSE( w.getMaxValue()->isInclusive ); + EXPECT_EQ( w.getLimitsMode(), LimitsMode::Error ); +} + +TEST_F( WrapperLimits, Array1dSetGet ) +{ + auto & w = makeWrapper< array1d< real64 > >( "array1d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + ASSERT_TRUE( w.getMinValue().has_value() ); + ASSERT_TRUE( w.getMaxValue().has_value() ); + EXPECT_DOUBLE_EQ( w.getMinValue()->value, 0.0 ); + EXPECT_DOUBLE_EQ( w.getMaxValue()->value, 1.0 ); + EXPECT_EQ( w.getLimitsMode(), LimitsMode::Error ); +} + +TEST_F( WrapperLimits, ScalarValidateInRange ) +{ + auto & w = makeWrapper< real64 >( "scalar" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.reference() = 0.5; + EXPECT_NO_THROW( w.validateLimits() ); +} + +TEST_F( WrapperLimits, ScalarValidateBelowMinThrows ) +{ + auto & w = makeWrapper< real64 >( "scalar" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.reference() = -0.1; + EXPECT_THROW( w.validateLimits(), InputError ); +} + +TEST_F( WrapperLimits, ScalarValidateAboveMaxThrows ) +{ + auto & w = makeWrapper< real64 >( "scalar" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.reference() = 1.1; + EXPECT_THROW( w.validateLimits(), InputError ); +} + +TEST_F( WrapperLimits, ScalarValidateInclusiveBoundary ) +{ + auto & w = makeWrapper< real64 >( "scalar" ); + w.setLimits( inclusive( 0.0 ), inclusive( 1.0 ), LimitsMode::Error ); + + w.reference() = 0.0; + EXPECT_NO_THROW( w.validateLimits() ); + + w.reference() = 1.0; + EXPECT_NO_THROW( w.validateLimits() ); +} + +TEST_F( WrapperLimits, ScalarValidateExclusiveBoundary ) +{ + auto & w = makeWrapper< real64 >( "scalar" ); + w.setLimits( exclusive( 0.0 ), exclusive( 1.0 ), LimitsMode::Error ); + + w.reference() = 0.0; + EXPECT_THROW( w.validateLimits(), InputError ); + + w.reference() = 0.5; + EXPECT_NO_THROW( w.validateLimits() ); + + w.reference() = 1.0; + EXPECT_THROW( w.validateLimits(), InputError ); +} + +TEST_F( WrapperLimits, Array1dValidateAllInRange ) +{ + auto & w = makeWrapper< array1d< real64 > >( "array1d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + array1d< real64 > & data = w.reference(); + data.resize( 4 ); + data[ 0 ] = 0.0; + data[ 1 ] = 0.25; + data[ 2 ] = 0.75; + data[ 3 ] = 1.0; + + EXPECT_NO_THROW( w.validateLimits() ); +} + +TEST_F( WrapperLimits, Array1dValidateOutOfRange ) +{ + auto & w = makeWrapper< array1d< real64 > >( "array1d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + array1d< real64 > & data = w.reference(); + data.resize( 4 ); + data[ 0 ] = 0.5; + data[ 1 ] = 0.5; + data[ 2 ] = 42.0; + data[ 3 ] = 0.5; + + EXPECT_THROW( w.validateLimits(), InputError ); +} + +TEST_F( WrapperLimits, Array1dValidateEmpty ) +{ + auto & w = makeWrapper< array1d< real64 > >( "array1d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + EXPECT_NO_THROW( w.validateLimits() ); +} + +TEST_F( WrapperLimits, Array2dValidateAllInRange ) +{ + auto & w = makeWrapper< array2d< real64 > >( "array2d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + array2d< real64 > & data = w.reference(); + data.resize( 2, 3 ); + data( 0, 0 ) = 0.1; + data( 0, 1 ) = 0.2; + data( 0, 2 ) = 0.4; + data( 1, 0 ) = 0.6; + data( 1, 1 ) = 0.8; + data( 1, 2 ) = 0.9; + + EXPECT_NO_THROW( w.validateLimits() ); +} + +TEST_F( WrapperLimits, Array2dValidateOutOfRange ) +{ + auto & w = makeWrapper< array2d< real64 > >( "array2d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + array2d< real64 > & data = w.reference(); + data.resize( 2, 3 ); + data( 0, 0 ) = 0.5; + data( 0, 1 ) = 0.5; + data( 0, 2 ) = 0.5; + data( 1, 0 ) = 4000.0; + data( 1, 1 ) = 0.5; + data( 1, 2 ) = 0.5; + + EXPECT_THROW( w.validateLimits(), InputError ); +} + +TEST_F( WrapperLimits, Array2dValidateEmpty ) +{ + auto & w = makeWrapper< array2d< real64 > >( "array2d" ); + w.setLimits( 0.0, 1.0, LimitsMode::Error ); + + EXPECT_NO_THROW( w.validateLimits() ); +} From 65779e4d20dc6a48e35b32bb8a4a02f6d0a8b249 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 22 May 2026 16:58:49 +0200 Subject: [PATCH 14/32] add getDataContext() to the limit validation message --- src/coreComponents/dataRepository/Wrapper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 5b12d14ca77..75f178d47b2 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -1207,7 +1207,7 @@ class Wrapper final : public WrapperBase } string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range.", - value, getName() ); + value, getDataContext() ); switch( m_limitsMode ) { From 54053c7915548304a7f171503b4d98767b1fe929 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 22 May 2026 17:34:25 +0200 Subject: [PATCH 15/32] add range to the limit validation message This part may be refactored, to build the range string somewhere else --- src/coreComponents/dataRepository/Wrapper.hpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 75f178d47b2..2b006f3ca0b 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -1206,8 +1206,14 @@ class Wrapper final : public WrapperBase return; } - string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range.", - value, getDataContext() ); + string const lowerRange = m_limits.min.has_value() + ? GEOS_FMT( "{}{}", m_limits.min->isInclusive ? "[" : "(", m_limits.min->value ) + : string( "(-inf" ); + string const upperRange = m_limits.max.has_value() + ? GEOS_FMT( "{}{}", m_limits.max->value, m_limits.max->isInclusive ? "]" : ")" ) + : string( "+inf)" ); + string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range {}, {}.", + value, getDataContext(), lowerRange, upperRange ); switch( m_limitsMode ) { From 7d08bd2d6fd68ef81b728296b884b2b85552b746 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 22 May 2026 17:35:31 +0200 Subject: [PATCH 16/32] set default mode to Error --- src/coreComponents/dataRepository/Wrapper.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 2b006f3ca0b..cef73f75543 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -744,7 +744,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > setLimits( std::optional< Bound< limit_value_type_t< T > > > min, std::optional< Bound< limit_value_type_t< T > > > max, - LimitsMode mode = LimitsMode::Warning ) + LimitsMode mode = LimitsMode::Error ) { m_limits.min = min; m_limits.max = max; @@ -756,7 +756,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< !is_limitable_v< U > && !traits::is_array_type< U >, Wrapper< T > & > setLimits( std::optional< Bound< limit_value_type_t< T > > >, std::optional< Bound< limit_value_type_t< T > > >, - LimitsMode mode = LimitsMode::Warning ) + LimitsMode mode = LimitsMode::Error ) { static_assert( is_limitable_v< U >, "setLimits is only supported on scalar arithmetic types." ); From ace25d5a00e6d36196e0b7c238861799ca98911d Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 26 May 2026 15:17:36 +0200 Subject: [PATCH 17/32] move range string creation in the Limits struct --- src/coreComponents/dataRepository/AttributeLimits.hpp | 11 +++++++++++ src/coreComponents/dataRepository/Wrapper.hpp | 10 ++-------- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index fdc2a114e1e..12a596b2726 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -164,6 +164,17 @@ struct Limits< T, true > { std::optional< Bound< limit_value_type_t< T > > > min; std::optional< Bound< limit_value_type_t< T > > > max; + + string getRangeStr() const + { + string const lowerRange = this->min.has_value() + ? GEOS_FMT( "{}{}", this->min->isInclusive ? "[" : "(", this->min->value ) + : string( "(-inf" ); + string const upperRange = this->max.has_value() + ? GEOS_FMT( "{}{}", this->max->value, this->max->isInclusive ? "]" : ")" ) + : string( "+inf)" ); + return GEOS_FMT( "{}, {}", lowerRange, upperRange ); + } }; diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index cef73f75543..e11ae55f9a8 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -1206,14 +1206,8 @@ class Wrapper final : public WrapperBase return; } - string const lowerRange = m_limits.min.has_value() - ? GEOS_FMT( "{}{}", m_limits.min->isInclusive ? "[" : "(", m_limits.min->value ) - : string( "(-inf" ); - string const upperRange = m_limits.max.has_value() - ? GEOS_FMT( "{}{}", m_limits.max->value, m_limits.max->isInclusive ? "]" : ")" ) - : string( "+inf)" ); - string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range {}, {}.", - value, getDataContext(), lowerRange, upperRange ); + string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range {}.", + value, getDataContext(), m_limits.getRangeStr() ); switch( m_limitsMode ) { From 0f51019dfb726f1813ea0cf9eabda7b03d28e059 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 29 May 2026 15:35:37 +0200 Subject: [PATCH 18/32] add limits to generated documentation --- .../dataRepository/AttributeLimits.hpp | 5 +++++ src/coreComponents/dataRepository/Wrapper.hpp | 22 +++++++++++++++++++ .../dataRepository/WrapperBase.hpp | 7 ++++++ src/coreComponents/schema/schemaUtilities.cpp | 6 +++++ 4 files changed, 40 insertions(+) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index 12a596b2726..676fc25c76b 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -167,6 +167,11 @@ struct Limits< T, true > string getRangeStr() const { + if( !this->min.has_value() && !this->max.has_value() ) + { + return string(); + } + string const lowerRange = this->min.has_value() ? GEOS_FMT( "{}{}", this->min->isInclusive ? "[" : "(", this->min->value ) : string( "(-inf" ); diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index e11ae55f9a8..e8bc0dd68c9 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -787,6 +787,28 @@ class Wrapper final : public WrapperBase return m_limits.max; } + /** + * @copydoc WrapperBase::getLimitsString() + */ + virtual string getLimitsString() const override + { + return getLimitsStringImpl(); + } + + template< typename U=T > + std::enable_if_t< is_limitable_v< U >, string > + getLimitsStringImpl() const + { + return m_limits.getRangeStr(); + } + + template< typename U=T > + std::enable_if_t< !is_limitable_v< U >, string > + getLimitsStringImpl() const + { + return string(); + } + template< typename U=T > std::enable_if_t< is_limitable_v< U > && !traits::is_array_type< U >, void > validateLimits() diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 5ebfc57471b..2e5b2b31957 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -204,6 +204,13 @@ class WrapperBase */ virtual string getDefaultValueString() const = 0; + /** + * @brief Return a string representing the allowed value range. + * @return A string of the form "[min, max]" using standard interval notation, or + * an empty string if no limits are set. + */ + virtual string getLimitsString() const = 0; + /** * @brief Initialize the wrapper from the input xml node. * @param targetNode the xml node to initialize from. diff --git a/src/coreComponents/schema/schemaUtilities.cpp b/src/coreComponents/schema/schemaUtilities.cpp index 9d447842c09..73b80249072 100644 --- a/src/coreComponents/schema/schemaUtilities.cpp +++ b/src/coreComponents/schema/schemaUtilities.cpp @@ -249,6 +249,12 @@ void SchemaConstruction( Group & group, commentString += "(no description available)"; } + string const limitsString = wrapper.getLimitsString(); + if( !limitsString.empty() ) + { + commentString += GEOS_FMT( " Allowed range: {}", limitsString ); + } + // List of objects that registered this field std::set< string > const & registrars = wrapper.getRegisteringObjects(); if( !registrars.empty() ) From 3ccdc0f9a9ff7859291b50f9dc3a62a4db6d5db6 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Fri, 29 May 2026 16:12:29 +0200 Subject: [PATCH 19/32] add GEOS_UNUSED_PARAM to setLimits overload for non-limits --- src/coreComponents/dataRepository/Wrapper.hpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index e8bc0dd68c9..683c38df492 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -756,7 +756,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< !is_limitable_v< U > && !traits::is_array_type< U >, Wrapper< T > & > setLimits( std::optional< Bound< limit_value_type_t< T > > >, std::optional< Bound< limit_value_type_t< T > > >, - LimitsMode mode = LimitsMode::Error ) + LimitsMode GEOS_UNUSED_PARAM( mode ) ) { static_assert( is_limitable_v< U >, "setLimits is only supported on scalar arithmetic types." ); From 931c469e62905893474a55d354bb4c1e346f0a4e Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Mon, 22 Jun 2026 11:32:52 +0200 Subject: [PATCH 20/32] modify BCBakerRelPerm to use limits --- .../BrooksCoreyBakerRelativePermeability.cpp | 50 ++----------------- src/coreComponents/schema/schema.xsd | 10 ++-- 2 files changed, 10 insertions(+), 50 deletions(-) diff --git a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.cpp b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.cpp index 095d26b2bef..9bdb5f117b2 100644 --- a/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.cpp +++ b/src/coreComponents/constitutive/relativePermeability/BrooksCoreyBakerRelativePermeability.cpp @@ -33,29 +33,34 @@ BrooksCoreyBakerRelativePermeability::BrooksCoreyBakerRelativePermeability( stri { registerWrapper( viewKeyStruct::phaseMinVolumeFractionString(), &m_phaseMinVolumeFraction ). setApplyDefaultValue( 0.0 ). + setLimits( 0.0, 1.0 ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Minimum volume fraction value for each phase (between 0 and 1) used to compute the relative permeability " ); registerWrapper( viewKeyStruct::waterOilRelPermExponentString(), &m_waterOilRelPermExponent ). setApplyDefaultValue( 1.0 ). + setLimits( 0.0, std::nullopt ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Rel perm power law exponent for the pair (water phase, oil phase) at residual gas saturation\n" "The expected format is \"{ waterExp, oilExp }\", in that order" ); registerWrapper( viewKeyStruct::waterOilRelPermMaxValueString(), &m_waterOilRelPermMaxValue ). setApplyDefaultValue( 0.0 ). + setLimits( 0.0, 1.0 ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Maximum rel perm value for the pair (water phase, oil phase) at residual gas saturation\n" "The expected format is \"{ waterMax, oilMax }\", in that order" ); registerWrapper( viewKeyStruct::gasOilRelPermExponentString(), &m_gasOilRelPermExponent ). setApplyDefaultValue( 1.0 ). + setLimits( 0.0, std::nullopt ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Rel perm power law exponent for the pair (gas phase, oil phase) at residual water saturation\n" "The expected format is \"{ gasExp, oilExp }\", in that order" ); registerWrapper( viewKeyStruct::gasOilRelPermMaxValueString(), &m_gasOilRelPermMaxValue ). setApplyDefaultValue( 0.0 ). + setLimits( 0.0, 1.0 ). setInputFlag( InputFlags::OPTIONAL ). setDescription( "Maximum rel perm value for the pair (gas phase, oil phase) at residual water saturation\n" "The expected format is \"{ gasMax, oilMax }\", in that order" ); @@ -97,17 +102,6 @@ void BrooksCoreyBakerRelativePermeability::postInputInitialization() m_volFracScale = 1.0; for( integer ip = 0; ip < numFluidPhases(); ++ip ) { - auto const errorMsg = [&]( auto const & attribute ) - { - return GEOS_FMT( "invalid value at {}[{}]", attribute, ip ); - }; - GEOS_UNUSED_VAR( errorMsg ); - GEOS_THROW_IF_LT_MSG( m_phaseMinVolumeFraction[ip], 0.0, - errorMsg( viewKeyStruct::phaseMinVolumeFractionString() ), - InputError, getDataContext() ); - GEOS_THROW_IF_GT_MSG( m_phaseMinVolumeFraction[ip], 1.0, - errorMsg( viewKeyStruct::phaseMinVolumeFractionString() ), - InputError, getDataContext() ); m_volFracScale -= m_phaseMinVolumeFraction[ip]; } @@ -116,40 +110,6 @@ void BrooksCoreyBakerRelativePermeability::postInputInitialization() InputError, getDataContext() ); - for( integer ip = 0; ip < 2; ++ip ) - { - auto const errorMsg = [&]( auto const & attribute ) - { - return GEOS_FMT( "invalid value at {}[{}]", attribute, ip ); - }; - GEOS_UNUSED_VAR( errorMsg ); - if( m_phaseOrder[PhaseType::WATER] >= 0 ) - { - GEOS_THROW_IF_LT_MSG( m_waterOilRelPermExponent[ip], 0.0, - errorMsg( viewKeyStruct::waterOilRelPermExponentString() ), - InputError, getDataContext() ); - GEOS_THROW_IF_LT_MSG( m_waterOilRelPermMaxValue[ip], 0.0, - errorMsg( viewKeyStruct::waterOilRelPermMaxValueString() ), - InputError, getDataContext() ); - GEOS_THROW_IF_GT_MSG( m_waterOilRelPermMaxValue[ip], 1.0, - errorMsg( viewKeyStruct::waterOilRelPermMaxValueString() ), - InputError, getDataContext() ); - } - - if( m_phaseOrder[PhaseType::GAS] >= 0 ) - { - GEOS_THROW_IF_LT_MSG( m_gasOilRelPermExponent[ip], 0.0, - errorMsg( viewKeyStruct::gasOilRelPermExponentString() ), - InputError, getDataContext() ); - GEOS_THROW_IF_LT_MSG( m_gasOilRelPermMaxValue[ip], 0.0, - errorMsg( viewKeyStruct::gasOilRelPermMaxValueString() ), - InputError, getDataContext() ); - GEOS_THROW_IF_GT_MSG( m_gasOilRelPermMaxValue[ip], 1.0, - errorMsg( viewKeyStruct::gasOilRelPermMaxValueString() ), - InputError, getDataContext() ); - } - } - if( m_phaseOrder[PhaseType::WATER] >= 0 && m_phaseOrder[PhaseType::GAS] >= 0 ) { real64 const mean = 0.5 * ( m_gasOilRelPermMaxValue[GasOilPairPhaseType::OIL] diff --git a/src/coreComponents/schema/schema.xsd b/src/coreComponents/schema/schema.xsd index 00589e978ab..337474b9b09 100644 --- a/src/coreComponents/schema/schema.xsd +++ b/src/coreComponents/schema/schema.xsd @@ -6925,20 +6925,20 @@ For instance, if "oil" is before "gas" in "phaseNames", the table order should b +The expected format is "{ gasExp, oilExp }", in that order Allowed range: [0, +inf)--> +The expected format is "{ gasMax, oilMax }", in that order Allowed range: [0, 1]--> - + +The expected format is "{ waterExp, oilExp }", in that order Allowed range: [0, +inf)--> +The expected format is "{ waterMax, oilMax }", in that order Allowed range: [0, 1]--> From 9067eec38114b17875c048b10ef50fd438484c80 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 09:11:41 +0200 Subject: [PATCH 21/32] remove un-needed inclusions --- src/coreComponents/dataRepository/Wrapper.hpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 683c38df492..9534e22d5c6 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -21,8 +21,6 @@ #define GEOS_DATAREPOSITORY_WRAPPER_HPP_ // Source inclues -#include "common/format/Format.hpp" -#include "common/logger/Logger.hpp" #include "dataRepository/AttributeLimits.hpp" #include "wrapperHelpers.hpp" #include "KeyNames.hpp" From 600d6cf7b44166812a1be5023ef7ecf091f525b6 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 09:21:02 +0200 Subject: [PATCH 22/32] rename Bound struct to WrapperBound --- .../dataRepository/AttributeLimits.hpp | 28 +++++++++---------- src/coreComponents/dataRepository/Wrapper.hpp | 12 ++++---- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index 676fc25c76b..eca8ae9f131 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -102,27 +102,27 @@ template< typename T > inline constexpr bool is_limitable_v = is_limitable< T >::value; /** - * @struct Bound + * @struct WrapperBound * @brief Structure containing informations about an attribute limit. */ template< typename T > -struct Bound +struct WrapperBound { T value; bool isInclusive = true; /** - * @brief Bound constructor to write a limit without the "Bound{ ... }" syntax + * @brief WrapperBound constructor to write a limit without the "WrapperBound{ ... }" syntax * @param value The limit value to set * @param isInclusive Wether the limit should be inclusive or not * * @code - * .setLimits( 0.0, 1.0 ) // where setLimits takes `Bound` parameters, those parameters can - * // be written only with the value. The isInclusive property will + * .setLimits( 0.0, 1.0 ) // where setLimits takes `WrapperBound` parameters, those parameters + * // can be written only with the value. The isInclusive property will * // default to true. * @endcode */ - Bound( T v, bool inclusive = true ) + WrapperBound( T v, bool inclusive = true ) : value( v ), isInclusive( inclusive ) {} }; @@ -132,9 +132,9 @@ struct Bound * @param value The inclusive limit value to set */ template< typename T > -Bound< T > inclusive( T value ) +WrapperBound< T > inclusive( T value ) { - return Bound< T >{ value, /*isInclusive*/ true }; + return WrapperBound< T >{ value, /*isInclusive*/ true }; } /** @@ -142,9 +142,9 @@ Bound< T > inclusive( T value ) * @param value The inclusive limit value to set */ template< typename T > -Bound< T > exclusive( T value ) +WrapperBound< T > exclusive( T value ) { - return Bound< T >{ value, /*isInclusive*/ false }; + return WrapperBound< T >{ value, /*isInclusive*/ false }; } /** @@ -162,8 +162,8 @@ struct Limits template< typename T > struct Limits< T, true > { - std::optional< Bound< limit_value_type_t< T > > > min; - std::optional< Bound< limit_value_type_t< T > > > max; + std::optional< WrapperBound< limit_value_type_t< T > > > min; + std::optional< WrapperBound< limit_value_type_t< T > > > max; string getRangeStr() const { @@ -193,7 +193,7 @@ struct Limits< T, true > * @return True if the value is below the min limit, false otherwise */ template< typename T > -static bool isValueBelowMin( T const & value, Bound< T > const & minLimit ) +static bool isValueBelowMin( T const & value, WrapperBound< T > const & minLimit ) { return minLimit.isInclusive ? ( value < minLimit.value ) : ( value <= minLimit.value ); @@ -207,7 +207,7 @@ static bool isValueBelowMin( T const & value, Bound< T > const & minLimit ) * @return True if the value is above the max limit, false otherwise */ template< typename T > -static bool isValueAboveMax( T const & value, Bound< T > const & maxLimit ) +static bool isValueAboveMax( T const & value, WrapperBound< T > const & maxLimit ) { return maxLimit.isInclusive ? ( value > maxLimit.value ) : ( value >= maxLimit.value ); diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 9534e22d5c6..e3bb8cc350f 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -740,8 +740,8 @@ class Wrapper final : public WrapperBase */ template< typename U=T > std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > - setLimits( std::optional< Bound< limit_value_type_t< T > > > min, - std::optional< Bound< limit_value_type_t< T > > > max, + setLimits( std::optional< WrapperBound< limit_value_type_t< T > > > min, + std::optional< WrapperBound< limit_value_type_t< T > > > max, LimitsMode mode = LimitsMode::Error ) { m_limits.min = min; @@ -752,8 +752,8 @@ class Wrapper final : public WrapperBase template< typename U=T > std::enable_if_t< !is_limitable_v< U > && !traits::is_array_type< U >, Wrapper< T > & > - setLimits( std::optional< Bound< limit_value_type_t< T > > >, - std::optional< Bound< limit_value_type_t< T > > >, + setLimits( std::optional< WrapperBound< limit_value_type_t< T > > >, + std::optional< WrapperBound< limit_value_type_t< T > > >, LimitsMode GEOS_UNUSED_PARAM( mode ) ) { static_assert( is_limitable_v< U >, @@ -767,7 +767,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable_v< U >, std::optional< Bound< limit_value_type_t< T > > > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< WrapperBound< limit_value_type_t< T > > > const & > getMinValue() const { return m_limits.min; @@ -779,7 +779,7 @@ class Wrapper final : public WrapperBase * @note Only available when T is a limitable type */ template< typename U=T > - std::enable_if_t< is_limitable_v< U >, std::optional< Bound< limit_value_type_t< T > > > const & > + std::enable_if_t< is_limitable_v< U >, std::optional< WrapperBound< limit_value_type_t< T > > > const & > getMaxValue() const { return m_limits.max; From 3de917495538a61a4c86b5f653c0997f6d6d4973 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 10:39:23 +0200 Subject: [PATCH 23/32] rename Limits to WrapperLimits --- .../dataRepository/AttributeLimits.hpp | 14 ++--- src/coreComponents/dataRepository/Wrapper.hpp | 16 ++--- .../dataRepository/WrapperBase.cpp | 2 +- .../dataRepository/WrapperBase.hpp | 6 +- .../dataRepository/unitTests/testWrapper.cpp | 62 +++++++++---------- 5 files changed, 50 insertions(+), 50 deletions(-) diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/AttributeLimits.hpp index eca8ae9f131..90b188fa93e 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/AttributeLimits.hpp @@ -30,21 +30,21 @@ namespace dataRepository { /** - * @enum LimitsMode + * @enum WrapperLimitsMode * @brief Enforcement mode associated with the limits of an attribute * * - Indicative: the limits are documentation only, no runtime check is performed. * - Warning: a value outside the limits emits a runtime warning. * - Error: a value outside the limits throws. */ -enum class LimitsMode : integer +enum class WrapperLimitsMode : integer { Indicative, Warning, Error }; -ENUM_STRINGS( LimitsMode, +ENUM_STRINGS( WrapperLimitsMode, "Indicative", "Warning", "Error" ); @@ -86,7 +86,7 @@ using limit_value_type_t = typename LimitValueType< T >::type; * @tparam T type to check * @brief Trait determining whether attribute limits can be applied to type @p T * - * Limits apply to numeric types (integer, real32, real64, etc.) including arrays + * WrapperLimits apply to numeric types (integer, real32, real64, etc.) including arrays * of numeric types (array1d< integer >, array2d< real64 >, etc.) */ template< typename T > @@ -148,7 +148,7 @@ WrapperBound< T > exclusive( T value ) } /** - * @struct Limits + * @struct WrapperLimits * @brief Storage for the optional min/max bounds of a wrapped value. * * Specialized so that the members (std::optional< T >) are only instanciated @@ -156,11 +156,11 @@ WrapperBound< T > exclusive( T value ) * abstract types that can't be instantiated with std::optional< absT >. */ template< typename T, bool = is_limitable_v< T > > -struct Limits +struct WrapperLimits {}; template< typename T > -struct Limits< T, true > +struct WrapperLimits< T, true > { std::optional< WrapperBound< limit_value_type_t< T > > > min; std::optional< WrapperBound< limit_value_type_t< T > > > max; diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index e3bb8cc350f..2e7bc1429ba 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -742,7 +742,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > setLimits( std::optional< WrapperBound< limit_value_type_t< T > > > min, std::optional< WrapperBound< limit_value_type_t< T > > > max, - LimitsMode mode = LimitsMode::Error ) + WrapperLimitsMode mode = WrapperLimitsMode::Error ) { m_limits.min = min; m_limits.max = max; @@ -754,7 +754,7 @@ class Wrapper final : public WrapperBase std::enable_if_t< !is_limitable_v< U > && !traits::is_array_type< U >, Wrapper< T > & > setLimits( std::optional< WrapperBound< limit_value_type_t< T > > >, std::optional< WrapperBound< limit_value_type_t< T > > >, - LimitsMode GEOS_UNUSED_PARAM( mode ) ) + WrapperLimitsMode GEOS_UNUSED_PARAM( mode ) ) { static_assert( is_limitable_v< U >, "setLimits is only supported on scalar arithmetic types." ); @@ -812,7 +812,7 @@ class Wrapper final : public WrapperBase validateLimits() { if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || - m_limitsMode == LimitsMode::Indicative ) + m_limitsMode == WrapperLimitsMode::Indicative ) { return; } @@ -824,7 +824,7 @@ class Wrapper final : public WrapperBase validateLimits() { if( (!m_limits.min.has_value() && !m_limits.max.has_value()) || - m_limitsMode == LimitsMode::Indicative ) + m_limitsMode == WrapperLimitsMode::Indicative ) { return; } @@ -1231,16 +1231,16 @@ class Wrapper final : public WrapperBase switch( m_limitsMode ) { - case LimitsMode::Warning: + case WrapperLimitsMode::Warning: GEOS_WARNING( msg ); break; - case LimitsMode::Error: + case WrapperLimitsMode::Error: GEOS_THROW( msg, InputError ); break; default: - GEOS_LOG_RANK_0( "Unimplemented LimitsMode" ); + GEOS_LOG_RANK_0( "Unimplemented WrapperLimitsMode" ); break; } } @@ -1261,7 +1261,7 @@ class Wrapper final : public WrapperBase wrapperHelpers::ArrayDimLabels< T > m_dimLabels; /// stores the (optional) min/max bounds for the wrapped value. - Limits< T > m_limits; + WrapperLimits< T > m_limits; }; } diff --git a/src/coreComponents/dataRepository/WrapperBase.cpp b/src/coreComponents/dataRepository/WrapperBase.cpp index 684679b459f..7dc60572359 100644 --- a/src/coreComponents/dataRepository/WrapperBase.cpp +++ b/src/coreComponents/dataRepository/WrapperBase.cpp @@ -39,7 +39,7 @@ WrapperBase::WrapperBase( string const & name, m_inputFlag( InputFlags::INVALID ), m_successfulReadFromInput( false ), m_description(), - m_limitsMode( LimitsMode::Indicative ), + m_limitsMode( WrapperLimitsMode::Indicative ), m_rtTypeName( rtTypeName ), m_registeringObjects(), m_conduitNode( parent.getConduitNode()[ name ] ), diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index 2e5b2b31957..d420d1b2afe 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -539,9 +539,9 @@ class WrapperBase /** * @brief Get the enforcement mode of the (optional) attribute limits - * @return the LimitsMode of the wrapper + * @return the WrapperLimitsMode of the wrapper */ - LimitsMode getLimitsMode() const + WrapperLimitsMode getLimitsMode() const { return m_limitsMode; } @@ -717,7 +717,7 @@ class WrapperBase string m_description; /// Enforcement mode of the (optional) attribute limits - LimitsMode m_limitsMode; + WrapperLimitsMode m_limitsMode; /// A string regex to validate the input values string to parse for the wrapped object string m_rtTypeName; diff --git a/src/coreComponents/dataRepository/unitTests/testWrapper.cpp b/src/coreComponents/dataRepository/unitTests/testWrapper.cpp index 0dcd64c8018..77f27374c4e 100644 --- a/src/coreComponents/dataRepository/unitTests/testWrapper.cpp +++ b/src/coreComponents/dataRepository/unitTests/testWrapper.cpp @@ -151,10 +151,10 @@ TYPED_TEST( WrapperSetGet, Description ) this->testDescription( "Second description." ); } -class WrapperLimits : public ::testing::Test +class WrapperLimitsTest : public ::testing::Test { protected: - WrapperLimits(): + WrapperLimitsTest(): m_node(), m_group( "root", m_node ) {} @@ -169,7 +169,7 @@ class WrapperLimits : public ::testing::Test Group m_group; }; -TEST_F( WrapperLimits, IsLimitableTrait ) +TEST_F( WrapperLimitsTest, IsLimitableTrait ) { static_assert( is_limitable_v< integer >, "integer must be limitable" ); static_assert( is_limitable_v< real64 >, "real64 must be limitable" ); @@ -182,10 +182,10 @@ TEST_F( WrapperLimits, IsLimitableTrait ) static_assert( std::is_same< limit_value_type_t< array2d< integer > >, integer >::value, "" ); } -TEST_F( WrapperLimits, ScalarSetGet ) +TEST_F( WrapperLimitsTest, ScalarSetGet ) { auto & w = makeWrapper< real64 >( "scalar" ); - w.setLimits( inclusive( 0.0 ), exclusive( 1.0 ), LimitsMode::Error ); + w.setLimits( inclusive( 0.0 ), exclusive( 1.0 ), WrapperLimitsMode::Error ); ASSERT_TRUE( w.getMinValue().has_value() ); ASSERT_TRUE( w.getMaxValue().has_value() ); @@ -193,49 +193,49 @@ TEST_F( WrapperLimits, ScalarSetGet ) EXPECT_TRUE( w.getMinValue()->isInclusive ); EXPECT_DOUBLE_EQ( w.getMaxValue()->value, 1.0 ); EXPECT_FALSE( w.getMaxValue()->isInclusive ); - EXPECT_EQ( w.getLimitsMode(), LimitsMode::Error ); + EXPECT_EQ( w.getLimitsMode(), WrapperLimitsMode::Error ); } -TEST_F( WrapperLimits, Array1dSetGet ) +TEST_F( WrapperLimitsTest, Array1dSetGet ) { auto & w = makeWrapper< array1d< real64 > >( "array1d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); ASSERT_TRUE( w.getMinValue().has_value() ); ASSERT_TRUE( w.getMaxValue().has_value() ); EXPECT_DOUBLE_EQ( w.getMinValue()->value, 0.0 ); EXPECT_DOUBLE_EQ( w.getMaxValue()->value, 1.0 ); - EXPECT_EQ( w.getLimitsMode(), LimitsMode::Error ); + EXPECT_EQ( w.getLimitsMode(), WrapperLimitsMode::Error ); } -TEST_F( WrapperLimits, ScalarValidateInRange ) +TEST_F( WrapperLimitsTest, ScalarValidateInRange ) { auto & w = makeWrapper< real64 >( "scalar" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); w.reference() = 0.5; EXPECT_NO_THROW( w.validateLimits() ); } -TEST_F( WrapperLimits, ScalarValidateBelowMinThrows ) +TEST_F( WrapperLimitsTest, ScalarValidateBelowMinThrows ) { auto & w = makeWrapper< real64 >( "scalar" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); w.reference() = -0.1; EXPECT_THROW( w.validateLimits(), InputError ); } -TEST_F( WrapperLimits, ScalarValidateAboveMaxThrows ) +TEST_F( WrapperLimitsTest, ScalarValidateAboveMaxThrows ) { auto & w = makeWrapper< real64 >( "scalar" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); w.reference() = 1.1; EXPECT_THROW( w.validateLimits(), InputError ); } -TEST_F( WrapperLimits, ScalarValidateInclusiveBoundary ) +TEST_F( WrapperLimitsTest, ScalarValidateInclusiveBoundary ) { auto & w = makeWrapper< real64 >( "scalar" ); - w.setLimits( inclusive( 0.0 ), inclusive( 1.0 ), LimitsMode::Error ); + w.setLimits( inclusive( 0.0 ), inclusive( 1.0 ), WrapperLimitsMode::Error ); w.reference() = 0.0; EXPECT_NO_THROW( w.validateLimits() ); @@ -244,10 +244,10 @@ TEST_F( WrapperLimits, ScalarValidateInclusiveBoundary ) EXPECT_NO_THROW( w.validateLimits() ); } -TEST_F( WrapperLimits, ScalarValidateExclusiveBoundary ) +TEST_F( WrapperLimitsTest, ScalarValidateExclusiveBoundary ) { auto & w = makeWrapper< real64 >( "scalar" ); - w.setLimits( exclusive( 0.0 ), exclusive( 1.0 ), LimitsMode::Error ); + w.setLimits( exclusive( 0.0 ), exclusive( 1.0 ), WrapperLimitsMode::Error ); w.reference() = 0.0; EXPECT_THROW( w.validateLimits(), InputError ); @@ -259,10 +259,10 @@ TEST_F( WrapperLimits, ScalarValidateExclusiveBoundary ) EXPECT_THROW( w.validateLimits(), InputError ); } -TEST_F( WrapperLimits, Array1dValidateAllInRange ) +TEST_F( WrapperLimitsTest, Array1dValidateAllInRange ) { auto & w = makeWrapper< array1d< real64 > >( "array1d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); array1d< real64 > & data = w.reference(); data.resize( 4 ); @@ -274,10 +274,10 @@ TEST_F( WrapperLimits, Array1dValidateAllInRange ) EXPECT_NO_THROW( w.validateLimits() ); } -TEST_F( WrapperLimits, Array1dValidateOutOfRange ) +TEST_F( WrapperLimitsTest, Array1dValidateOutOfRange ) { auto & w = makeWrapper< array1d< real64 > >( "array1d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); array1d< real64 > & data = w.reference(); data.resize( 4 ); @@ -289,18 +289,18 @@ TEST_F( WrapperLimits, Array1dValidateOutOfRange ) EXPECT_THROW( w.validateLimits(), InputError ); } -TEST_F( WrapperLimits, Array1dValidateEmpty ) +TEST_F( WrapperLimitsTest, Array1dValidateEmpty ) { auto & w = makeWrapper< array1d< real64 > >( "array1d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); EXPECT_NO_THROW( w.validateLimits() ); } -TEST_F( WrapperLimits, Array2dValidateAllInRange ) +TEST_F( WrapperLimitsTest, Array2dValidateAllInRange ) { auto & w = makeWrapper< array2d< real64 > >( "array2d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); array2d< real64 > & data = w.reference(); data.resize( 2, 3 ); @@ -314,10 +314,10 @@ TEST_F( WrapperLimits, Array2dValidateAllInRange ) EXPECT_NO_THROW( w.validateLimits() ); } -TEST_F( WrapperLimits, Array2dValidateOutOfRange ) +TEST_F( WrapperLimitsTest, Array2dValidateOutOfRange ) { auto & w = makeWrapper< array2d< real64 > >( "array2d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); array2d< real64 > & data = w.reference(); data.resize( 2, 3 ); @@ -331,10 +331,10 @@ TEST_F( WrapperLimits, Array2dValidateOutOfRange ) EXPECT_THROW( w.validateLimits(), InputError ); } -TEST_F( WrapperLimits, Array2dValidateEmpty ) +TEST_F( WrapperLimitsTest, Array2dValidateEmpty ) { auto & w = makeWrapper< array2d< real64 > >( "array2d" ); - w.setLimits( 0.0, 1.0, LimitsMode::Error ); + w.setLimits( 0.0, 1.0, WrapperLimitsMode::Error ); EXPECT_NO_THROW( w.validateLimits() ); } From 2a0af9fdc6db81bfc2ed73e24dd3c7d9d378cce4 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 10:51:05 +0200 Subject: [PATCH 24/32] rename file AttributeLimits.hpp to WrapperLimits.hpp --- src/coreComponents/dataRepository/CMakeLists.txt | 2 +- src/coreComponents/dataRepository/Wrapper.hpp | 2 +- src/coreComponents/dataRepository/WrapperBase.hpp | 2 +- .../{AttributeLimits.hpp => WrapperLimits.hpp} | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) rename src/coreComponents/dataRepository/{AttributeLimits.hpp => WrapperLimits.hpp} (97%) diff --git a/src/coreComponents/dataRepository/CMakeLists.txt b/src/coreComponents/dataRepository/CMakeLists.txt index f75d8e70ca7..521e7d1fad4 100644 --- a/src/coreComponents/dataRepository/CMakeLists.txt +++ b/src/coreComponents/dataRepository/CMakeLists.txt @@ -22,7 +22,6 @@ Also contains a wrapper to process entries from an xml file into data types. # Specify all headers # set( dataRepository_headers - AttributeLimits.hpp BufferOps.hpp BufferOpsDevice.hpp BufferOps_inline.hpp @@ -44,6 +43,7 @@ set( dataRepository_headers Wrapper.hpp WrapperBase.hpp wrapperHelpers.hpp + WrapperLimits.hpp xmlWrapper.hpp DataContext.hpp GroupContext.hpp diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 2e7bc1429ba..79c52bc66d3 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -21,7 +21,7 @@ #define GEOS_DATAREPOSITORY_WRAPPER_HPP_ // Source inclues -#include "dataRepository/AttributeLimits.hpp" +#include "dataRepository/WrapperLimits.hpp" #include "wrapperHelpers.hpp" #include "KeyNames.hpp" #include "LvArray/src/limits.hpp" diff --git a/src/coreComponents/dataRepository/WrapperBase.hpp b/src/coreComponents/dataRepository/WrapperBase.hpp index d420d1b2afe..7935bf6b45d 100644 --- a/src/coreComponents/dataRepository/WrapperBase.hpp +++ b/src/coreComponents/dataRepository/WrapperBase.hpp @@ -21,7 +21,7 @@ #include "common/DataTypes.hpp" #include "common/GEOS_RAJA_Interface.hpp" #include "common/Span.hpp" -#include "dataRepository/AttributeLimits.hpp" +#include "dataRepository/WrapperLimits.hpp" #include "InputFlags.hpp" #include "xmlWrapper.hpp" #include "RestartFlags.hpp" diff --git a/src/coreComponents/dataRepository/AttributeLimits.hpp b/src/coreComponents/dataRepository/WrapperLimits.hpp similarity index 97% rename from src/coreComponents/dataRepository/AttributeLimits.hpp rename to src/coreComponents/dataRepository/WrapperLimits.hpp index 90b188fa93e..617863fbf1b 100644 --- a/src/coreComponents/dataRepository/AttributeLimits.hpp +++ b/src/coreComponents/dataRepository/WrapperLimits.hpp @@ -14,8 +14,8 @@ */ -#ifndef GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ -#define GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ +#ifndef GEOS_DATAREPOSITORY_WRAPPERLIMITS_HPP_ +#define GEOS_DATAREPOSITORY_WRAPPERLIMITS_HPP_ #include "codingUtilities/traits.hpp" #include "common/DataTypes.hpp" @@ -217,4 +217,4 @@ static bool isValueAboveMax( T const & value, WrapperBound< T > const & maxLimit } /* namespace geos */ -#endif /* GEOS_DATAREPOSITORY_ATTRIBUTELIMITS_HPP_ */ +#endif /* GEOS_DATAREPOSITORY_WRAPPERLIMITS_HPP_ */ From 23d56cec5fe8d9988a1595710a5fc1a05399bab6 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 10:55:15 +0200 Subject: [PATCH 25/32] add doxygen group delimiters for helper methods in WrapperLimits --- src/coreComponents/dataRepository/WrapperLimits.hpp | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/WrapperLimits.hpp b/src/coreComponents/dataRepository/WrapperLimits.hpp index 617863fbf1b..ba81b52e7f7 100644 --- a/src/coreComponents/dataRepository/WrapperLimits.hpp +++ b/src/coreComponents/dataRepository/WrapperLimits.hpp @@ -114,7 +114,7 @@ struct WrapperBound /** * @brief WrapperBound constructor to write a limit without the "WrapperBound{ ... }" syntax * @param value The limit value to set - * @param isInclusive Wether the limit should be inclusive or not + * @param isInclusive Whether the limit should be inclusive or not * * @code * .setLimits( 0.0, 1.0 ) // where setLimits takes `WrapperBound` parameters, those parameters @@ -183,7 +183,10 @@ struct WrapperLimits< T, true > }; -// Helper methods +/** + * @name Helper methods + */ +///@{ /** * @brief Compare the given value with the min limit, taking account for the inclusive xor exclusive @@ -213,6 +216,8 @@ static bool isValueAboveMax( T const & value, WrapperBound< T > const & maxLimit : ( value >= maxLimit.value ); } +///@} + } /* namespace dataRepository */ } /* namespace geos */ From 1af0ec5c7f48ef1c77063f51f2991acd2dc242ec Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 10:57:36 +0200 Subject: [PATCH 26/32] remove static qualifier --- src/coreComponents/dataRepository/WrapperLimits.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/coreComponents/dataRepository/WrapperLimits.hpp b/src/coreComponents/dataRepository/WrapperLimits.hpp index ba81b52e7f7..6b6574049d2 100644 --- a/src/coreComponents/dataRepository/WrapperLimits.hpp +++ b/src/coreComponents/dataRepository/WrapperLimits.hpp @@ -196,7 +196,7 @@ struct WrapperLimits< T, true > * @return True if the value is below the min limit, false otherwise */ template< typename T > -static bool isValueBelowMin( T const & value, WrapperBound< T > const & minLimit ) +bool isValueBelowMin( T const & value, WrapperBound< T > const & minLimit ) { return minLimit.isInclusive ? ( value < minLimit.value ) : ( value <= minLimit.value ); @@ -210,7 +210,7 @@ static bool isValueBelowMin( T const & value, WrapperBound< T > const & minLimit * @return True if the value is above the max limit, false otherwise */ template< typename T > -static bool isValueAboveMax( T const & value, WrapperBound< T > const & maxLimit ) +bool isValueAboveMax( T const & value, WrapperBound< T > const & maxLimit ) { return maxLimit.isInclusive ? ( value > maxLimit.value ) : ( value >= maxLimit.value ); From 919b359bff06e9d2515ce92fdedec0d7e1df0e3a Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 14:40:34 +0200 Subject: [PATCH 27/32] add check for min <= max when setting a limit --- src/coreComponents/dataRepository/Wrapper.hpp | 5 +++++ .../dataRepository/WrapperLimits.hpp | 20 +++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 79c52bc66d3..27b3ede79b6 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -744,6 +744,11 @@ class Wrapper final : public WrapperBase std::optional< WrapperBound< limit_value_type_t< T > > > max, WrapperLimitsMode mode = WrapperLimitsMode::Error ) { + if( min.has_value() && max.has_value() ) + { + GEOS_ASSERT_LE_MSG( min.value(), max.value(), + "Min value should be less or equal to max value." ); + } m_limits.min = min; m_limits.max = max; m_limitsMode = mode; diff --git a/src/coreComponents/dataRepository/WrapperLimits.hpp b/src/coreComponents/dataRepository/WrapperLimits.hpp index 6b6574049d2..d151ef1a415 100644 --- a/src/coreComponents/dataRepository/WrapperLimits.hpp +++ b/src/coreComponents/dataRepository/WrapperLimits.hpp @@ -125,6 +125,26 @@ struct WrapperBound WrapperBound( T v, bool inclusive = true ) : value( v ), isInclusive( inclusive ) {} + + /// @cond DO_NOT_DOCUMENT + friend bool operator<( WrapperBound const & left, WrapperBound const & right ) + { return left.value < right.value; } + + friend bool operator>( WrapperBound const & left, WrapperBound const & right ) + { return !( operator<( right, left ) ); } + + friend bool operator<=( WrapperBound const & left, WrapperBound const & right ) + { return !( operator>( left, right ) ); } + + friend bool operator>=( WrapperBound const & left, WrapperBound const & right ) + { return !( operator<( left, right ) ); } + + friend inline std::ostream & operator<<( std::ostream & os, WrapperBound const & bound ) + { + os << bound.value; + return os; + } + ///@endcond }; /** From 0022a3953927ca4350e3971426e38938dc3143f9 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 17:39:52 +0200 Subject: [PATCH 28/32] remove includes --- src/coreComponents/dataRepository/Wrapper.hpp | 2 -- src/coreComponents/dataRepository/WrapperLimits.hpp | 4 +--- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 27b3ede79b6..851bbefcaf8 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -21,7 +21,6 @@ #define GEOS_DATAREPOSITORY_WRAPPER_HPP_ // Source inclues -#include "dataRepository/WrapperLimits.hpp" #include "wrapperHelpers.hpp" #include "KeyNames.hpp" #include "LvArray/src/limits.hpp" @@ -39,7 +38,6 @@ // System includes #include -#include #include namespace geos diff --git a/src/coreComponents/dataRepository/WrapperLimits.hpp b/src/coreComponents/dataRepository/WrapperLimits.hpp index d151ef1a415..8f54794b0b5 100644 --- a/src/coreComponents/dataRepository/WrapperLimits.hpp +++ b/src/coreComponents/dataRepository/WrapperLimits.hpp @@ -20,8 +20,6 @@ #include "codingUtilities/traits.hpp" #include "common/DataTypes.hpp" #include "common/format/EnumStrings.hpp" -#include -#include namespace geos { @@ -131,7 +129,7 @@ struct WrapperBound { return left.value < right.value; } friend bool operator>( WrapperBound const & left, WrapperBound const & right ) - { return !( operator<( right, left ) ); } + { return operator<( right, left ); } friend bool operator<=( WrapperBound const & left, WrapperBound const & right ) { return !( operator>( left, right ) ); } From d0cb744748b847a1b252f6d2734542d909422284 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Tue, 23 Jun 2026 18:01:05 +0200 Subject: [PATCH 29/32] move getDataContext() in logger macros --- src/coreComponents/dataRepository/Wrapper.hpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 851bbefcaf8..6ba4cba6486 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -1229,17 +1229,17 @@ class Wrapper final : public WrapperBase return; } - string const msg = GEOS_FMT( "Value {} for attribute '{}' is outside the allowed range {}.", - value, getDataContext(), m_limits.getRangeStr() ); + string const msg = GEOS_FMT( "Value {} is outside the allowed range {}.", + value, m_limits.getRangeStr() ); switch( m_limitsMode ) { case WrapperLimitsMode::Warning: - GEOS_WARNING( msg ); + GEOS_WARNING( msg, getDataContext() ); break; case WrapperLimitsMode::Error: - GEOS_THROW( msg, InputError ); + GEOS_THROW( msg, InputError, getDataContext() ); break; default: From 7affa36b61361ad1a3c324c70ccb3d7e5b192168 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 24 Jun 2026 14:58:51 +0200 Subject: [PATCH 30/32] remove implicit conversions for WrapperBound --- src/coreComponents/dataRepository/Wrapper.hpp | 19 ++++--- .../dataRepository/WrapperLimits.hpp | 51 ++++++++++++++++--- 2 files changed, 54 insertions(+), 16 deletions(-) diff --git a/src/coreComponents/dataRepository/Wrapper.hpp b/src/coreComponents/dataRepository/Wrapper.hpp index 6ba4cba6486..3783f7e8448 100644 --- a/src/coreComponents/dataRepository/Wrapper.hpp +++ b/src/coreComponents/dataRepository/Wrapper.hpp @@ -738,25 +738,28 @@ class Wrapper final : public WrapperBase */ template< typename U=T > std::enable_if_t< is_limitable_v< U >, Wrapper< T > & > - setLimits( std::optional< WrapperBound< limit_value_type_t< T > > > min, - std::optional< WrapperBound< limit_value_type_t< T > > > max, + setLimits( std::optional< LimitArg< limit_value_type_t< T > > > min, + std::optional< LimitArg< limit_value_type_t< T > > > max, WrapperLimitsMode mode = WrapperLimitsMode::Error ) { - if( min.has_value() && max.has_value() ) + using LimitT = limit_value_type_t< T >; + std::optional< WrapperBound< LimitT > > const minBound = toWrapperBound< LimitT >( min ); + std::optional< WrapperBound< LimitT > > const maxBound = toWrapperBound< LimitT >( max ); + if( minBound.has_value() && maxBound.has_value() ) { - GEOS_ASSERT_LE_MSG( min.value(), max.value(), + GEOS_ASSERT_LE_MSG( minBound.value(), maxBound.value(), "Min value should be less or equal to max value." ); } - m_limits.min = min; - m_limits.max = max; + m_limits.min = minBound; + m_limits.max = maxBound; m_limitsMode = mode; return *this; } template< typename U=T > std::enable_if_t< !is_limitable_v< U > && !traits::is_array_type< U >, Wrapper< T > & > - setLimits( std::optional< WrapperBound< limit_value_type_t< T > > >, - std::optional< WrapperBound< limit_value_type_t< T > > >, + setLimits( std::optional< LimitArg< limit_value_type_t< T > > >, + std::optional< LimitArg< limit_value_type_t< T > > >, WrapperLimitsMode GEOS_UNUSED_PARAM( mode ) ) { static_assert( is_limitable_v< U >, diff --git a/src/coreComponents/dataRepository/WrapperLimits.hpp b/src/coreComponents/dataRepository/WrapperLimits.hpp index 8f54794b0b5..6c0ff9e57be 100644 --- a/src/coreComponents/dataRepository/WrapperLimits.hpp +++ b/src/coreComponents/dataRepository/WrapperLimits.hpp @@ -20,6 +20,7 @@ #include "codingUtilities/traits.hpp" #include "common/DataTypes.hpp" #include "common/format/EnumStrings.hpp" +#include namespace geos { @@ -110,17 +111,11 @@ struct WrapperBound bool isInclusive = true; /** - * @brief WrapperBound constructor to write a limit without the "WrapperBound{ ... }" syntax + * @brief Construct a limit bound * @param value The limit value to set * @param isInclusive Whether the limit should be inclusive or not - * - * @code - * .setLimits( 0.0, 1.0 ) // where setLimits takes `WrapperBound` parameters, those parameters - * // can be written only with the value. The isInclusive property will - * // default to true. - * @endcode */ - WrapperBound( T v, bool inclusive = true ) + explicit WrapperBound( T v, bool inclusive = true ) : value( v ), isInclusive( inclusive ) {} @@ -165,6 +160,46 @@ WrapperBound< T > exclusive( T value ) return WrapperBound< T >{ value, /*isInclusive*/ false }; } +/** + * @brief Type containing either a raw type or a WrapperBound + * + * Used as a type argument for methods that expect a wrapper boundary, + * allowing them to accept either a "raw" value or a WrapperBound. + * This simplifies the API of those method + * + * @code + * // foo signature: foo( LimitArg< T > const & ) + * foo( 1 ); + * foo( exclusive( 1 ) ); // <- exclusive() builds a WrapperBound + * @endcode + */ +template< typename T > +using LimitArg = std::variant< T, WrapperBound< T > >; + +/** + * @brief Transfrom a "raw" bound value (like an `int`) into a WrapperBound. + * @param bound the value to transform + * @return A WrapperBound containing the @p bound value, or @p bound it is already a WrapperBound + */ +template< typename T > +std::optional< WrapperBound< T > > toWrapperBound( std::optional< LimitArg< T > > const & bound ) +{ + if( !bound.has_value() ) + { + return std::nullopt; + } + return std::visit( []( auto const & value ) -> WrapperBound< T > { + if constexpr ( std::is_same_v< std::decay_t< decltype( value ) >, T > ) + { + return inclusive( value ); + } + else + { + return value; + } + }, bound.value() ); +} + /** * @struct WrapperLimits * @brief Storage for the optional min/max bounds of a wrapped value. From 718d2218cb497753547b5a6caffb316ce09e28d8 Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 24 Jun 2026 15:18:23 +0200 Subject: [PATCH 31/32] fix: rename conduit node "root" to "problem Causes problems with getDataContext(). See src/coreComponents/dataRepository/Group.cpp:139 "In the Conduit node hierarchy everything begins with 'Problem', we should change it so that the ProblemManager actually uses the root Conduit Node but that will require a full rebaseline." --- src/coreComponents/dataRepository/unitTests/testWrapper.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/coreComponents/dataRepository/unitTests/testWrapper.cpp b/src/coreComponents/dataRepository/unitTests/testWrapper.cpp index 77f27374c4e..eb5ecd40a6a 100644 --- a/src/coreComponents/dataRepository/unitTests/testWrapper.cpp +++ b/src/coreComponents/dataRepository/unitTests/testWrapper.cpp @@ -156,7 +156,7 @@ class WrapperLimitsTest : public ::testing::Test protected: WrapperLimitsTest(): m_node(), - m_group( "root", m_node ) + m_group( "Problem", m_node ) {} template< typename T > From 98cdb7f34ba7b3818212fe9dda371f3ea974ba5a Mon Sep 17 00:00:00 2001 From: kdrienCG Date: Wed, 24 Jun 2026 15:24:38 +0200 Subject: [PATCH 32/32] add schema.xsd.other --- src/coreComponents/schema/schema.xsd.other | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/coreComponents/schema/schema.xsd.other b/src/coreComponents/schema/schema.xsd.other index 12b9eae3e1d..7dc310b9a55 100644 --- a/src/coreComponents/schema/schema.xsd.other +++ b/src/coreComponents/schema/schema.xsd.other @@ -526,7 +526,7 @@ A field can represent a physical variable. (pressure, temperature, global compos - + @@ -1609,7 +1609,7 @@ A field can represent a physical variable. (pressure, temperature, global compos - + @@ -2443,11 +2443,11 @@ A field can represent a physical variable. (pressure, temperature, global compos - + - +