feat(model): added model sub system

SERiF defines a so-called "model" subsystem. A model is a fully compile-time description of a problem. Note that this is a description not an evaluation tool. The basic idea is that since we have this robust description we can more easily serialize and be confident that others who load a model are getting the exact same thing. Further, this provides a convient place to do things like validate that all options are compatible, do some limited initial construction of the needed computational graph, allocate needed memory, etc... Some of the stuff that might initially seem to belong to a model will be in the discritization system though (e.g. FEM object lifetimes)
This commit is contained in:
2026-11-09 13:20:46 -05:00
parent c6d1686c5d
commit 48c6bac1cc
24 changed files with 1816 additions and 0 deletions

View File

@@ -0,0 +1,60 @@
#include <algorithm>
#include <cmath>
#include <stdexcept>
#include "serif/model/specifications/fixed_angular_momentum.hpp"
#include "serif/model/specifications/fixed_total_mass.hpp"
#include "serif/utils/error/errors.hpp"
namespace serif::model
{
/** @brief Validate a dimensional scalar target before storing a physical prescription. */
static void validate_target(double value, bool zero_allowed, std::string_view name)
{
using namespace utils::errors;
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(std::isfinite(value) && (zero_allowed ? value >= 0.0 : value > 0.0),
"The {} target must be finite and {}; received {}. Supply a physical target in the permitted range.", name, zero_allowed ? "nonnegative" : "positive", value);
}
/** @brief Store a physically admissible total mass. */
FixedTotalMass::FixedTotalMass(TargetValue mass) : m_target(mass)
{
validate_target(mass.value(), false, "total mass");
}
/** @brief Delegate named mass parameters to the dimensional constructor. */
FixedTotalMass::FixedTotalMass(Parameters parameters) : FixedTotalMass(parameters.total_mass)
{
}
/** @brief Use the default rotation axis for a dimensional angular momentum target. */
FixedAngularMomentum::FixedAngularMomentum(TargetValue angular_momentum) : FixedAngularMomentum(Parameters{.angular_momentum = angular_momentum})
{
}
/** @brief Preserve arbitrary finite axes and centers without overflowing the normalization calculation. */
FixedAngularMomentum::FixedAngularMomentum(Parameters parameters) : m_target(parameters.angular_momentum), m_axis(parameters.axis), m_center(parameters.center)
{
using namespace utils::errors;
validate_target(m_target.value(), true, "angular momentum");
double maximum_component = 0.0;
for (std::size_t component = 0; component < m_axis.size(); ++component)
{
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(std::isfinite(m_axis[component]) && std::isfinite(m_center[component]),
"Rotation axis and center components must be finite; component {} contains axis {} and center {}. Supply a finite rotation axis and center.", component, m_axis[component], m_center[component]);
maximum_component = std::max(maximum_component, std::abs(m_axis[component]));
}
serif_ensure<SERiFErrorCode::INVALID_ARGUMENT, std::invalid_argument>(maximum_component > 0.0, "A rotation axis must be nonzero; supply a direction such as (0, 0, 1).");
for (double& component : m_axis)
{
component /= maximum_component;
}
const double norm = std::hypot(m_axis[0], m_axis[1], m_axis[2]);
for (double& component : m_axis)
{
component /= norm;
}
}
} // namespace serif::model