feat(quadrature): quadrature system brought over
Ported and dramatically cleaned up the quadrature system. This includes centralizing all field definitions
This commit is contained in:
@@ -4,29 +4,9 @@
|
||||
#include <string>
|
||||
|
||||
namespace serif::eos {
|
||||
enum class EOSEvaluationErrorCode : uint8_t {
|
||||
unsupported_relation,
|
||||
unsupported_derivative,
|
||||
wrong_input_count,
|
||||
wrong_input_quantity,
|
||||
nonfinite_input,
|
||||
outside_domain,
|
||||
nonfinite_result,
|
||||
invalid_thermodynamic_input
|
||||
};
|
||||
|
||||
class EOSEvaluationError final : public std::domain_error {
|
||||
public:
|
||||
explicit EOSEvaluationError(
|
||||
const EOSEvaluationErrorCode code,
|
||||
std::string message ):
|
||||
std::domain_error(std::move(message)),
|
||||
m_code(code) {}
|
||||
|
||||
[[nodiscard]] EOSEvaluationErrorCode code() const noexcept {
|
||||
return m_code;
|
||||
}
|
||||
private:
|
||||
EOSEvaluationErrorCode m_code;
|
||||
explicit EOSEvaluationError(std::string message):
|
||||
std::domain_error(std::move(message)){}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,7 @@
|
||||
#pragma once
|
||||
#include <format>
|
||||
#include "serif/eos/relations/relations.hpp"
|
||||
#include "serif/dimensions/type_alias.hpp"
|
||||
#include "serif/dimensions/partial.hpp"
|
||||
#include "serif/utils/misc/finite.hpp"
|
||||
|
||||
namespace serif::eos::models {
|
||||
struct PolytropeParameters final {
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "serif/dimensions/runtime/concepts.hpp"
|
||||
#include "serif/eos/exceptions.hpp"
|
||||
#include "serif/eos/relations/catalog.hpp"
|
||||
#include "serif/utils/error/codes.hpp"
|
||||
|
||||
namespace serif::eos::runtime {
|
||||
template <typename RelationT>
|
||||
@@ -134,32 +135,31 @@ namespace serif::eos::runtime {
|
||||
};
|
||||
};
|
||||
|
||||
[[nodiscard]] inline std::expected<double, EOSEvaluationError> runtimeEvaluationFailure(
|
||||
const EOSEvaluationErrorCode code,
|
||||
std::string message
|
||||
[[nodiscard]] inline std::expected<double, utils::errors::SERiFErrorCode> runtimeEvaluationFailure(
|
||||
const utils::errors::SERiFErrorCode code
|
||||
) {
|
||||
return std::unexpected<EOSEvaluationError>{EOSEvaluationError{code, std::move(message)}};
|
||||
return std::unexpected{code};
|
||||
}
|
||||
|
||||
template <typename EOS, typename OutputT, typename... InputTs>
|
||||
[[nodiscard]] std::expected<double, EOSEvaluationError> evaluateRuntimeRelation(
|
||||
[[nodiscard]] std::expected<double, utils::errors::SERiFErrorCode> evaluateRuntimeRelation(
|
||||
const EOS& eos,
|
||||
relations::Relation<OutputT, InputTs...> /*relation*/,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputValues
|
||||
) {
|
||||
/* Once again we find ourself at a use of folding. I reccomend you read the comment earlier in this file regarding folding, or find the C++ docs on folding.
|
||||
/* Once again we find ourself at a use of folding. I recommend you read the comment earlier in this file regarding folding, or find the C++ docs on folding.
|
||||
*
|
||||
* The general premis here is that we call the evaluation function for the EOS with the given relation and input values. We fold over all indicies using the final ... operator.
|
||||
* The general premise here is that we call the evaluation function for the EOS with the given relation and input values. We fold over all indices using the final ... operator.
|
||||
* This is an implicit loop generated by the compiler over the so called "parameter pack".
|
||||
*/
|
||||
const auto invoke_evaluate = [&]<std::size_t... Indices>(std::index_sequence<Indices...>) {
|
||||
return eos::evaluate<OutputT>(eos, dimensions::QuantityValue<InputTs>{inputValues[Indices].value}...);
|
||||
return eos::evaluate<OutputT>(eos, dimensions::QuantityValue<InputTs>{inputValues[Indices].value}...).value();
|
||||
};
|
||||
|
||||
try {
|
||||
return invoke_evaluate(std::make_index_sequence<sizeof...(InputTs)>{});
|
||||
} catch (const EOSEvaluationError& e) { // We may want to reconsider using a try-catch for this, but this is good enough for now. Realistically if we want to not use a try catch we will need to change the signature of the EOS evaluation function to return a std::expected instead of throwing an exception. This is a larger change that I don't want to make right now.
|
||||
return std::unexpected<EOSEvaluationError>{e};
|
||||
return std::unexpected<utils::errors::SERiFErrorCode>{utils::errors::SERiFErrorCode::EOS_ERROR};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -169,7 +169,7 @@ namespace serif::eos::runtime {
|
||||
relations::Relation<OutputT, InputTs...> /*relation*/,
|
||||
const dimensions::runtime::ThermodynamicQuantityID withRespectTo,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputValues,
|
||||
std::expected<double, EOSEvaluationError>& result
|
||||
std::expected<double, utils::errors::SERiFErrorCode>& result
|
||||
) {
|
||||
if (withRespectTo != dimensions::runtime::thermodynamicQuantityID<InputQuantityT>) {
|
||||
return false;
|
||||
@@ -182,26 +182,25 @@ namespace serif::eos::runtime {
|
||||
|
||||
try {
|
||||
result = invoke_evaluate_partial_derivative(std::index_sequence_for<InputTs...>{});
|
||||
} catch (const EOSEvaluationError& e) {
|
||||
result = std::unexpected<EOSEvaluationError>{e};
|
||||
} catch (const EOSEvaluationError& /*e*/) {
|
||||
result = std::unexpected{utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE};
|
||||
}
|
||||
} else {
|
||||
result = runtimeEvaluationFailure(EOSEvaluationErrorCode::unsupported_derivative, "The requested EOS partial derivative is not available in the selected EOS.");
|
||||
result = runtimeEvaluationFailure(utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename EOS, typename OutputT, typename... InputTs>
|
||||
[[nodiscard]] std::expected<double, EOSEvaluationError> evaluateRuntimePartialDerivative (
|
||||
[[nodiscard]] std::expected<double, utils::errors::SERiFErrorCode> evaluateRuntimePartialDerivative (
|
||||
const EOS& eos,
|
||||
relations::Relation<OutputT, InputTs...> relation,
|
||||
const dimensions::runtime::ThermodynamicQuantityID withRespectTo,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputValues
|
||||
) {
|
||||
std::expected<double, EOSEvaluationError> result = runtimeEvaluationFailure(
|
||||
EOSEvaluationErrorCode::unsupported_derivative,
|
||||
"The requested quantity is not an input to the EOS relation."
|
||||
std::expected<double, utils::errors::SERiFErrorCode> result = runtimeEvaluationFailure(
|
||||
utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE
|
||||
);
|
||||
|
||||
[[maybe_unused]] const bool matched = (tryRuntimePartialDerivative<InputTs>(eos, relation, withRespectTo, inputValues, result) || ...); // Here we fold over all input and try to find one that we can evaluate.
|
||||
@@ -233,7 +232,7 @@ namespace serif::eos::runtime {
|
||||
|
||||
template <typename EOS, typename...RelationTs>
|
||||
struct RuntimeCatalogDispatch<EOS, relations::RelationCatalog<RelationTs...>> {
|
||||
[[nodiscard]] static std::expected<double, EOSEvaluationError> evaluate(
|
||||
[[nodiscard]] static std::expected<double, utils::errors::SERiFErrorCode> evaluate(
|
||||
const void *object,
|
||||
const dimensions::runtime::ThermodynamicQuantityID& outputQuantity,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputValues
|
||||
@@ -241,8 +240,8 @@ namespace serif::eos::runtime {
|
||||
const auto &eos = *static_cast<const EOS*>(object); // Not sure if polymorphic type erasure is the right tool here. It works but we may want to revisit it if it precent compiler optimizations. This does however let us pass any eos model to the runtime view without needing to know the type at compile time (which is therefore helpful when building extension systems in other languages like python). Further, it may make sense to take the performance hit here and use something like a dynamic_cast to ensure that the object is actually of the correct type. This would be a runtime check but it would be a more robust check than just blindly casting to the expected type.
|
||||
|
||||
|
||||
std::expected<double, EOSEvaluationError> result = runtimeEvaluationFailure(
|
||||
EOSEvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available in the current equation of state."
|
||||
std::expected<double, utils::errors::SERiFErrorCode> result = runtimeEvaluationFailure(
|
||||
utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION
|
||||
);
|
||||
|
||||
/* Fold over all relations in the catalog and check if any of them match the requested output quantity and input quantities. If the match is found then
|
||||
@@ -253,7 +252,7 @@ namespace serif::eos::runtime {
|
||||
return result;
|
||||
}
|
||||
|
||||
[[nodiscard]] static std::expected<double, EOSEvaluationError> partial_derivative(
|
||||
[[nodiscard]] static std::expected<double, utils::errors::SERiFErrorCode> partial_derivative(
|
||||
const void *object,
|
||||
const dimensions::runtime::ThermodynamicQuantityID& outputQuantity,
|
||||
const dimensions::runtime::ThermodynamicQuantityID& withRespectTo,
|
||||
@@ -261,8 +260,8 @@ namespace serif::eos::runtime {
|
||||
) {
|
||||
const auto &eos = *static_cast<const EOS*>(object); // Same Comment as above
|
||||
|
||||
std::expected<double, EOSEvaluationError> result = runtimeEvaluationFailure(
|
||||
EOSEvaluationErrorCode::unsupported_relation, "The requested EOS relation is not available in the current equation of state."
|
||||
std::expected<double, utils::errors::SERiFErrorCode> result = runtimeEvaluationFailure(
|
||||
utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION
|
||||
);
|
||||
|
||||
[[maybe_unused]] const bool matched = ((runtimeRelationMatches<EOS, RelationTs>(outputQuantity, inputValues) ? (result = evaluateRuntimePartialDerivative(eos, RelationTs{}, withRespectTo, inputValues), true) : false) || ...);
|
||||
|
||||
@@ -7,11 +7,13 @@
|
||||
namespace serif::eos::runtime {
|
||||
class EOSView final {
|
||||
public:
|
||||
template <RuntimeEOSModel EOS>
|
||||
explicit EOSView(EOS& eos) noexcept :
|
||||
template <typename EOS>
|
||||
requires RuntimeEOSModel<std::remove_cvref_t<EOS>>
|
||||
explicit EOSView(const EOS& eos) noexcept :
|
||||
m_object(std::addressof(eos)),
|
||||
m_relations(&RuntimeEOSAdapter<std::remove_cv_t<EOS>>::evaluate),
|
||||
m_partialDerivative(&RuntimeEOSAdapter<std::remove_cv_t<EOS>>::partial_derivative) {}
|
||||
m_relations(runtimeRelationDescriptors<std::remove_cvref_t<EOS>>()),
|
||||
m_evaluate(&RuntimeEOSAdapter<std::remove_cvref_t<EOS>>::evaluate),
|
||||
m_partialDerivative(&RuntimeEOSAdapter<std::remove_cvref_t<EOS>>::partial_derivative) {}
|
||||
|
||||
// TODO: This function can be moved to an implantation file, it may need to be to prevent ODR violations
|
||||
[[nodiscard]] std::span<const dimensions::runtime::RuntimeRelationDescriptor> relations() const noexcept {
|
||||
@@ -26,36 +28,113 @@ namespace serif::eos::runtime {
|
||||
return find_relation(outputQuantity, inputQuantities) != nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<dimensions::runtime::RuntimeQuantityValue, EOSEvaluationError> try_evaluate(
|
||||
[[nodiscard]] std::expected<dimensions::runtime::RuntimeQuantityValue, utils::errors::SERiFErrorCode> try_evaluate(
|
||||
const dimensions::runtime::ThermodynamicQuantityID outputQuantity,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputQuantities
|
||||
) {
|
||||
) const {
|
||||
const auto validation = validate_relation_request(outputQuantity, inputQuantities);
|
||||
|
||||
if (!validation.has_value()) {
|
||||
return std::unexpected<EOSEvaluationError>{validation.error()};
|
||||
return std::unexpected{validation.error()};
|
||||
}
|
||||
|
||||
auto result = m_evaluate(m_object, outputQuantity, inputQuantities);
|
||||
|
||||
if (!result.has_value()) {
|
||||
return std::unexpected<EOSEvaluationError>{result.error()};
|
||||
return std::unexpected{result.error()};
|
||||
}
|
||||
return dimensions::runtime::RuntimeQuantityValue{outputQuantity, *result};
|
||||
}
|
||||
|
||||
// TODO: Still need to translate from the old code the templated version of try_evaluate along with the try_partial_derivative function. The templated version is more user friendly and should be kept, but the non-templated version is needed for the runtime view.
|
||||
template <dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity OutputQuantity, dimensions::ThermodynamicQuantityValueType... InputValues>
|
||||
[[nodiscard]] std::expected<dimensions::QuantityValue<OutputQuantity>, utils::errors::SERiFErrorCode> try_evaluate(
|
||||
const InputValues... inputValues
|
||||
) const {
|
||||
constexpr bool inputsHaveRuntimeIdentifiers = (dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity<dimensions::QuantityOfT<InputValues>> && ...);
|
||||
|
||||
static_assert(inputsHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
|
||||
|
||||
const std::array<dimensions::runtime::RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs {
|
||||
dimensions::runtime::RuntimeQuantityValue{
|
||||
.id = dimensions::runtime::thermodynamicQuantityID<dimensions::QuantityOfT<InputValues>>, .value = inputValues.value()
|
||||
}...
|
||||
};
|
||||
|
||||
auto result = try_evaluate(
|
||||
dimensions::runtime::thermodynamicQuantityID<OutputQuantity>, std::span<const dimensions::runtime::RuntimeQuantityValue>{runtimeInputs}
|
||||
);
|
||||
|
||||
if (!result.has_value()) {
|
||||
return std::unexpected<utils::errors::SERiFErrorCode>{result.error()};
|
||||
}
|
||||
|
||||
return dimensions::QuantityValue<OutputQuantity>{result->value};
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<double, utils::errors::SERiFErrorCode> try_partial_derivative(
|
||||
const dimensions::runtime::ThermodynamicQuantityID outputQuantity,
|
||||
const dimensions::runtime::ThermodynamicQuantityID withRespectTo,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputValues
|
||||
) const {
|
||||
|
||||
const auto validation = validate_relation_request(outputQuantity, inputValues);
|
||||
|
||||
if (!validation.has_value()) {
|
||||
return std::unexpected<utils::errors::SERiFErrorCode>{validation.error()};
|
||||
}
|
||||
|
||||
const dimensions::runtime::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 runtime_failure<double>(utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_DERIVATIVE);
|
||||
}
|
||||
|
||||
return m_partialDerivative(m_object, outputQuantity, withRespectTo, inputValues);
|
||||
}
|
||||
|
||||
template <dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity OutputQuantity, dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity InputQuantity, dimensions::QuantityValueType... InputValues>
|
||||
[[nodiscard]] std::expected<dimensions::PartialDerivative<OutputQuantity, InputQuantity>, EOSEvaluationError> try_partial_derivative(
|
||||
const InputValues... inputValues
|
||||
) const {
|
||||
constexpr bool inputHaveRuntimeIdentifiers = (dimensions::runtime::RuntimeIdentifiedThermodynamicQuantity<dimensions::QuantityOfT<InputValues>> && ...);
|
||||
static_assert(inputHaveRuntimeIdentifiers, "Every runtime EOS input quantity needs a stable identifier.");
|
||||
|
||||
const std::array<dimensions::runtime::RuntimeQuantityValue, sizeof...(InputValues)> runtimeInputs{
|
||||
dimensions::runtime::RuntimeQuantityValue{.id = dimensions::runtime::thermodynamicQuantityID<dimensions::QuantityOfT<InputValues>>, .value = inputValues.value()}...
|
||||
};
|
||||
|
||||
auto result = try_partial_derivative(
|
||||
dimensions::runtime::thermodynamicQuantityID<OutputQuantity>, // output
|
||||
dimensions::runtime::thermodynamicQuantityID<InputQuantity>, // input
|
||||
std::span<const dimensions::runtime::RuntimeQuantityValue>{runtimeInputs} // where to evaluate
|
||||
);
|
||||
|
||||
if (!result.has_value()) {
|
||||
return std::unexpected<EOSEvaluationError>{result.error()};
|
||||
}
|
||||
|
||||
return dimensions::PartialDerivative<OutputQuantity, InputQuantity>{*result};
|
||||
}
|
||||
|
||||
private: // Type aliases
|
||||
using RuntimeEvaluateFunction = std::expected<double, EOSEvaluationError> (*)(
|
||||
using RuntimeEvaluateFunction = std::expected<double, utils::errors::SERiFErrorCode> (*)(
|
||||
const void*,
|
||||
dimensions::runtime::ThermodynamicQuantityID,
|
||||
const dimensions::runtime::ThermodynamicQuantityID&,
|
||||
std::span<const dimensions::runtime::RuntimeQuantityValue>
|
||||
);
|
||||
|
||||
using RuntimePartialDerivativeFunction = std::expected<double, EOSEvaluationError> (*)(
|
||||
using RuntimePartialDerivativeFunction = std::expected<double, utils::errors::SERiFErrorCode> (*)(
|
||||
const void*,
|
||||
dimensions::runtime::ThermodynamicQuantityID,
|
||||
dimensions::runtime::ThermodynamicQuantityID,
|
||||
const dimensions::runtime::ThermodynamicQuantityID&,
|
||||
const dimensions::runtime::ThermodynamicQuantityID&,
|
||||
std::span<const dimensions::runtime::RuntimeQuantityValue>
|
||||
);
|
||||
private: // Private methods
|
||||
@@ -84,7 +163,7 @@ namespace serif::eos::runtime {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] std::expected<const dimensions::runtime::RuntimeRelationDescriptor *, EOSEvaluationError> validate_relation_request(
|
||||
[[nodiscard]] std::expected<const dimensions::runtime::RuntimeRelationDescriptor *, utils::errors::SERiFErrorCode> validate_relation_request(
|
||||
const dimensions::runtime::ThermodynamicQuantityID outputQuantity,
|
||||
const std::span<const dimensions::runtime::RuntimeQuantityValue> inputValues
|
||||
) const {
|
||||
@@ -114,32 +193,26 @@ namespace serif::eos::runtime {
|
||||
|
||||
if (!outputAvailable) {
|
||||
return runtime_failure<const dimensions::runtime::RuntimeRelationDescriptor *>(
|
||||
EOSEvaluationErrorCode::unsupported_relation,
|
||||
"The requested EOS relation is not available."
|
||||
utils::errors::SERiFErrorCode::EOS_UNSUPPORTED_RELATION
|
||||
);
|
||||
}
|
||||
|
||||
if (!inputCountAvailable) {
|
||||
return runtime_failure<const dimensions::runtime::RuntimeRelationDescriptor *>(
|
||||
EOSEvaluationErrorCode::wrong_input_count,
|
||||
"No EOS relation for given output quantity '" + std::string{outputQuantity.name()} + "' accepts the provided number of inputs."
|
||||
utils::errors::SERiFErrorCode::EOS_WRONG_INPUT_COUNT
|
||||
);
|
||||
}
|
||||
|
||||
return runtime_failure<const dimensions::runtime::RuntimeRelationDescriptor *>(
|
||||
EOSEvaluationErrorCode::wrong_input_quantity,
|
||||
"No EOS relation for given output quantity '" + std::string{outputQuantity.name()} + "' accepts the provided input quantities."
|
||||
utils::errors::SERiFErrorCode::EOS_WRONG_INPUT_QUANTITY
|
||||
);
|
||||
}
|
||||
|
||||
template <typename Value>
|
||||
[[nodiscard]] static std::expected<Value, EOSEvaluationError> runtime_failure(
|
||||
const EOSEvaluationErrorCode code,
|
||||
const std::string message
|
||||
[[nodiscard]] static std::expected<Value, utils::errors::SERiFErrorCode> runtime_failure(
|
||||
const utils::errors::SERiFErrorCode code
|
||||
) {
|
||||
return std::unexpected<EOSEvaluationError>{
|
||||
EOSEvaluationError{code, std::move(message)}
|
||||
};
|
||||
return std::unexpected{code};
|
||||
}
|
||||
|
||||
private: // Private members
|
||||
|
||||
Reference in New Issue
Block a user