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:
@@ -3,6 +3,7 @@
|
||||
#include <type_traits>
|
||||
#include <compare>
|
||||
#include <concepts>
|
||||
#include <tuple>
|
||||
|
||||
#include "serif/utils/misc/std_helper/cleaning.hpp"
|
||||
#include "serif/utils/misc/concepts/numeric.hpp"
|
||||
@@ -114,6 +115,7 @@ namespace serif::dimensions {
|
||||
template <typename Candidate>
|
||||
concept QuantityValueType = IsQuantityValue<std::remove_cvref_t<Candidate>>::value;
|
||||
|
||||
|
||||
template <typename Candidate>
|
||||
struct QuantityOf;
|
||||
|
||||
@@ -135,4 +137,7 @@ namespace serif::dimensions {
|
||||
struct QuantityAt<Index, QuantityList<Quantities...>> {
|
||||
using Type = std::tuple_element_t<Index, std::tuple<Quantities...>>;
|
||||
};
|
||||
|
||||
template <typename Candidate>
|
||||
concept ThermodynamicQuantityValueType = QuantityValueType<Candidate> && ThermodynamicQuantityType<QuantityOfT<Candidate>>;
|
||||
}
|
||||
|
||||
@@ -7,9 +7,12 @@
|
||||
namespace serif::dimensions::runtime {
|
||||
class ThermodynamicQuantityID final {
|
||||
public:
|
||||
explicit constexpr ThermodynamicQuantityID(std::string_view name) noexcept;
|
||||
explicit constexpr ThermodynamicQuantityID(std::string_view name) noexcept : m_name(name) {};
|
||||
|
||||
[[nodiscard]] constexpr std::string_view name() const noexcept {
|
||||
return m_name;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr std::string_view name() const noexcept;
|
||||
[[nodiscard]] friend constexpr bool operator==(const ThermodynamicQuantityID &, const ThermodynamicQuantityID &) noexcept = default;
|
||||
private:
|
||||
std::string_view m_name; // We can use a string view here since the constructor is constexpr and the string view will be valid for the lifetime of the program.
|
||||
@@ -26,6 +29,8 @@ namespace serif::dimensions::runtime {
|
||||
std::span<const ThermodynamicQuantityID> inputQuantities;
|
||||
std::uint64_t partialDerivativeMask;
|
||||
|
||||
[[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept;
|
||||
[[nodiscard]] constexpr bool hasPartialDerivative(const std::size_t inputIndex) const noexcept {
|
||||
return inputIndex < inputQuantities.size() && (partialDerivativeMask & (std::uint64_t{1} << inputIndex)) != 0;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -1 +1,65 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include "serif/discretization/blocks/fields.hpp"
|
||||
|
||||
namespace serif::discretization::blocks {
|
||||
template <typename FieldCandidate>
|
||||
concept Field = std::derived_from<FieldCandidate, field>;
|
||||
|
||||
template <typename T>
|
||||
concept Term = std::derived_from<std::remove_cvref_t<T>, term_base> && // a term must derive from term_base
|
||||
requires {typename std::remove_cvref_t<T>::field_type;} && // a term must have a field_type member
|
||||
Field<typename std::remove_cvref_t<T>::field_type>; // That field type member must be a valid field
|
||||
|
||||
template <typename T>
|
||||
concept HasFunctionalSpace =
|
||||
Term<T> &&
|
||||
requires {
|
||||
typename std::remove_cvref_t<T>::FunctionalSpace;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasSupport =
|
||||
Term<T> &&
|
||||
requires {
|
||||
typename std::remove_cvref_t<T>::Support;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasQuantity =
|
||||
Term<T> &&
|
||||
requires {
|
||||
typename std::remove_cvref_t<T>::Quantity;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasValueBlock =
|
||||
Term<T> &&
|
||||
requires {
|
||||
typename std::remove_cvref_t<T>::value;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept HasResidualBlock =
|
||||
Term<T> &&
|
||||
requires {
|
||||
typename std::remove_cvref_t<T>::residual;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
concept SpatialTerm =
|
||||
Term<T> &&
|
||||
HasFunctionalSpace<T> &&
|
||||
HasSupport<T>;
|
||||
|
||||
template <typename T>
|
||||
concept PhysicalSpatialTerm =
|
||||
SpatialTerm<T> &&
|
||||
HasQuantity<T>;
|
||||
|
||||
template <typename T>
|
||||
struct IsSpatialTerm : std::bool_constant<SpatialTerm<T>> {};
|
||||
|
||||
template <typename T>
|
||||
struct IsPhysicalSpatialTerm : std::bool_constant<PhysicalSpatialTerm<T>> {};
|
||||
}
|
||||
@@ -1,65 +1,130 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
|
||||
#include "base.hpp"
|
||||
#include "serif/dimensions/dimensions.hpp"
|
||||
#include "serif/discretization/blocks/base.hpp"
|
||||
#include "serif/discretization/functional_space/space.hpp"
|
||||
#include "serif/discretization/domain/concepts.hpp"
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/utils/types/type_list.hpp"
|
||||
|
||||
#include "serif/meta/names.hpp"
|
||||
|
||||
namespace serif::discretization::blocks {
|
||||
struct field {};
|
||||
struct term {};
|
||||
struct term_base {};
|
||||
|
||||
template <typename FieldT>
|
||||
struct term : term_base {
|
||||
using field_type = FieldT;
|
||||
};
|
||||
|
||||
struct density final : field {
|
||||
struct mass final : term {
|
||||
static constexpr std::string_view name = meta::type_name<density>();
|
||||
|
||||
struct mass final : term<density> { // Note the reading here is mass density (that is this is not in reference to the common FEM usage of mass as a term in a weak form)
|
||||
static constexpr std::string_view name = meta::type_name<mass>();
|
||||
|
||||
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::L2, 2, 0>; // second order scalar L2 space
|
||||
using Support = domain::StellarDomains;
|
||||
using Quantity = dimensions::Density;
|
||||
static constexpr std::string_view symbol = "ρ";
|
||||
|
||||
struct value final : value_block_base {};
|
||||
struct residual final : residual_block_base {};
|
||||
};
|
||||
static inline constexpr mass mass_density_term{};
|
||||
|
||||
static inline constexpr mass mass_term{};
|
||||
using Terms = utils::types::TypeList<mass>;
|
||||
};
|
||||
|
||||
struct displacement final : field {
|
||||
struct geometry final : term {
|
||||
static constexpr std::string_view name = meta::type_name<displacement>();
|
||||
|
||||
struct vector final : term<displacement> { // Similarly this is the displacement vector
|
||||
static constexpr std::string_view name = meta::type_name<vector>();
|
||||
|
||||
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::H1, 3, 1>; // third order vector H1 space
|
||||
using Support = domain::AllDomains;
|
||||
using Quantity = dimensions::Length;
|
||||
static constexpr std::string_view symbol = "d";
|
||||
|
||||
struct value final : value_block_base {};
|
||||
struct residual final : residual_block_base {};
|
||||
};
|
||||
static inline constexpr vector vector_term{};
|
||||
|
||||
static inline constexpr geometry geometry_term{};
|
||||
using Terms = utils::types::TypeList<vector>;
|
||||
};
|
||||
|
||||
struct surface_deformation final : field {
|
||||
struct parameters final : term {
|
||||
struct surface_deformation final : field { // Surface deformation is a more general field that the others, it is not pointwise defined over the domain. Use concepts to constrain this as a non spatial field.
|
||||
static constexpr std::string_view name = meta::type_name<surface_deformation>();
|
||||
|
||||
struct coefficients final : term<surface_deformation> {
|
||||
static constexpr std::string_view name = meta::type_name<coefficients>();
|
||||
|
||||
struct value final : value_block_base {};
|
||||
};
|
||||
|
||||
struct shape_equilibrium final : term {
|
||||
struct shape_equilibrium final : term<surface_deformation> {
|
||||
static constexpr std::string_view name = meta::type_name<shape_equilibrium>();
|
||||
|
||||
struct residual final : residual_block_base {};
|
||||
};
|
||||
|
||||
static inline constexpr parameters parameters_term{};
|
||||
static inline constexpr coefficients coefficients_term{};
|
||||
static inline constexpr shape_equilibrium shape_equilibrium_term{};
|
||||
|
||||
using Terms = utils::types::TypeList<coefficients, shape_equilibrium>;
|
||||
};
|
||||
|
||||
struct gravity final : field {
|
||||
struct gradient final : term {
|
||||
static constexpr std::string_view name = meta::type_name<gravity>();
|
||||
|
||||
struct gradient final : term<gravity> {
|
||||
static constexpr std::string_view name = meta::type_name<gradient>();
|
||||
|
||||
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::Hdiv, 2, 1>;
|
||||
using Support = domain::AllDomains;
|
||||
using Quantity = dimensions::Acceleration;
|
||||
static constexpr std::string_view symbol = "∇φ";
|
||||
|
||||
struct value final : value_block_base {};
|
||||
struct residual final : residual_block_base {};
|
||||
};
|
||||
|
||||
struct potential final : term {
|
||||
struct potential final : term<gravity> {
|
||||
static constexpr std::string_view name = meta::type_name<potential>();
|
||||
|
||||
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::L2, 2, 0>;
|
||||
using Support = domain::AllDomains;
|
||||
using Quantity = dimensions::GravitationalPotential;
|
||||
static constexpr std::string_view symbol = "φ";
|
||||
|
||||
struct value final : value_block_base {};
|
||||
struct residual final : residual_block_base {};
|
||||
};
|
||||
|
||||
static inline constexpr potential potential_term{};
|
||||
static inline constexpr gradient gradient_term{};
|
||||
|
||||
using Terms = utils::types::TypeList<potential, gradient>;
|
||||
};
|
||||
|
||||
struct enthalpy final : field {
|
||||
struct specific final : term {
|
||||
static constexpr std::string_view name = meta::type_name<enthalpy>();
|
||||
|
||||
struct specific final : term<enthalpy> {
|
||||
static constexpr std::string_view name = meta::type_name<specific>();
|
||||
|
||||
using FunctionalSpace = functional_space::DiscreteFunctionalSpace<functional_space::H1, 3, 0>;
|
||||
using Support = domain::StellarDomains;
|
||||
using Quantity = dimensions::SpecificEnthalpy;
|
||||
static constexpr std::string_view symbol = "h";
|
||||
|
||||
struct value final : value_block_base { };
|
||||
struct residual final : residual_block_base { };
|
||||
};
|
||||
|
||||
static inline constexpr specific specific_term{};
|
||||
};
|
||||
|
||||
using Terms = utils::types::TypeList<specific>;
|
||||
};
|
||||
}
|
||||
|
||||
43
src/include/serif/discretization/blocks/runtime.hpp
Normal file
43
src/include/serif/discretization/blocks/runtime.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
|
||||
namespace serif::discretization::blocks {
|
||||
struct FieldDescriptor final {
|
||||
std::string_view name;
|
||||
|
||||
[[nodiscard]] friend constexpr bool operator==(const FieldDescriptor&, const FieldDescriptor&) noexcept = default;
|
||||
};
|
||||
|
||||
struct TermDescriptor final {
|
||||
FieldDescriptor field;
|
||||
std::string_view name;
|
||||
|
||||
[[nodiscard]] friend constexpr bool operator==(const TermDescriptor&, const TermDescriptor&) noexcept = default;
|
||||
};
|
||||
|
||||
template <Field FieldT>
|
||||
inline constexpr FieldDescriptor field_descriptor{.name = FieldT::name};
|
||||
|
||||
template <Term TermT>
|
||||
inline constexpr TermDescriptor term_descriptor{
|
||||
.field = field_descriptor<typename TermT::field_type>,
|
||||
.name = TermT::name
|
||||
};
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::blocks::FieldDescriptor> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::blocks::FieldDescriptor& fd, FormatContext& ctx) const {
|
||||
return std::formatter<std::string_view>::format(fd.name, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::blocks::TermDescriptor> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::blocks::TermDescriptor& td, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(std::format("TermDescriptor(field={}, name={})", td.field, td.name), ctx);
|
||||
}
|
||||
};
|
||||
@@ -1 +1,32 @@
|
||||
#pragma once
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
|
||||
namespace serif::discretization::blocks {
|
||||
template <Term TermT>
|
||||
using FieldOf = std::remove_cvref_t<TermT>::field_type;
|
||||
|
||||
template <HasFunctionalSpace TermT>
|
||||
using FunctionalSpaceOf = std::remove_cvref_t<TermT>::FunctionalSpace;
|
||||
|
||||
template <HasSupport TermT>
|
||||
using SupportOf = std::remove_cvref_t<TermT>::Support;
|
||||
|
||||
template <HasQuantity TermT>
|
||||
using QuantityOf = std::remove_cvref_t<TermT>::Quantity;
|
||||
|
||||
template <typename FieldT>
|
||||
using TermsOf = FieldT::Terms;
|
||||
|
||||
template <SpatialTerm TermT>
|
||||
struct SpatialTermTraits {
|
||||
using FunctionalSpace = TermT::FunctionalSpace;
|
||||
using Support = TermT::Support;
|
||||
|
||||
static constexpr auto family = FunctionalSpace::functionalSpace.family;
|
||||
static constexpr int order = FunctionalSpace::order;
|
||||
static constexpr std::size_t rank = FunctionalSpace::rank;
|
||||
};
|
||||
}
|
||||
@@ -1,6 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include <span>
|
||||
#include <array>
|
||||
#include <format>
|
||||
#include <numeric>
|
||||
|
||||
#include "serif/discretization/domain/types.hpp"
|
||||
#include "serif/utils/misc/std_helper/cleaning.hpp"
|
||||
@@ -17,10 +22,33 @@ namespace serif::discretization::domain {
|
||||
concept IsBoundary = AIsBaseClassOfB<T, Boundary>;
|
||||
|
||||
// A DomainSet is several volumes treated as one. StellarDomains
|
||||
// (core + envelope) is the motivating example.
|
||||
// (core + envelope) is a motivating example.
|
||||
template <IsDomain... DomainTs>
|
||||
struct DomainSet {
|
||||
static constexpr std::size_t count = CountVariadicArguments<DomainTs...>();
|
||||
|
||||
static inline const std::array<std::string_view, count> names{
|
||||
DomainTs::name...
|
||||
};
|
||||
};
|
||||
|
||||
// Do not make the constructors of this class explicit. We want to be able to pass a single domain or a domain set to functions that take a DomainOrSet.
|
||||
class DomainOrSet final {
|
||||
public:
|
||||
template <IsDomain DomainT>
|
||||
// ReSharper disable once CppNonExplicitConvertingConstructor
|
||||
constexpr DomainOrSet(DomainT) noexcept : m_domains(&DomainT::name, 1) {}
|
||||
|
||||
template <IsDomain... DomainTs>
|
||||
// ReSharper disable once CppNonExplicitConvertingConstructor
|
||||
constexpr DomainOrSet(DomainSet<DomainTs...>) noexcept : m_domains(DomainSet<DomainTs...>::names) {}
|
||||
|
||||
[[nodiscard]] constexpr std::span<const std::string_view> domains() const noexcept {
|
||||
return m_domains;
|
||||
}
|
||||
|
||||
private:
|
||||
std::span<const std::string_view> m_domains;
|
||||
};
|
||||
|
||||
template <IsBoundary... BoundaryTs>
|
||||
@@ -53,3 +81,25 @@ namespace serif::discretization::domain {
|
||||
template <typename BoundaryT, typename BoundaryIDT>
|
||||
concept BoundaryIsIdentifiedBy = utils::misc::std_helper::SameType<BoundaryT, typename BoundaryIDT::boundary_type>;
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::domain::DomainOrSet> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::domain::DomainOrSet& dos, FormatContext& ctx) const {
|
||||
auto join = [&](std::span<const std::string_view> strings, std::string_view delimiter) {
|
||||
return std::accumulate(
|
||||
strings.begin(),
|
||||
strings.end(),
|
||||
std::string{},
|
||||
[delimiter](std::string acc, std::string_view s) {
|
||||
if (!acc.empty()) {
|
||||
acc += delimiter;
|
||||
}
|
||||
acc += s;
|
||||
return acc;
|
||||
}
|
||||
);
|
||||
};
|
||||
return std::formatter<std::string_view>::format(std::format("DomainOrSet(domains=[{}])", join(dos.domains(), ", ")), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
10
src/include/serif/discretization/forms/concept.hpp
Normal file
10
src/include/serif/discretization/forms/concept.hpp
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
#include "serif/discretization/forms/forms.hpp"
|
||||
|
||||
namespace serif::discretization::forms {
|
||||
template <typename FormCandidate>
|
||||
concept Form = std::derived_from<std::remove_cvref_t<FormCandidate>, form> && // A form must derive from form
|
||||
requires {
|
||||
typename std::remove_cvref_t<FormCandidate>::operand_types; // a form must define a type list of operand types for the trial and test terms
|
||||
}; // We could consider strengthening this to enforce that the type list (operand_types) contains only operand types and that there is an entry for TrialT and TestT
|
||||
}
|
||||
48
src/include/serif/discretization/forms/form_traits.hpp
Normal file
48
src/include/serif/discretization/forms/form_traits.hpp
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/discretization/forms/concept.hpp"
|
||||
#include "serif/discretization/forms/forms.hpp"
|
||||
|
||||
namespace serif::discretization::forms {
|
||||
template <Form FormT>
|
||||
struct FormTraits {
|
||||
static constexpr int trial_order_reduction = 0;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct FormTraits<mass<TrialT, TestT>> {
|
||||
static constexpr int trial_order_reduction = 0;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct FormTraits<divergence<TrialT, TestT>> {
|
||||
static constexpr int trial_order_reduction = 1;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct FormTraits<source<TrialT, TestT>> {
|
||||
static constexpr int trial_order_reduction = 0;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct FormTraits<force<TrialT, TestT>> {
|
||||
static constexpr int trial_order_reduction = 0;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct FormTraits<boundary_flux<TrialT, TestT>> {
|
||||
static constexpr int trial_order_reduction = 0;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct FormTraits<projection<TrialT, TestT>> {
|
||||
static constexpr int trial_order_reduction = 0;
|
||||
static constexpr int test_order_reduction = 0;
|
||||
};
|
||||
}
|
||||
73
src/include/serif/discretization/forms/forms.hpp
Normal file
73
src/include/serif/discretization/forms/forms.hpp
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
#include "serif/discretization/forms/operands.hpp"
|
||||
#include "serif/utils/types/type_list.hpp"
|
||||
#include "serif/meta/names.hpp"
|
||||
|
||||
namespace serif::discretization::forms {
|
||||
struct form {};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct mass final : form {
|
||||
static constexpr std::string_view name = meta::template_name<mass>();
|
||||
|
||||
using trial_type = TrialT;
|
||||
using test_type = TestT;
|
||||
|
||||
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct divergence final : form {
|
||||
static constexpr std::string_view name = meta::template_name<divergence>();
|
||||
|
||||
using trial_type = TrialT;
|
||||
using test_type = TestT;
|
||||
|
||||
using operand_types = utils::types::TypeList<Operand<TrialT, Operation::Divergence>, Operand<TestT>>;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct source final : form {
|
||||
static constexpr std::string_view name = meta::template_name<source>();
|
||||
|
||||
using trial_type = TrialT;
|
||||
using test_type = TestT;
|
||||
|
||||
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct force final : form {
|
||||
static constexpr std::string_view name = meta::template_name<force>();
|
||||
|
||||
using trial_type = TrialT;
|
||||
using test_type = TestT;
|
||||
|
||||
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct boundary_flux final : form {
|
||||
static constexpr std::string_view name = meta::template_name<boundary_flux>();
|
||||
|
||||
using trial_type = TrialT;
|
||||
using test_type = TestT;
|
||||
|
||||
using operand_types = utils::types::TypeList<Operand<TrialT, Operation::NormalTrace>, Operand<TestT, Operation::NormalTrace>>;
|
||||
};
|
||||
|
||||
template <blocks::Term TrialT, blocks::Term TestT>
|
||||
struct projection final : form {
|
||||
static constexpr std::string_view name = meta::template_name<projection>();
|
||||
|
||||
using trial_type = TrialT;
|
||||
using test_type = TestT;
|
||||
|
||||
using operand_types = utils::types::TypeList<Operand<TrialT>, Operand<TestT>>;
|
||||
};
|
||||
}
|
||||
38
src/include/serif/discretization/forms/operands.hpp
Normal file
38
src/include/serif/discretization/forms/operands.hpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
|
||||
namespace serif::discretization::forms {
|
||||
struct Operation {
|
||||
struct Value {};
|
||||
struct Gradient {};
|
||||
struct Divergence {};
|
||||
struct Curl {};
|
||||
struct NormalTrace {};
|
||||
};
|
||||
|
||||
template <typename OperationCandidate>
|
||||
concept IsOperation =
|
||||
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::Value> ||
|
||||
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::Gradient> ||
|
||||
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::Divergence> ||
|
||||
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::Curl> ||
|
||||
std::same_as<std::remove_cvref_t<OperationCandidate>, Operation::NormalTrace>;
|
||||
|
||||
template <blocks::Term TermT, IsOperation OperationT = Operation::Value> // We choose to default to the value operation because it is the most common.
|
||||
struct Operand final {
|
||||
using term_type = TermT;
|
||||
using operation_type = OperationT;
|
||||
};
|
||||
|
||||
template <typename OperandCandidate>
|
||||
concept IsOperand = requires { // Recall that requires expressions are not evaluated. What this checks is that it is valid syntax given the type to requires the term type and operation type. It does not check that the term type is a valid term or that the operation type is a valid operation.
|
||||
typename std::remove_cvref_t<OperandCandidate>::term_type;
|
||||
typename std::remove_cvref_t<OperandCandidate>::operation_type; } &&
|
||||
blocks::Term<typename std::remove_cvref_t<OperandCandidate>::term_type> && // Then here, now that we know we can safely ask for the term type we validate it is actually a valid term
|
||||
IsOperation<typename std::remove_cvref_t<OperandCandidate>::operation_type>; // And here we validate that the operation type is actually a valid operation
|
||||
|
||||
};
|
||||
34
src/include/serif/discretization/forms/runtime.hpp
Normal file
34
src/include/serif/discretization/forms/runtime.hpp
Normal file
@@ -0,0 +1,34 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <format>
|
||||
|
||||
#include "serif/discretization/blocks/runtime.hpp"
|
||||
#include "serif/discretization/forms/concept.hpp"
|
||||
|
||||
namespace serif::discretization::forms {
|
||||
struct FormDescriptor final {
|
||||
std::string_view name;
|
||||
|
||||
blocks::TermDescriptor trial;
|
||||
blocks::TermDescriptor test;
|
||||
|
||||
[[nodiscard]] friend constexpr bool operator==(const FormDescriptor&, const FormDescriptor&) noexcept = default;
|
||||
};
|
||||
|
||||
template <Form FormT>
|
||||
inline constexpr FormDescriptor form_descriptor{
|
||||
.name = FormT::name,
|
||||
.trial = blocks::term_descriptor<typename FormT::trial_type>,
|
||||
.test = blocks::term_descriptor<typename FormT::test_type>
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::forms::FormDescriptor> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::forms::FormDescriptor& fd, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(std::format("FormDescriptor(name={}, trial={}, test={})", fd.name, fd.trial, fd.test), ctx);
|
||||
}
|
||||
};
|
||||
167
src/include/serif/discretization/functional_space/space.hpp
Normal file
167
src/include/serif/discretization/functional_space/space.hpp
Normal file
@@ -0,0 +1,167 @@
|
||||
#pragma once
|
||||
#include <type_traits>
|
||||
#include <format>
|
||||
|
||||
#include "serif/meta/enums.hpp"
|
||||
|
||||
namespace serif::discretization::functional_space {
|
||||
template <std::size_t R>
|
||||
struct Rank {
|
||||
constexpr static std::size_t value = R;
|
||||
};
|
||||
|
||||
enum class Family {
|
||||
l2,
|
||||
h1,
|
||||
hdiv,
|
||||
hcurl
|
||||
};
|
||||
|
||||
inline static auto Family2StringMap = meta::enum_to_enum_string_map<Family>();
|
||||
inline static auto String2FamilyMap = meta::enum_to_string_enum_map<Family>();
|
||||
|
||||
enum class Continuity {
|
||||
discontinuous,
|
||||
continuous,
|
||||
normal_continuous,
|
||||
tangential_continuous
|
||||
};
|
||||
|
||||
inline static auto Continuity2StringMap = meta::enum_to_enum_string_map<Continuity>();
|
||||
inline static auto String2ContinuityMap = meta::enum_to_string_enum_map<Continuity>();
|
||||
|
||||
enum class Trace {
|
||||
none,
|
||||
value,
|
||||
normal,
|
||||
tangential
|
||||
};
|
||||
|
||||
inline static auto Trace2StringMap = meta::enum_to_enum_string_map<Trace>();
|
||||
inline static auto String2TraceMap = meta::enum_to_string_enum_map<Trace>();
|
||||
|
||||
enum class Mapping {
|
||||
identity,
|
||||
contravariant_piola,
|
||||
covariant_piola
|
||||
};
|
||||
|
||||
inline static auto Mapping2StringMap = meta::enum_to_enum_string_map<Mapping>();
|
||||
inline static auto String2MappingMap = meta::enum_to_string_enum_map<Mapping>();
|
||||
|
||||
struct SpaceDescriptor final {
|
||||
const Family family;
|
||||
const Continuity continuity;
|
||||
const Trace trace;
|
||||
const Mapping mapping;
|
||||
};
|
||||
|
||||
inline static constexpr SpaceDescriptor L2{
|
||||
.family = Family::l2,
|
||||
.continuity = Continuity::discontinuous,
|
||||
.trace = Trace::none,
|
||||
.mapping = Mapping::identity
|
||||
};
|
||||
|
||||
inline static constexpr SpaceDescriptor H1{
|
||||
.family = Family::h1,
|
||||
.continuity = Continuity::continuous,
|
||||
.trace = Trace::value,
|
||||
.mapping = Mapping::identity
|
||||
};
|
||||
|
||||
inline static constexpr SpaceDescriptor Hdiv{
|
||||
.family = Family::hdiv,
|
||||
.continuity = Continuity::normal_continuous,
|
||||
.trace = Trace::normal,
|
||||
.mapping = Mapping::contravariant_piola
|
||||
};
|
||||
|
||||
inline static constexpr SpaceDescriptor Hcurl{
|
||||
.family = Family::hcurl,
|
||||
.continuity = Continuity::tangential_continuous,
|
||||
.trace = Trace::tangential,
|
||||
.mapping = Mapping::covariant_piola
|
||||
};
|
||||
|
||||
template <typename SpaceDescriptorCandidate>
|
||||
concept IsSpaceDescriptor = std::is_same_v<SpaceDescriptor, SpaceDescriptorCandidate>;
|
||||
|
||||
template <SpaceDescriptor Space, int Order, std::size_t R>
|
||||
struct DiscreteFunctionalSpace {
|
||||
static constexpr SpaceDescriptor functional_space = Space;
|
||||
static constexpr int order = Order;
|
||||
using rank = Rank<R>;
|
||||
};
|
||||
}
|
||||
|
||||
template <std::size_t R>
|
||||
struct std::formatter<serif::discretization::functional_space::Rank<R>> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::Rank<R>& /*rank*/, FormatContext& ctx) const {
|
||||
if (R == 0) {
|
||||
return std::formatter<std::string>::format("Scalar", ctx);
|
||||
} else if (R == 1) {
|
||||
return std::formatter<std::string>::format("Vector", ctx);
|
||||
} else if (R >=2) {
|
||||
return std::formatter<std::string>::format("Rank " + std::to_string(R) + " tensor", ctx);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::functional_space::SpaceDescriptor> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::SpaceDescriptor& space, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(std::format("SpaceDescriptor(family={}, continuity={}, trace={}, mapping={})",
|
||||
serif::discretization::functional_space::Family2StringMap.at(space.family),
|
||||
serif::discretization::functional_space::Continuity2StringMap.at(space.continuity),
|
||||
serif::discretization::functional_space::Trace2StringMap.at(space.trace),
|
||||
serif::discretization::functional_space::Mapping2StringMap.at(space.mapping)), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// formatter for DiscreteFunctionalSpace
|
||||
template <serif::discretization::functional_space::SpaceDescriptor Space, int Order, std::size_t R>
|
||||
struct std::formatter<serif::discretization::functional_space::DiscreteFunctionalSpace<Space, Order, R>> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::DiscreteFunctionalSpace<Space, Order, R>& space, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(std::format("DiscreteFunctionalSpace(functional_space={}, order={}, rank={})",
|
||||
space.functional_space,
|
||||
space.order,
|
||||
space.rank), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
// formatter for each enum
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::functional_space::Family> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::Family& family, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(serif::discretization::functional_space::Family2StringMap.at(family), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::functional_space::Continuity> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::Continuity& continuity, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(serif::discretization::functional_space::Continuity2StringMap.at(continuity), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::functional_space::Trace> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::Trace& trace, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(serif::discretization::functional_space::Trace2StringMap.at(trace), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::functional_space::Mapping> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::functional_space::Mapping& mapping, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(serif::discretization::functional_space::Mapping2StringMap.at(mapping), ctx);
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
#include "mfem/fem/intrules.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature::backend::mfem {
|
||||
template <typename IntegratorT>
|
||||
concept HasReferenceSetIntegrationRule = requires (IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) {
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
};
|
||||
|
||||
template <typename IntegratorT>
|
||||
concept HasPointerSetIntegrationRule = requires (IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) {
|
||||
integrator.SetIntRule(&integration_rule);
|
||||
};
|
||||
|
||||
template <typename IntegratorT>
|
||||
concept HasSetIntRule = requires (IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) {
|
||||
integrator.SetIntRule(integration_rule);
|
||||
};
|
||||
|
||||
template <typename IntegratorT>
|
||||
concept ConfigurableIntegrator = HasReferenceSetIntegrationRule<IntegratorT> ||
|
||||
HasPointerSetIntegrationRule<IntegratorT> ||
|
||||
HasSetIntRule<IntegratorT>;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "mfem/fem/intrules.hpp"
|
||||
|
||||
#include "serif/discretization/quadrature/backend/mfem/concepts.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature::backend::mfem {
|
||||
template <ConfigurableIntegrator IntegratorT>
|
||||
void set_integration_rule(IntegratorT &integrator, const ::mfem::IntegrationRule &integration_rule) {
|
||||
if constexpr (HasReferenceSetIntegrationRule<IntegratorT>) {
|
||||
integrator.SetIntegrationRule(integration_rule);
|
||||
} else if constexpr (HasPointerSetIntegrationRule<IntegratorT>) {
|
||||
integrator.SetIntegrationRule(&integration_rule);
|
||||
} else {
|
||||
integrator.SetIntRule(&integration_rule);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
#include <span>
|
||||
|
||||
#include "mfem/fem/geom.hpp"
|
||||
#include "mfem/fem/intrules.hpp"
|
||||
|
||||
#include "serif/discretization/blocks/traits.hpp"
|
||||
#include "serif/discretization/quadrature/rules.hpp"
|
||||
|
||||
#include "serif/discretization/quadrature/backend/order.hpp"
|
||||
|
||||
#include "serif/discretization/domain/concepts.hpp"
|
||||
#include "serif/discretization/quadrature/policy.hpp"
|
||||
#include "serif/discretization/quadrature/query.hpp"
|
||||
|
||||
#include "serif/discretization/quadrature/backend/mfem/concepts.hpp"
|
||||
#include "serif/discretization/quadrature/backend/mfem/mfem_static_order.hpp"
|
||||
|
||||
// We may want to consider renaming this file to runtime to stick to convention. The reason
|
||||
// I did not initially is that this is more specific than pure runtime projection of compile
|
||||
// time invariants (which is what most of the other files named runtime.hpp do). Something to think
|
||||
// about
|
||||
|
||||
// Note also that you will see ::mfem throughout. This is an important syntax to use here since MFEM's
|
||||
// code lives in the mfem namespace; however, we also have code living in an mfem namespace.
|
||||
// Outside of translation units that include using namespace serif::discretization::quadrature::backend
|
||||
// and outside of the serif::discretization::quadrature::backend::mfem namespace itself these two things
|
||||
// do not conflict. However, in those two cases there is a degeneracy the compiler cannot
|
||||
// resolve between mfem:: and mfem:: (see what I did there?) Using ::mfem explicitly tells the compiler
|
||||
// that we are looking from the root of the namespace tree not the current namespace. This is important to avoid ambiguity and compiler errors.
|
||||
|
||||
namespace serif::discretization::quadrature::backend::mfem {
|
||||
struct MFEMQuadratureContext final {
|
||||
QuadratureRole role = QuadratureRole::discretization;
|
||||
|
||||
::mfem::Geometry::Type geometry = ::mfem::Geometry::INVALID;
|
||||
int geometry_weight_order = 0;
|
||||
|
||||
std::span<const int> dynamic_orders{};
|
||||
domain::DomainOrSet domain = domain::AllDomains{};
|
||||
|
||||
MappingKind mapping = MappingKind::none;
|
||||
};
|
||||
|
||||
struct MFEMRule final {
|
||||
Resolution resolution;
|
||||
|
||||
const ::mfem::IntegrationRule *integration_rule = nullptr;
|
||||
};
|
||||
|
||||
class MFEMResolver final {
|
||||
public:
|
||||
explicit MFEMResolver (Policy policy) noexcept : m_policy(std::move(policy)) {} // Do we actually want to use move rvalue semantics here?
|
||||
|
||||
template <forms::Form FormT>
|
||||
[[nodiscard]] MFEMRule resolve(const MFEMQuadratureContext& context) const {
|
||||
if (context.geometry_weight_order < 0) {
|
||||
utils::errors::serif_error<std::invalid_argument, utils::errors::SERiFErrorCode::INVALID_QUADRATURE_ORDER>("MFEM geometry weight order must be non-negative. Received an order of {}", context.geometry_weight_order);
|
||||
}
|
||||
|
||||
const int dynamic_order = sum_dynamic_orders(context.dynamic_orders);
|
||||
const int base_order = mfem_form_static_order<FormT> + dynamic_order + context.geometry_weight_order;
|
||||
|
||||
const Query query {
|
||||
.form = forms::form_descriptor<FormT>,
|
||||
.role = context.role,
|
||||
.domain = context.domain,
|
||||
.mapping = context.mapping,
|
||||
.base_order = base_order
|
||||
};
|
||||
|
||||
const Resolution resolution = m_policy.resolve(query);
|
||||
|
||||
const ::mfem::IntegrationRule &integration_rule = ::mfem::IntRules.Get(context.geometry, resolution.order);
|
||||
return MFEMRule{.resolution = resolution, .integration_rule = &integration_rule};
|
||||
}
|
||||
|
||||
template <forms::Form FormT, ConfigurableIntegrator IntegratorT>
|
||||
Resolution configure(IntegratorT &integrator, const MFEMQuadratureContext& context) const {
|
||||
const MFEMRule rule = resolve<FormT>(context);
|
||||
set_integration_rule(integrator, *rule.integration_rule);
|
||||
return rule.resolution;
|
||||
}
|
||||
|
||||
private:
|
||||
Policy m_policy;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
#include "serif/discretization/forms/operands.hpp"
|
||||
#include "serif/utils/types/type_list.hpp"
|
||||
#include "serif/discretization/quadrature/backend/order.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature::backend::mfem {
|
||||
template <typename OperandListT>
|
||||
struct MFEMOperandListOrder;
|
||||
|
||||
template <forms::IsOperand... OperandTs>
|
||||
struct MFEMOperandListOrder<utils::types::TypeList<OperandTs...>> final {
|
||||
static constexpr int value = (resolve_operand_order<OperandTs>() + ... + 0);
|
||||
};
|
||||
|
||||
template <forms::Form FormT>
|
||||
inline constexpr int mfem_form_static_order = MFEMOperandListOrder<typename FormT::operand_types>::value;
|
||||
}
|
||||
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <type_traits>
|
||||
|
||||
#include "serif/discretization/functional_space/space.hpp"
|
||||
#include "serif/discretization/blocks/concepts.hpp"
|
||||
#include "serif/discretization/forms/operands.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature::backend::mfem {
|
||||
template <typename FunctionalSpaceT>
|
||||
struct DiscreteFunctionalSpaceTraits;
|
||||
|
||||
template <auto SpaceV, auto OrderV, auto RankV>
|
||||
struct DiscreteFunctionalSpaceTraits<functional_space::DiscreteFunctionalSpace<SpaceV, OrderV, RankV>> final {
|
||||
static constexpr auto space = SpaceV;
|
||||
static constexpr auto order = static_cast<int>(OrderV);
|
||||
static constexpr std::size_t rank = static_cast<std::size_t>(RankV);
|
||||
};
|
||||
|
||||
template <blocks::HasFunctionalSpace TermT>
|
||||
using FunctionalSpaceTraits = DiscreteFunctionalSpaceTraits<typename TermT::FunctionalSpace>;
|
||||
|
||||
template <forms::IsOperand OperandT>
|
||||
[[nodiscard]] consteval int resolve_operand_order() noexcept {
|
||||
using Operand = std::remove_cvref_t<OperandT>;
|
||||
using Term = Operand::term_type;
|
||||
using Operation = Operand::operation_type;
|
||||
|
||||
if constexpr (!blocks::HasFunctionalSpace<Term>) {
|
||||
static_assert(std::same_as<forms::Operation, forms::Operation::Value>, "Non-Spatial terms only support the value operation at the moment.");
|
||||
return 0;
|
||||
} else {
|
||||
using Space = FunctionalSpaceTraits<Term>;
|
||||
|
||||
constexpr auto family = Space::space.family;
|
||||
constexpr int order = Space::order;
|
||||
|
||||
if constexpr (std::same_as<Operation, forms::Operation::Value>) {
|
||||
if constexpr (family == functional_space::Family::hdiv) {
|
||||
return order + 1;
|
||||
} else {
|
||||
return order;
|
||||
}
|
||||
} else if constexpr (std::same_as<Operation, forms::Operation::Gradient>) {
|
||||
static_assert(family == functional_space::Family::h1, "Only H1 spaces support the gradient operation at the moment.");
|
||||
|
||||
return order > 0 ? order - 1 : 0;
|
||||
} else if constexpr (std::same_as<Operation, forms::Operation::Divergence>) {
|
||||
static_assert(family == functional_space::Family::hdiv, "Only H(div) spaces support the divergence operation at the moment.");
|
||||
|
||||
return order;
|
||||
} else if constexpr (std::same_as<Operation, forms::Operation::Curl>) {
|
||||
static_assert(family == functional_space::Family::hcurl, "Only H(curl) spaces support the curl operation at the moment.");
|
||||
|
||||
return order > 0 ? order - 1 : 0;
|
||||
} else if constexpr (std::same_as<Operation, forms::Operation::NormalTrace>) {
|
||||
static_assert(family == functional_space::Family::hdiv, "Only H(div) spaces support the normal trace operation at the moment.");
|
||||
|
||||
return order;
|
||||
} else {
|
||||
static_assert(false, "Unsupported operation type. This indicates that an operation was used which is not currently registered in the operand configuration system. If you are a developer and you are seeing this error, please register your operation in the resolve_operand_order function in discretization/quadrature/detail.hpp. If you are a user and you are seeing this error, please report it to the SERiF developers.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] inline int sum_dynamic_orders(const std::span<const int> dynamic_orders) {
|
||||
int total = 0;
|
||||
for (const int order : dynamic_orders) {
|
||||
if (order < 0) {
|
||||
utils::errors::serif_error<std::invalid_argument, utils::errors::SERiFErrorCode::INVALID_QUADRATURE_ORDER>("Dynamic quadrature order cannot be negative. Received {}.", order);
|
||||
}
|
||||
|
||||
total += order;
|
||||
}
|
||||
return total;
|
||||
}
|
||||
}
|
||||
|
||||
20
src/include/serif/discretization/quadrature/policy.hpp
Normal file
20
src/include/serif/discretization/quadrature/policy.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/discretization/quadrature/query.hpp"
|
||||
#include "serif/discretization/quadrature/rules.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature {
|
||||
class Policy final {
|
||||
public:
|
||||
explicit Policy(RuleSet ruleSet);
|
||||
|
||||
[[nodiscard]] Resolution resolve(const Query &query) const;
|
||||
|
||||
private:
|
||||
[[nodiscard]] static int compute_base_order(const Query &query);
|
||||
[[nodiscard]] const RuleControl &get_role_control(QuadratureRole role) const;
|
||||
|
||||
private:
|
||||
RuleSet m_ruleSet;
|
||||
};
|
||||
}
|
||||
74
src/include/serif/discretization/quadrature/query.hpp
Normal file
74
src/include/serif/discretization/quadrature/query.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <format>
|
||||
#include <string>
|
||||
|
||||
#include "serif/discretization/quadrature/rules.hpp"
|
||||
#include "serif/discretization/domain/physical_domains.hpp"
|
||||
#include "serif/discretization/forms/form_traits.hpp"
|
||||
#include "serif/discretization/forms/runtime.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature {
|
||||
struct Query final {
|
||||
forms::FormDescriptor form;
|
||||
|
||||
QuadratureRole role = QuadratureRole::discretization;
|
||||
domain::DomainOrSet domain = domain::AllDomains{};
|
||||
MappingKind mapping = MappingKind::none;
|
||||
|
||||
int trial_order = 0;
|
||||
int test_order = 0;
|
||||
int coefficient_order = 0;
|
||||
int geometry_weight_order = 0;
|
||||
int trial_order_reduction = 0;
|
||||
int test_order_reduction = 0;
|
||||
|
||||
std::optional<int> base_order;
|
||||
};
|
||||
|
||||
template <forms::Form FormT>
|
||||
[[nodiscard]] constexpr Query make_query(
|
||||
const QuadratureRole role,
|
||||
const domain::DomainOrSet domain,
|
||||
const MappingKind mapping,
|
||||
const int trialOrder,
|
||||
const int testOrder,
|
||||
const int coefficientOrder,
|
||||
const int geometryWeightOrder,
|
||||
const std::optional<int> baseOrder = std::nullopt
|
||||
) noexcept {
|
||||
return Query{
|
||||
.form = forms::form_descriptor<FormT>,
|
||||
.role = role,
|
||||
.domain = domain,
|
||||
.mapping = mapping,
|
||||
.trial_order = trialOrder,
|
||||
.test_order = testOrder,
|
||||
.coefficient_order = coefficientOrder,
|
||||
.geometry_weight_order = geometryWeightOrder,
|
||||
.trial_order_reduction = forms::FormTraits<FormT>::trial_order_reduction,
|
||||
.test_order_reduction = forms::FormTraits<FormT>::test_order_reduction,
|
||||
.base_order = baseOrder
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::Query> : std::formatter<std::string> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::Query& q, FormatContext& ctx) const {
|
||||
return std::formatter<std::string>::format(std::format("Query(form={}, role={}, domain={}, mapping={}, trial_order={}, test_order={}, coefficient_order={}, geometry_weight_order={}, trial_order_reduction={}, test_order_reduction={}, base_order={})",
|
||||
q.form,
|
||||
q.role,
|
||||
q.domain,
|
||||
q.mapping,
|
||||
q.trial_order,
|
||||
q.test_order,
|
||||
q.coefficient_order,
|
||||
q.geometry_weight_order,
|
||||
q.trial_order_reduction,
|
||||
q.test_order_reduction,
|
||||
(q.base_order.has_value() ? std::to_string(q.base_order.value()) : "nullopt")
|
||||
), ctx);
|
||||
}
|
||||
};
|
||||
342
src/include/serif/discretization/quadrature/rules.hpp
Normal file
342
src/include/serif/discretization/quadrature/rules.hpp
Normal file
@@ -0,0 +1,342 @@
|
||||
#pragma once
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <format>
|
||||
#include <utility>
|
||||
|
||||
#include "serif/discretization/forms/runtime.hpp"
|
||||
|
||||
namespace serif::discretization::quadrature {
|
||||
enum class QuadratureRole {
|
||||
discretization,
|
||||
preconditioner,
|
||||
diagnostic,
|
||||
projection
|
||||
};
|
||||
|
||||
enum class MappingKind {
|
||||
none,
|
||||
affine,
|
||||
general,
|
||||
kelvin
|
||||
};
|
||||
|
||||
enum class Mode {
|
||||
fast,
|
||||
production,
|
||||
reference,
|
||||
convergence
|
||||
};
|
||||
|
||||
struct RuleControl {
|
||||
std::optional<int> fixed_order;
|
||||
int boost = 0;
|
||||
};
|
||||
|
||||
struct FormRule final {
|
||||
forms::FormDescriptor form;
|
||||
RuleControl control;
|
||||
};
|
||||
|
||||
class RuleSet {
|
||||
public:
|
||||
template <forms::Form FormT>
|
||||
void set_form_control(
|
||||
const RuleControl control
|
||||
) {
|
||||
set_form_control(forms::form_descriptor<FormT>, control);
|
||||
}
|
||||
|
||||
void set_form_control(
|
||||
const forms::FormDescriptor &form,
|
||||
const RuleControl control
|
||||
) {
|
||||
for (auto &[formDescriptor, ruleControl] : m_formRules) {
|
||||
if (formDescriptor == form) {
|
||||
ruleControl = control;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
m_formRules.push_back(FormRule{.form = form, .control = control});
|
||||
}
|
||||
|
||||
[[nodiscard]] const RuleControl *find_form_control(
|
||||
const forms::FormDescriptor &form
|
||||
) const noexcept {
|
||||
for (const auto &[formDescriptor, ruleControl] : m_formRules) {
|
||||
if (formDescriptor == form) {
|
||||
return &ruleControl;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void set_role_control(
|
||||
const QuadratureRole role,
|
||||
const RuleControl control
|
||||
) noexcept {
|
||||
switch (role) {
|
||||
case QuadratureRole::discretization:
|
||||
m_discretizationControl = control;
|
||||
break;
|
||||
|
||||
case QuadratureRole::preconditioner:
|
||||
m_preconditionerControl = control;
|
||||
break;
|
||||
|
||||
case QuadratureRole::diagnostic:
|
||||
m_diagnosticControl = control;
|
||||
break;
|
||||
|
||||
case QuadratureRole::projection:
|
||||
m_projectionControl = control;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[[nodiscard]] const RuleControl &role_control(
|
||||
const QuadratureRole role
|
||||
) const noexcept {
|
||||
switch (role) {
|
||||
case QuadratureRole::discretization:
|
||||
return m_discretizationControl;
|
||||
|
||||
case QuadratureRole::preconditioner:
|
||||
return m_preconditionerControl;
|
||||
|
||||
case QuadratureRole::diagnostic:
|
||||
return m_diagnosticControl;
|
||||
|
||||
case QuadratureRole::projection:
|
||||
return m_projectionControl;
|
||||
}
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
void set_fallback_control(
|
||||
const RuleControl control
|
||||
) noexcept {
|
||||
m_fallbackControl = control;
|
||||
}
|
||||
|
||||
[[nodiscard]] const RuleControl &fallback_control() const noexcept {
|
||||
return m_fallbackControl;
|
||||
}
|
||||
|
||||
auto begin() {
|
||||
return m_formRules.begin();
|
||||
}
|
||||
|
||||
auto end() {
|
||||
return m_formRules.end();
|
||||
}
|
||||
|
||||
auto begin() const {
|
||||
return m_formRules.begin();
|
||||
}
|
||||
|
||||
auto end() const {
|
||||
return m_formRules.end();
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<FormRule> m_formRules;
|
||||
|
||||
RuleControl m_discretizationControl;
|
||||
RuleControl m_preconditionerControl;
|
||||
RuleControl m_diagnosticControl;
|
||||
RuleControl m_projectionControl;
|
||||
|
||||
RuleControl m_fallbackControl;
|
||||
};
|
||||
|
||||
struct RoleControls {
|
||||
RuleControl discretization;
|
||||
RuleControl preconditioner;
|
||||
RuleControl diagnostic;
|
||||
RuleControl projection;
|
||||
};
|
||||
|
||||
|
||||
struct Resolution {
|
||||
int base_order;
|
||||
int boost;
|
||||
int order;
|
||||
bool used_fixed_order;
|
||||
};
|
||||
|
||||
struct QuadratureTermOptions {
|
||||
std::optional<int> fixed_order;
|
||||
int additional_boost = 0;
|
||||
};
|
||||
|
||||
struct QuadratureManifestOptions {
|
||||
bool enabled = false;
|
||||
bool include_repeated_queries = false;
|
||||
std::optional<std::string> output_file;
|
||||
};
|
||||
|
||||
struct QuadratureValidationOptions {
|
||||
bool require_explicit_base_order = false;
|
||||
bool require_explicit_mfem_rule = false;
|
||||
bool reject_negative_boosts = true;
|
||||
bool report_unused_overrides = true;
|
||||
};
|
||||
|
||||
struct QuadratureRoleOptions {
|
||||
QuadratureTermOptions discretization;
|
||||
QuadratureTermOptions preconditioner;
|
||||
QuadratureTermOptions diagnostic;
|
||||
QuadratureTermOptions projection;
|
||||
};
|
||||
|
||||
struct QuadratureOptions {
|
||||
Mode mode = Mode::production;
|
||||
int global_boost = 0;
|
||||
std::optional<int> fallback_fixed_order;
|
||||
|
||||
QuadratureTermOptions gravity_hdiv_mass;
|
||||
QuadratureTermOptions gravity_divergence;
|
||||
QuadratureTermOptions gravity_source;
|
||||
QuadratureTermOptions gravity_force;
|
||||
QuadratureTermOptions gravity_boundary;
|
||||
QuadratureTermOptions centrifugal;
|
||||
QuadratureTermOptions density_projection;
|
||||
QuadratureTermOptions eos_closure;
|
||||
QuadratureTermOptions hydrostatic_equilibrium;
|
||||
QuadratureTermOptions isobaric_surface;
|
||||
QuadratureTermOptions mesh_extension;
|
||||
QuadratureTermOptions mass_conservation;
|
||||
QuadratureTermOptions mass_normalization;
|
||||
QuadratureTermOptions center_of_mass;
|
||||
QuadratureTermOptions quadrupole;
|
||||
QuadratureTermOptions gravitational_energy;
|
||||
QuadratureTermOptions pressure_integral;
|
||||
QuadratureTermOptions pressure_force;
|
||||
QuadratureTermOptions virial;
|
||||
QuadratureTermOptions error_norm;
|
||||
|
||||
QuadratureRoleOptions roles;
|
||||
|
||||
std::vector<int> convergence_boosts = {0, 2, 4};
|
||||
QuadratureManifestOptions manifest;
|
||||
QuadratureValidationOptions validation;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::QuadratureRole> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::QuadratureRole& role, FormatContext& ctx) const {
|
||||
std::string_view roleStr;
|
||||
switch (role) {
|
||||
case serif::discretization::quadrature::QuadratureRole::preconditioner:
|
||||
roleStr = "preconditioner";
|
||||
break;
|
||||
case serif::discretization::quadrature::QuadratureRole::diagnostic:
|
||||
roleStr = "diagnostic";
|
||||
break;
|
||||
case serif::discretization::quadrature::QuadratureRole::projection:
|
||||
roleStr = "projection";
|
||||
break;
|
||||
default:
|
||||
roleStr = "unknown";
|
||||
break;
|
||||
}
|
||||
return std::formatter<std::string_view>::format(roleStr, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::MappingKind> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::MappingKind& mk, FormatContext& ctx) const {
|
||||
std::string_view mappingKindStr;
|
||||
switch (mk) {
|
||||
case serif::discretization::quadrature::MappingKind::none:
|
||||
mappingKindStr = "none";
|
||||
break;
|
||||
case serif::discretization::quadrature::MappingKind::affine:
|
||||
mappingKindStr = "affine";
|
||||
break;
|
||||
case serif::discretization::quadrature::MappingKind::general:
|
||||
mappingKindStr = "general";
|
||||
break;
|
||||
case serif::discretization::quadrature::MappingKind::kelvin:
|
||||
mappingKindStr = "kelvin";
|
||||
break;
|
||||
default:
|
||||
mappingKindStr = "unknown";
|
||||
break;
|
||||
}
|
||||
return std::formatter<std::string_view>::format(mappingKindStr, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::Mode> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::Mode& mode, FormatContext& ctx) const {
|
||||
std::string_view modeStr;
|
||||
switch (mode) {
|
||||
case serif::discretization::quadrature::Mode::fast:
|
||||
modeStr = "fast";
|
||||
break;
|
||||
case serif::discretization::quadrature::Mode::production:
|
||||
modeStr = "production";
|
||||
break;
|
||||
case serif::discretization::quadrature::Mode::reference:
|
||||
modeStr = "reference";
|
||||
break;
|
||||
case serif::discretization::quadrature::Mode::convergence:
|
||||
modeStr = "convergence";
|
||||
break;
|
||||
default:
|
||||
modeStr = "unknown";
|
||||
break;
|
||||
}
|
||||
return std::formatter<std::string_view>::format(modeStr, ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::RuleControl> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::RuleControl& rc, FormatContext& ctx) const {
|
||||
return std::formatter<std::string_view>::format(
|
||||
std::format("RuleControl(fixed_order={}, boost={})", rc.fixed_order.has_value() ? std::to_string(rc.fixed_order.value()) : "none", rc.boost),
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::FormRule> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::FormRule& fr, FormatContext& ctx) const {
|
||||
return std::formatter<std::string_view>::format(
|
||||
std::format("FormRule(form={}, control={})", fr.form, fr.control),
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <>
|
||||
struct std::formatter<serif::discretization::quadrature::RuleSet> : std::formatter<std::string_view> {
|
||||
template <typename FormatContext>
|
||||
auto format(const serif::discretization::quadrature::RuleSet& rs, FormatContext& ctx) const {
|
||||
std::string rulesStr;
|
||||
for (const auto& rule : rs) {
|
||||
if (!rulesStr.empty()) {
|
||||
rulesStr += ", ";
|
||||
}
|
||||
rulesStr += std::format("{}", rule);
|
||||
}
|
||||
return std::formatter<std::string_view>::format(
|
||||
std::format("RuleSet(formRules=[{}])", rulesStr),
|
||||
ctx);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
31
src/include/serif/meta/enums.hpp
Normal file
31
src/include/serif/meta/enums.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
#include <unordered_map>
|
||||
#include <type_traits>
|
||||
|
||||
#include "magic_enum/magic_enum_all.hpp"
|
||||
|
||||
namespace serif::meta {
|
||||
template <typename EnumType>
|
||||
[[nodiscard]] constexpr std::unordered_map<std::string_view, EnumType> enum_to_string_enum_map() noexcept {
|
||||
static_assert(std::is_enum_v<EnumType>, "EnumType must be an enum type");
|
||||
|
||||
std::unordered_map<std::string_view, EnumType> map;
|
||||
|
||||
for (const auto& [value, name] : magic_enum::enum_entries<EnumType>()) {
|
||||
map.emplace(name, value);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
template <typename EnumType>
|
||||
[[nodiscard]] constexpr std::unordered_map<EnumType, std::string_view> enum_to_enum_string_map() noexcept {
|
||||
static_assert(std::is_enum_v<EnumType>, "EnumType must be an enum type");
|
||||
|
||||
std::unordered_map<EnumType, std::string_view> map;
|
||||
|
||||
for (const auto& [value, name] : magic_enum::enum_entries<EnumType>()) {
|
||||
map.emplace(value, name);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
}
|
||||
25
src/include/serif/meta/names.hpp
Normal file
25
src/include/serif/meta/names.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include <string_view>
|
||||
|
||||
#include "nameof.hpp"
|
||||
|
||||
/* If we ever move to C++26 or above we can replace these with native reflection
|
||||
*/
|
||||
namespace serif::meta {
|
||||
template <typename T>
|
||||
[[nodiscard]] consteval std::string_view type_name() noexcept {
|
||||
return nameof::nameof_short_type<T>();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
[[nodiscard]] consteval std::string_view template_name() noexcept {
|
||||
constexpr std::string_view typeName = type_name<T>();
|
||||
constexpr std::size_t templateStart = typeName.find('<');
|
||||
|
||||
if constexpr (templateStart == std::string_view::npos) {
|
||||
return typeName;
|
||||
}
|
||||
|
||||
return typeName.substr(0, templateStart);
|
||||
}
|
||||
}
|
||||
52
src/include/serif/utils/error/codes.hpp
Normal file
52
src/include/serif/utils/error/codes.hpp
Normal file
@@ -0,0 +1,52 @@
|
||||
#pragma once
|
||||
#include <cstdint>
|
||||
|
||||
#include "serif/meta/enums.hpp"
|
||||
|
||||
namespace serif::utils::errors {
|
||||
// Each section should start at another hundred to allow for simple reading of error codes by users
|
||||
// C++ enums automatically count up from the previous value so you only need to specify the first
|
||||
// member of that category. By convention please make the first member the most general
|
||||
// class of error in that category, subsequent errors can specify further.
|
||||
// subcategories should follow the same patter but in 10s
|
||||
|
||||
// Further, by convention please keep all names upper case. The meta functions
|
||||
// will convert all lower case names to upper case but its good convention to
|
||||
// stay consistent here.
|
||||
|
||||
// At some point someone should go through and come up with some well defined schema for all this
|
||||
enum class SERiFErrorCode : uint16_t {
|
||||
UNKNOWN_ERROR = 0,
|
||||
|
||||
// RUNTIME FAILURES (>100)
|
||||
// Invalid argument errors
|
||||
INVALID_ARGUMENT = 100,
|
||||
INVALID_QUADRATURE_ORDER,
|
||||
|
||||
INVALID_DOMAIN_ERROR=110,
|
||||
NEGATIVE_VALUE_ERROR,
|
||||
NON_FINITE_VALUE_ERROR,
|
||||
|
||||
// Jacobian Errors
|
||||
JACOBIAN_ERROR = 200,
|
||||
INVERTED_JACOBIAN,
|
||||
|
||||
// EOS Errors
|
||||
EOS_ERROR = 300,
|
||||
EOS_UNSUPPORTED_RELATION,
|
||||
EOS_UNSUPPORTED_DERIVATIVE,
|
||||
EOS_WRONG_INPUT_COUNT,
|
||||
EOS_WRONG_INPUT_QUANTITY,
|
||||
EOS_NONFINITE_INPUT,
|
||||
EOS_OUTSIDE_DOMAIN,
|
||||
EOS_NONFINITE_RESULT,
|
||||
EOS_INVALID_THERMODYNAMIC_INPUT,
|
||||
|
||||
// COMPILE TIME FAILURES (>1000)
|
||||
// Type Invariant Failures
|
||||
FAILED_TYPE_INVARIANT = 1000
|
||||
};
|
||||
|
||||
inline auto SERiFErrorCodeNameToTypeMap = meta::enum_to_string_enum_map<SERiFErrorCode>();
|
||||
inline auto SERiFErrorCodeTypeToNameMap = meta::enum_to_enum_string_map<SERiFErrorCode>();
|
||||
}
|
||||
78
src/include/serif/utils/error/errors.hpp
Normal file
78
src/include/serif/utils/error/errors.hpp
Normal file
@@ -0,0 +1,78 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <format>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
|
||||
#include "serif/utils/misc/terminal/colors.hpp"
|
||||
#include "serif/utils/misc/concepts/exceptions.hpp"
|
||||
#include "serif/utils/error/codes.hpp"
|
||||
#include "serif/utils/types/strings.hpp"
|
||||
|
||||
// The *ONLY* place in SERiF that throw should
|
||||
// be called is here. All other error throwing locations should
|
||||
// call serif_error to ensure we have consistent handling of errors
|
||||
|
||||
// If you want to throw a warning then look at the parallel warnings module and call serif_warning
|
||||
// Note errors cannot be disabled at compile time, warnings can be disabled to a noop at compile time.
|
||||
|
||||
namespace serif::utils::errors {
|
||||
template <misc::concepts::IsException ExceptionT, SERiFErrorCode code>
|
||||
void serif_error(const std::string_view message, auto... args) {
|
||||
std::string error_msg;
|
||||
std::string code_name{SERiFErrorCodeTypeToNameMap.at(code)};
|
||||
|
||||
std::ranges::transform(code_name, code_name.begin(), [](const unsigned char c){ return std::toupper(c); });
|
||||
if (misc::terminal::supports_ansi(stderr)) {
|
||||
error_msg = std::format("{}{}{}: {}", misc::terminal::a_red, "Error", misc::terminal::a_reset, std::vformat(message, std::make_format_args(args...)));
|
||||
// format error code with color if terminal supports ANSI
|
||||
error_msg = std::format("{} (code No.: {} [{}{}{}])", error_msg, static_cast<std::underlying_type_t<SERiFErrorCode>>(code), misc::terminal::a_red, code_name, misc::terminal::a_reset);
|
||||
} else {
|
||||
error_msg = std::format("Error: {}", std::vformat(message, std::make_format_args(args...)));
|
||||
// Add error code without color if terminal does not support ANSI
|
||||
error_msg = std::format("{} (code No.: {} [{}])", error_msg, static_cast<std::underlying_type_t<SERiFErrorCode>>(code), code_name);
|
||||
}
|
||||
|
||||
throw ExceptionT(error_msg);
|
||||
}
|
||||
|
||||
template <SERiFErrorCode code>
|
||||
void serif_assert(const bool condition, const std::string_view message, auto... args) {
|
||||
// collapse to a noop if NDEBUG is defined
|
||||
#ifndef NDEBUG
|
||||
constexpr bool is_debug_build = true;
|
||||
#else
|
||||
constexpr bool is_debug_build = false;
|
||||
#endif
|
||||
if constexpr(is_debug_build) {
|
||||
if (!condition) {
|
||||
serif_error<std::logic_error, code>(message, args...);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
template <auto Message, auto Code>
|
||||
struct serif_error_printer;
|
||||
|
||||
template <SERiFErrorCode code, types::ct_error_string message, bool condition>
|
||||
constexpr void serif_assert() {
|
||||
#ifndef NDEBUG
|
||||
constexpr bool is_debug_build = true;
|
||||
#else
|
||||
constexpr bool is_debug_build = false;
|
||||
#endif
|
||||
|
||||
if constexpr (is_debug_build) {
|
||||
if constexpr (!condition) {
|
||||
// Trigger an intentional error to display the message and code at compile time
|
||||
|
||||
// Note if we move to C++26 we can replace this with a more standard and less convoluted
|
||||
// formated static_assert.
|
||||
sizeof(serif_error_printer<message, code>);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
25
src/include/serif/utils/misc/concepts/concat.hpp
Normal file
25
src/include/serif/utils/misc/concepts/concat.hpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
#include "serif/utils/types/type_list.hpp"
|
||||
|
||||
namespace serif::utils::misc::concepts {
|
||||
template <typename... Lists>
|
||||
struct Concat;
|
||||
|
||||
template <>
|
||||
struct Concat<> {
|
||||
using Type = types::TypeList<>;
|
||||
};
|
||||
|
||||
template <typename... Ts>
|
||||
struct Concat<types::TypeList<Ts...>> {
|
||||
using Type = types::TypeList<Ts...>;
|
||||
};
|
||||
|
||||
template <typename... LhsTs, typename... RhsTs, typename... Rest>
|
||||
struct Concat<types::TypeList<LhsTs...>, types::TypeList<RhsTs...>, Rest...> {
|
||||
using Type = typename Concat<types::TypeList<LhsTs..., RhsTs...>, Rest...>::Type;
|
||||
};
|
||||
|
||||
template <typename... Lists>
|
||||
using ConcatT = Concat<Lists...>::Type;
|
||||
}
|
||||
18
src/include/serif/utils/misc/concepts/enumeration.hpp
Normal file
18
src/include/serif/utils/misc/concepts/enumeration.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include "serif/utils/misc/concepts/concat.hpp"
|
||||
#include "serif/utils/types/type_list.hpp"
|
||||
|
||||
namespace serif::utils::misc::concepts {
|
||||
// Might make sense to rename these to FlatMap or some such
|
||||
template <typename TypeList, template <typename> typename Descriptor>
|
||||
struct EnumerateTypes;
|
||||
|
||||
template <template <typename> typename Descriptor, typename... TypeTs>
|
||||
struct EnumerateTypes<types::TypeList<TypeTs...>, Descriptor> {
|
||||
using Type = ConcatT<Descriptor<TypeTs>...>;
|
||||
};
|
||||
|
||||
template <typename TypeList, template <typename> typename Descriptor>
|
||||
using EnumerateTypesT = EnumerateTypes<TypeList, Descriptor>::Type;
|
||||
}
|
||||
37
src/include/serif/utils/misc/concepts/filter.hpp
Normal file
37
src/include/serif/utils/misc/concepts/filter.hpp
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include <type_traits>
|
||||
#include "serif/utils/types/type_list.hpp"
|
||||
|
||||
namespace serif::utils::misc::concepts {
|
||||
template <typename T, typename List>
|
||||
struct Prepend;
|
||||
|
||||
template <typename T, typename... Ts>
|
||||
struct Prepend<T, types::TypeList<Ts...>> {
|
||||
using Type = types::TypeList<T, Ts...>;
|
||||
};
|
||||
|
||||
template <typename T, typename List>
|
||||
using PrependT = Prepend<T, List>::Type;
|
||||
|
||||
|
||||
template <typename List, template <typename> typename Predicate>
|
||||
struct Filter;
|
||||
|
||||
template <template <typename> typename Predicate>
|
||||
struct Filter<types::TypeList<>, Predicate> {
|
||||
using Type = types::TypeList<>;
|
||||
};
|
||||
|
||||
template <typename Head, typename... Tail, template <typename> typename Predicate>
|
||||
struct Filter<types::TypeList<Head, Tail...>, Predicate> {
|
||||
private:
|
||||
using FilteredTail = Filter<types::TypeList<Tail...>, Predicate>::Type;
|
||||
|
||||
public:
|
||||
using Type = std::conditional_t<Predicate<Head>::value, PrependT<Head, FilteredTail>, FilteredTail>;
|
||||
};
|
||||
|
||||
template <typename List, template <typename> typename Predicate>
|
||||
using FilterT = Filter<List, Predicate>::Type;
|
||||
}
|
||||
@@ -5,39 +5,36 @@
|
||||
|
||||
#include "serif/utils/misc/concepts/numeric.hpp"
|
||||
#include "serif/utils/misc/concepts/exceptions.hpp"
|
||||
#include "serif/utils/error/errors.hpp"
|
||||
|
||||
namespace serif::utils::misc {
|
||||
template <typename ExceptionType, concepts::IsNumeric Scalar, concepts::IsEnum ErrorCode>
|
||||
requires concepts::IsExceptionWithErrorCode<ExceptionType, ErrorCode>
|
||||
void validate_finite(const Scalar value, ErrorCode error_code) {
|
||||
template <typename ExceptionType, errors::SERiFErrorCode ErrorCode, concepts::IsNumeric Scalar>
|
||||
void validate_finite(const Scalar value) {
|
||||
if (!std::isfinite(value)) {
|
||||
throw ExceptionType(error_code, std::format("The value must be finite. Instead a value of {} has been provided", value));
|
||||
errors::serif_error<ExceptionType, ErrorCode>("The value must be finite. Instead a value of {} has been provided", value);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename ExceptionType, concepts::IsComparableToNumeric Scalar, concepts::IsEnum ErrorCode>
|
||||
requires concepts::IsExceptionWithErrorCode<ExceptionType, ErrorCode>
|
||||
void validate_greater_than(const Scalar value, const Scalar predicate, ErrorCode error_code) {
|
||||
validate_finite<ExceptionType>(value, error_code);
|
||||
template <typename ExceptionType, errors::SERiFErrorCode ErrorCode, concepts::IsComparableToNumeric Scalar>
|
||||
void validate_greater_than(const Scalar value, const Scalar predicate) {
|
||||
validate_finite<ExceptionType, ErrorCode>(value);
|
||||
if (value < predicate) {
|
||||
throw ExceptionType(error_code, std::format("The value must be finite and greater than {}. While the value is finite it is also less than {}. A value of {} has been provided", predicate, predicate, value));
|
||||
errors::serif_error<ExceptionType, ErrorCode>("The value must be finite and greater than {}. While the value is finite it is also less than {}. A value of {} has been provided", predicate, predicate, value);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename ExceptionType, concepts::IsComparableToNumeric Scalar, concepts::IsEnum ErrorCode>
|
||||
requires concepts::IsExceptionWithErrorCode<ExceptionType, ErrorCode>
|
||||
void validate_less_than(const Scalar value, const Scalar predicate, ErrorCode error_code) {
|
||||
validate_finite<ExceptionType>(value, error_code);
|
||||
template <typename ExceptionType, errors::SERiFErrorCode ErrorCode, concepts::IsComparableToNumeric Scalar>
|
||||
void validate_less_than(const Scalar value, const Scalar predicate) {
|
||||
validate_finite<ExceptionType, ErrorCode>(value);
|
||||
if (value > predicate) {
|
||||
throw ExceptionType(error_code, std::format("The value must be finite and less than {}. While the value is finite it is also greater than {}. A value of {} has been provided", predicate, predicate, value));
|
||||
errors::serif_error<ExceptionType, ErrorCode>("The value must be finite and less than {}. While the value is finite it is also greater than {}. A value of {} has been provided", predicate, predicate, value);
|
||||
};
|
||||
}
|
||||
|
||||
template <typename ExceptionType, concepts::IsComparableToNumeric Scalar, concepts::IsEnum ErrorCode>
|
||||
requires concepts::IsExceptionWithErrorCode<ExceptionType, ErrorCode>
|
||||
void validate_nonnegativity(const Scalar value, ErrorCode error_code) {
|
||||
validate_finite<ExceptionType>(value, error_code);
|
||||
validate_greater_than<ExceptionType>(value,static_cast<Scalar>(0), error_code);
|
||||
template <typename ExceptionType, errors::SERiFErrorCode ErrorCode, concepts::IsComparableToNumeric Scalar>
|
||||
void validate_nonnegativity(const Scalar value) {
|
||||
validate_finite<ExceptionType, ErrorCode>(value);
|
||||
validate_greater_than<ExceptionType, ErrorCode>(value,static_cast<Scalar>(0));
|
||||
}
|
||||
|
||||
|
||||
|
||||
74
src/include/serif/utils/misc/terminal/colors.hpp
Normal file
74
src/include/serif/utils/misc/terminal/colors.hpp
Normal file
@@ -0,0 +1,74 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace serif::utils::misc::terminal {
|
||||
enum class ANSIColors : int {
|
||||
BLACK = 30,
|
||||
RED = 31,
|
||||
GREEN = 32,
|
||||
YELLOW = 33,
|
||||
BLUE = 34,
|
||||
MAGENTA = 35,
|
||||
CYAN = 36,
|
||||
WHITE = 37,
|
||||
BRIGHT_BLACK = 90,
|
||||
BRIGHT_RED = 91,
|
||||
BRIGHT_GREEN = 92,
|
||||
RESET = 0
|
||||
};
|
||||
|
||||
consteval std::string_view resolve_ansi_color(const ANSIColors color) noexcept {
|
||||
switch (color) {
|
||||
case ANSIColors::BLACK: return "\033[30m";
|
||||
case ANSIColors::RED: return "\033[31m";
|
||||
case ANSIColors::GREEN: return "\033[32m";
|
||||
case ANSIColors::YELLOW: return "\033[33m";
|
||||
case ANSIColors::BLUE: return "\033[34m";
|
||||
case ANSIColors::MAGENTA: return "\033[35m";
|
||||
case ANSIColors::CYAN: return "\033[36m";
|
||||
case ANSIColors::WHITE: return "\033[37m";
|
||||
case ANSIColors::BRIGHT_BLACK: return "\033[90m";
|
||||
case ANSIColors::BRIGHT_RED: return "\033[91m";
|
||||
case ANSIColors::BRIGHT_GREEN: return "\033[92m";
|
||||
case ANSIColors::RESET: return "\033[0m";
|
||||
}
|
||||
|
||||
std::unreachable();
|
||||
}
|
||||
|
||||
constexpr std::string_view a_black = resolve_ansi_color(ANSIColors::BLACK);
|
||||
constexpr std::string_view a_red = resolve_ansi_color(ANSIColors::RED);
|
||||
constexpr std::string_view a_green = resolve_ansi_color(ANSIColors::GREEN);
|
||||
constexpr std::string_view a_yellow = resolve_ansi_color(ANSIColors::YELLOW);
|
||||
constexpr std::string_view a_blue = resolve_ansi_color(ANSIColors::BLUE);
|
||||
constexpr std::string_view a_magenta = resolve_ansi_color(ANSIColors::MAGENTA);
|
||||
constexpr std::string_view a_cyan = resolve_ansi_color(ANSIColors::CYAN);
|
||||
constexpr std::string_view a_white = resolve_ansi_color(ANSIColors::WHITE);
|
||||
constexpr std::string_view a_bright_black = resolve_ansi_color(ANSIColors::BRIGHT_BLACK);
|
||||
constexpr std::string_view a_bright_red = resolve_ansi_color(ANSIColors::BRIGHT_RED);
|
||||
constexpr std::string_view a_bright_green = resolve_ansi_color(ANSIColors::BRIGHT_GREEN);
|
||||
constexpr std::string_view a_reset = resolve_ansi_color(ANSIColors::RESET);
|
||||
|
||||
inline bool supports_ansi(std::FILE* stream = stdout) noexcept {
|
||||
if (const char* noColor = std::getenv("NO_COLOR");
|
||||
noColor != nullptr && *noColor != '\0') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (::isatty(::fileno(stream)) == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const char* term = std::getenv("TERM");
|
||||
|
||||
if (term == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return std::string_view(term) != "dumb";
|
||||
}
|
||||
}
|
||||
31
src/include/serif/utils/types/strings.hpp
Normal file
31
src/include/serif/utils/types/strings.hpp
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <string_view>
|
||||
|
||||
namespace serif::utils::types {
|
||||
template<std::size_t N>
|
||||
struct ctstring {
|
||||
char data[N]{};
|
||||
|
||||
constexpr ctstring(const char (&str)[N]) {
|
||||
std::copy_n(str, N, data);
|
||||
}
|
||||
|
||||
constexpr std::string_view view() const { return {data, N - 1}; }
|
||||
};
|
||||
|
||||
template<std::size_t N>
|
||||
struct ct_error_string {
|
||||
char data[289+N]{};
|
||||
char padding[289]{"NOTE: This error message is generated at compile time. C++ < 26 limits how compile time errors can be formatted, This is the relevant error message not downstream warnings about implicit instantiations of an undefined template. Those are used to force the compiler to display this message"};
|
||||
|
||||
constexpr ct_error_string(const char (&str)[N]) {
|
||||
std::copy_n(str, N, data);
|
||||
std::copy_n(padding, 289, data+N);
|
||||
}
|
||||
|
||||
constexpr std::string_view view() const { return {data, N - 1}; }
|
||||
};
|
||||
}
|
||||
6
src/include/serif/utils/types/type_list.hpp
Normal file
6
src/include/serif/utils/types/type_list.hpp
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
namespace serif::utils::types {
|
||||
template <typename...Ts>
|
||||
struct TypeList {};
|
||||
}
|
||||
24
src/include/serif/utils/warnings/warning.hpp
Normal file
24
src/include/serif/utils/warnings/warning.hpp
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
// TODO: Sort out how to get this from a meson option and ideally not use any macros or at least resolve them to type safe compile time values
|
||||
#define DISABLE_WARNING 0
|
||||
|
||||
#include <string_view>
|
||||
#include <format>
|
||||
#include <print>
|
||||
#include <cstdio>
|
||||
#include "serif/utils/misc/terminal/colors.hpp"
|
||||
|
||||
// We may want to do a similar thing to what we did with errors and introduce a SERiFWarningCode enum and a serif_warning function that takes a warning code and a message. This would allow us to have more structured warnings and potentially allow users to filter or handle warnings based on their codes. However, for now, we will keep it simple with just a message.
|
||||
|
||||
namespace serif::utils::warnings {
|
||||
void serif_warning(const std::string_view message, auto... args) {
|
||||
if constexpr (DISABLE_WARNING == 0) { // This is a constexpr so that the compiler can optimize out the warnings to a no-op
|
||||
if (misc::terminal::supports_ansi(stderr)) {
|
||||
std::println(stderr, "{}{}{}: {}", misc::terminal::a_yellow, "Warning", misc::terminal::a_reset, std::vformat(message, std::make_format_args(args...)));
|
||||
} else {
|
||||
std::println(stderr, "Warning: {}", std::vformat(message, std::make_format_args(args...)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user