module; #include #include #include #include #include #include #include #include #include #include #include #include export module mean_field:eos.runtime; export import :eos.evaluation; export namespace mean_field::eos { class ThermodynamicQuantityId final { public: explicit constexpr ThermodynamicQuantityId(const std::string_view name) noexcept : m_name(name) { } [[nodiscard]] constexpr std::string_view name() const noexcept { return m_name; } [[nodiscard]] friend constexpr bool operator==( const ThermodynamicQuantityId &, const ThermodynamicQuantityId & ) noexcept = default; private: std::string_view m_name; }; template concept RuntimeIdentifiedThermodynamicQuantity = ThermodynamicQuantityType && requires { { Quantity::identifier } -> std::convertible_to; } && (std::string_view{Quantity::identifier}.size() > 0); template inline constexpr ThermodynamicQuantityId thermodynamicQuantityId{std::string_view{Quantity::identifier}}; struct RuntimeQuantityValue final { ThermodynamicQuantityId quantity; double value; }; struct RuntimeRelationDescriptor final { ThermodynamicQuantityId outputQuantity; std::span inputQuantities; std::uint64_t partialDerivativeMask; [[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept { return inputIndex < inputQuantities.size() && (partialDerivativeMask & (std::uint64_t{1} << inputIndex)) != 0; } }; namespace detail { template struct HasRuntimeQuantityIdentifiers : std::false_type { }; template struct HasRuntimeQuantityIdentifiers> : std::bool_constant< RuntimeIdentifiedThermodynamicQuantity && (RuntimeIdentifiedThermodynamicQuantity && ...)> { }; template struct RuntimeRelationQuantities; template struct RuntimeRelationQuantities> { using Type = std::tuple; }; template using RuntimeCatalogQuantityTuple = decltype(std::tuple_cat(std::declval::Type>()...)); template < typename FirstQuantity, typename SecondQuantity> [[nodiscard]] consteval bool runtimeQuantityIdentifiersAreCompatible() { if constexpr (std::same_as) { return true; } else { return thermodynamicQuantityId != thermodynamicQuantityId; } } template < typename QuantityTuple, std::size_t First, std::size_t... Offsets> [[nodiscard]] consteval bool runtimeQuantityIdentifierIsUnambiguous(std::index_sequence) { return ( runtimeQuantityIdentifiersAreCompatible< std::tuple_element_t, std::tuple_element_t>() && ... ); } template < typename QuantityTuple, std::size_t... Indices> [[nodiscard]] consteval bool runtimeQuantityIdentifiersAreUnambiguous(std::index_sequence) { return ( runtimeQuantityIdentifierIsUnambiguous( std::make_index_sequence - Indices - 1>{} ) && ... ); } template struct RuntimeRelationsAreSupported : std::false_type { }; template struct RuntimeRelationsAreSupported : std::bool_constant>( std::make_index_sequence>>{} )> { }; template struct RuntimeCatalogIsSupported : std::false_type { }; template struct RuntimeCatalogIsSupported> : RuntimeRelationsAreSupported<(HasRuntimeQuantityIdentifiers::value && ...), Relations...> { }; } // namespace detail template concept RuntimeEquationOfStateModel = EquationOfStateModel && detail::RuntimeCatalogIsSupported::Relations>::value; namespace detail { template struct RuntimeRelationStorage; template struct RuntimeRelationStorage> { using RelationType = Relation; static_assert( sizeof...(Inputs) <= 64, "Runtime EOS relation descriptors support at most 64 inputs." ); inline static constexpr std::array inputQuantityIds{ thermodynamicQuantityId... }; template [[nodiscard]] static consteval std::uint64_t makePartialDerivativeMask(std::index_sequence) { using InputTuple = std::tuple; return ( std::uint64_t{0} | ... | (SupportsPartialDerivative> ? (std::uint64_t{1} << Indices) : std::uint64_t{0}) ); } inline static constexpr std::uint64_t partialDerivativeMask = makePartialDerivativeMask(std::index_sequence_for{}); inline static constexpr RuntimeRelationDescriptor descriptor{ thermodynamicQuantityId, std::span{inputQuantityIds}, partialDerivativeMask }; }; template struct RuntimeCatalogStorage; template struct RuntimeCatalogStorage> { inline static constexpr std::array descriptors{ RuntimeRelationStorage::descriptor... }; }; [[nodiscard]] inline std::expected< double, EvaluationError> runtimeEvaluationFailure( const EvaluationErrorCode code, std::string message ) { return std::unexpected{EvaluationError{code, std::move(message)}}; } template < typename EquationOfState, typename Output, typename... Inputs> [[nodiscard]] std::expected< double, EvaluationError> evaluateRuntimeRelation( const EquationOfState &equationOfState, Relation< Output, Inputs...>, const std::span inputValues ) { const auto invoke = [&](std::index_sequence) { return eos::evaluate(equationOfState, QuantityValue{inputValues[Indices].value}...) .value(); }; try { return invoke(std::index_sequence_for{}); } catch (const EvaluationError &error) { return std::unexpected{error}; } } template < typename InputQuantity, typename EquationOfState, typename Output, typename... Inputs> [[nodiscard]] bool tryRuntimePartialDerivative( const EquationOfState &equationOfState, Relation< Output, Inputs...> relation, const ThermodynamicQuantityId withRespectTo, const std::span inputValues, std::expected< double, EvaluationError> &result ) { if (withRespectTo != thermodynamicQuantityId) { return false; } if constexpr (SupportsPartialDerivative, InputQuantity>) { const auto invoke = [&](std::index_sequence) { return eos::partialDerivative( equationOfState, QuantityValue{inputValues[Indices].value}... ) .value(); }; try { result = invoke(std::index_sequence_for{}); } catch (const EvaluationError &error) { result = std::unexpected{error}; } } else { result = runtimeEvaluationFailure( EvaluationErrorCode::unsupported_derivative, "The requested EOS partial derivative is not available." ); } return true; } template < typename EquationOfState, typename Output, typename... Inputs> [[nodiscard]] std::expected< double, EvaluationError> evaluateRuntimePartialDerivative( const EquationOfState &equationOfState, Relation< Output, Inputs...> relation, const ThermodynamicQuantityId withRespectTo, const std::span inputValues ) { std::expected result = runtimeEvaluationFailure( EvaluationErrorCode::unsupported_derivative, "The requested quantity is not an input to the EOS relation." ); const bool matched = (tryRuntimePartialDerivative(equationOfState, relation, withRespectTo, inputValues, result) || ...); static_cast(matched); return result; } template < typename EquationOfState, typename RelationType> [[nodiscard]] bool runtimeRelationMatches( const ThermodynamicQuantityId outputQuantity, const std::span inputValues ) { const RuntimeRelationDescriptor &descriptor = RuntimeRelationStorage::descriptor; if (descriptor.outputQuantity != outputQuantity || descriptor.inputQuantities.size() != inputValues.size()) { return false; } for (std::size_t index = 0; index < inputValues.size(); ++index) { if (descriptor.inputQuantities[index] != inputValues[index].quantity) { return false; } } return true; } template struct RuntimeCatalogDispatch; template struct RuntimeCatalogDispatch> { [[nodiscard]] static std::expected< double, EvaluationError> evaluate( const void *object, const ThermodynamicQuantityId outputQuantity, const std::span inputValues ) { const auto &equationOfState = *static_cast(object); std::expected result = runtimeEvaluationFailure( EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available." ); const bool matched = ((runtimeRelationMatches(outputQuantity, inputValues) ? (result = evaluateRuntimeRelation(equationOfState, Relations{}, inputValues), true) : false) || ...); static_cast(matched); return result; } [[nodiscard]] static std::expected< double, EvaluationError> partialDerivative( const void *object, const ThermodynamicQuantityId outputQuantity, const ThermodynamicQuantityId withRespectTo, const std::span inputValues ) { const auto &equationOfState = *static_cast(object); std::expected result = runtimeEvaluationFailure( EvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available." ); const bool matched = ((runtimeRelationMatches(outputQuantity, inputValues) ? (result = evaluateRuntimePartialDerivative( equationOfState, Relations{}, withRespectTo, inputValues ), true) : false) || ...); static_cast(matched); return result; } }; template using RuntimeAdapter = RuntimeCatalogDispatch; template [[nodiscard]] constexpr std::span runtimeRelationDescriptors() noexcept { return RuntimeCatalogStorage::descriptors; } } // namespace detail class EquationOfStateView final { public: template explicit EquationOfStateView(EquationOfState &equationOfState) noexcept : m_object(std::addressof(equationOfState)), m_relations(detail::runtimeRelationDescriptors>()), m_evaluate(&detail::RuntimeAdapter>::evaluate), m_partialDerivative(&detail::RuntimeAdapter>::partialDerivative) { } [[nodiscard]] std::span relations() const noexcept { return m_relations; } [[nodiscard]] bool supports( const ThermodynamicQuantityId outputQuantity, const std::span inputQuantities ) const noexcept { return findRelation(outputQuantity, inputQuantities) != nullptr; } template < RuntimeIdentifiedThermodynamicQuantity OutputQuantity, RuntimeIdentifiedThermodynamicQuantity... InputQuantities> [[nodiscard]] bool supports() const noexcept { constexpr std::array inputs{ thermodynamicQuantityId... }; return supports(thermodynamicQuantityId, std::span{inputs}); } [[nodiscard]] std::expected< RuntimeQuantityValue, EvaluationError> tryEvaluate( const ThermodynamicQuantityId outputQuantity, const std::span inputValues ) const { const auto validation = validateRelationRequest(outputQuantity, inputValues); if (!validation.has_value()) { return std::unexpected{validation.error()}; } auto result = m_evaluate(m_object, outputQuantity, inputValues); if (!result.has_value()) { return std::unexpected{result.error()}; } return RuntimeQuantityValue{outputQuantity, *result}; } template < RuntimeIdentifiedThermodynamicQuantity OutputQuantity, QuantityValueType... InputValues> [[nodiscard]] std::expected< QuantityValue, EvaluationError> tryEvaluate(const InputValues... inputValues) const { constexpr bool inputsHaveRuntimeIdentifiers = (RuntimeIdentifiedThermodynamicQuantity> && ...); static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier."); const std::array runtimeInputs{ RuntimeQuantityValue{thermodynamicQuantityId>, inputValues.value()}... }; auto result = tryEvaluate( thermodynamicQuantityId, std::span{runtimeInputs} ); if (!result.has_value()) { return std::unexpected{result.error()}; } return QuantityValue{result->value}; } [[nodiscard]] std::expected< double, EvaluationError> tryPartialDerivative( const ThermodynamicQuantityId outputQuantity, const ThermodynamicQuantityId withRespectTo, const std::span inputValues ) const { const auto validation = validateRelationRequest(outputQuantity, inputValues); if (!validation.has_value()) { return std::unexpected{validation.error()}; } const RuntimeRelationDescriptor &descriptor = **validation; bool derivativeAvailable = false; for (std::size_t index = 0; index < descriptor.inputQuantities.size(); ++index) { if (descriptor.inputQuantities[index] == withRespectTo) { derivativeAvailable = descriptor.hasPartialDerivative(index); break; } } if (!derivativeAvailable) { return runtimeFailure( EvaluationErrorCode::unsupported_derivative, "The requested EOS partial derivative is not available." ); } return m_partialDerivative(m_object, outputQuantity, withRespectTo, inputValues); } template < RuntimeIdentifiedThermodynamicQuantity OutputQuantity, RuntimeIdentifiedThermodynamicQuantity InputQuantity, QuantityValueType... InputValues> [[nodiscard]] std::expected< PartialDerivative< OutputQuantity, InputQuantity>, EvaluationError> tryPartialDerivative(const InputValues... inputValues) const { constexpr bool inputsHaveRuntimeIdentifiers = (RuntimeIdentifiedThermodynamicQuantity> && ...); static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier."); const std::array runtimeInputs{ RuntimeQuantityValue{thermodynamicQuantityId>, inputValues.value()}... }; auto result = tryPartialDerivative( thermodynamicQuantityId, thermodynamicQuantityId, std::span{runtimeInputs} ); if (!result.has_value()) { return std::unexpected{result.error()}; } return PartialDerivative{*result}; } private: using RuntimeEvaluateFunction = std::expected< double, EvaluationError> (*)( const void *, ThermodynamicQuantityId, std::span ); using RuntimePartialDerivativeFunction = std::expected< double, EvaluationError> (*)( const void *, ThermodynamicQuantityId, ThermodynamicQuantityId, std::span ); [[nodiscard]] const RuntimeRelationDescriptor *findRelation( const ThermodynamicQuantityId outputQuantity, const std::span inputQuantities ) const noexcept { for (const RuntimeRelationDescriptor &descriptor : m_relations) { if (descriptor.outputQuantity != outputQuantity || descriptor.inputQuantities.size() != inputQuantities.size()) { continue; } bool matches = true; for (std::size_t index = 0; index < inputQuantities.size(); ++index) { if (descriptor.inputQuantities[index] != inputQuantities[index]) { matches = false; break; } } if (matches) { return std::addressof(descriptor); } } return nullptr; } [[nodiscard]] std::expected< const RuntimeRelationDescriptor *, EvaluationError> validateRelationRequest( const ThermodynamicQuantityId outputQuantity, const std::span inputValues ) const { bool outputAvailable = false; bool inputCountAvailable = false; for (const RuntimeRelationDescriptor &descriptor : m_relations) { if (descriptor.outputQuantity != outputQuantity) { continue; } outputAvailable = true; if (descriptor.inputQuantities.size() != inputValues.size()) { continue; } inputCountAvailable = true; bool matches = true; for (std::size_t index = 0; index < inputValues.size(); ++index) { if (descriptor.inputQuantities[index] != inputValues[index].quantity) { matches = false; break; } } if (matches) { return std::addressof(descriptor); } } if (!outputAvailable) { return runtimeFailure( EvaluationErrorCode::unsupported_relation, "The EOS does not provide a relation for output quantity '" + std::string{outputQuantity.name()} + "'." ); } if (!inputCountAvailable) { return runtimeFailure( EvaluationErrorCode::wrong_input_count, "No EOS relation for output quantity '" + std::string{outputQuantity.name()} + "' accepts the supplied number of inputs." ); } return runtimeFailure( EvaluationErrorCode::wrong_input_quantity, "No EOS relation for output quantity '" + std::string{outputQuantity.name()} + "' accepts the supplied input quantities." ); } template [[nodiscard]] static std::expected< Value, EvaluationError> runtimeFailure( const EvaluationErrorCode code, std::string message ) { return std::unexpected{EvaluationError{code, std::move(message)}}; } const void *m_object; std::span m_relations; RuntimeEvaluateFunction m_evaluate; RuntimePartialDerivativeFunction m_partialDerivative; }; } // namespace mean_field::eos