module; #include #include #include #include #include #include export module mean_field:operators.prepared_surface_constraint; export import :field.mfem; export import :surface.compiled; namespace mean_field::operators::detail { template struct SingleQuantitySurfaceState final { dimensions::QuantityValue quantityValue; [[nodiscard]] dimensions::QuantityValue value(Quantity) const noexcept { return quantityValue; } }; } // namespace mean_field::operators::detail export namespace mean_field::operators { /* * Runtime enforcement currently supports a pointwise pressure constraint * whose row field is also its sole state field. The concept is expressed * entirely in compiled-constraint metadata: no thermodynamic carrier or * concrete field is selected by this prepared layer. */ template concept SingleFieldPressureSurfaceConstraint = requires { typename std::remove_cvref_t::PhysicalQuantity; typename std::remove_cvref_t::CarrierQuantity; typename std::remove_cvref_t::CarrierField; typename std::remove_cvref_t::SurfaceDependencies; } && std::same_as::PhysicalQuantity, dimensions::quantity::Pressure> && std::same_as< typename std::remove_cvref_t::SurfaceDependencies::RowField, typename std::remove_cvref_t::CarrierField> && std::same_as< typename std::remove_cvref_t::SurfaceDependencies::StateFieldTypes, field::TypeList::CarrierField>>; template concept SingleFieldPressureSurfaceConstraintFor = SingleFieldPressureSurfaceConstraint && std::same_as::SurfaceDependencies::RowField, Field>; /* * Non-owning runtime bridge for a statically compiled pressure constraint. * There is one function-pointer dispatch per complete row application; * the concrete loop remains templated so EOS operations can be inlined. */ class PressureSurfaceConstraintView final { public: template explicit PressureSurfaceConstraintView(const Constraint &constraint) noexcept : m_constraint(std::addressof(constraint)), m_applyResidualRows(&applyResidualRows), m_applyJacobianRows(&applyJacobianRows), m_descriptor(constraint.descriptor()) { } void ApplyResidualRows( const mfem::Vector &surfaceState, const field::FieldBoundaryDofMap &surfaceRows, mfem::Vector &rowResidual ) const { m_applyResidualRows(m_constraint, surfaceState, surfaceRows, rowResidual); } void ApplyJacobianRows( const mfem::Vector &surfaceState, const field::FieldBoundaryDofMap &surfaceRows, const mfem::Vector &stateVariation, mfem::Vector &rowAction ) const { m_applyJacobianRows(m_constraint, surfaceState, surfaceRows, stateVariation, rowAction); } [[nodiscard]] surface::PressureSurfaceDescriptor descriptor() const noexcept { return m_descriptor; } private: using ApplyResidualRowsFunction = void (*)( const void *, const mfem::Vector &, const field::FieldBoundaryDofMap &, mfem::Vector & ); using ApplyJacobianRowsFunction = void (*)( const void *, const mfem::Vector &, const field::FieldBoundaryDofMap &, const mfem::Vector &, mfem::Vector & ); template static void applyResidualRows( const void *constraint, const mfem::Vector &surfaceState, const field::FieldBoundaryDofMap &surfaceRows, mfem::Vector &rowResidual ) { using CarrierQuantity = typename Constraint::CarrierQuantity; for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) { const detail::SingleQuantitySurfaceState state{ dimensions::QuantityValue{surfaceState(surfaceIndex)} }; rowResidual(surfaceRows.reduced_dofs()[surfaceIndex]) = static_cast(constraint)->residual(state); } } template static void applyJacobianRows( const void *constraint, const mfem::Vector &surfaceState, const field::FieldBoundaryDofMap &surfaceRows, const mfem::Vector &stateVariation, mfem::Vector &rowAction ) { using CarrierQuantity = typename Constraint::CarrierQuantity; for (int surfaceIndex = 0; surfaceIndex < surfaceRows.size(); ++surfaceIndex) { const int reducedDof = surfaceRows.reduced_dofs()[surfaceIndex]; const detail::SingleQuantitySurfaceState state{ dimensions::QuantityValue{surfaceState(surfaceIndex)} }; const detail::SingleQuantitySurfaceState variation{ dimensions::QuantityValue{stateVariation(reducedDof)} }; rowAction(reducedDof) = static_cast(constraint)->jacobianAction(state, variation); } } const void *m_constraint; ApplyResidualRowsFunction m_applyResidualRows; ApplyJacobianRowsFunction m_applyJacobianRows; surface::PressureSurfaceDescriptor m_descriptor; }; struct PreparedSurfaceConstraintReport final { bool cachedSurfaceState{false}; [[nodiscard]] bool DidAnyWork() const noexcept { return cachedSurfaceState; } }; class PreparedPressureSurfaceConstraint final { public: PreparedPressureSurfaceConstraint( field::FieldBoundaryDofMap surfaceRows, const PressureSurfaceConstraintView constraint ) : m_surfaceRows(std::move(surfaceRows)), m_constraint(constraint), m_surfaceState(m_surfaceRows.size()) { } [[nodiscard]] PreparedSurfaceConstraintReport Prepare( const mfem::Vector &reducedState, const bool stateChanged ) { MFEM_VERIFY( reducedState.Size() == m_surfaceRows.field_size(), "The pressure surface constraint received a state vector with the wrong size." ); PreparedSurfaceConstraintReport report; if (!m_isPrepared || stateChanged) { for (int surfaceIndex = 0; surfaceIndex < m_surfaceRows.size(); ++surfaceIndex) { const double value = reducedState(m_surfaceRows.reduced_dofs()[surfaceIndex]); MFEM_VERIFY(std::isfinite(value), "The pressure surface constraint received non-finite state."); m_surfaceState(surfaceIndex) = value; } report.cachedSurfaceState = true; } m_isPrepared = true; return report; } void ApplyResidualRows(mfem::Vector &rowResidual) const { VerifyPrepared(); MFEM_VERIFY( rowResidual.Size() == m_surfaceRows.field_size(), "The pressure surface constraint received a residual vector with the wrong size." ); m_constraint.ApplyResidualRows(m_surfaceState, m_surfaceRows, rowResidual); } void ApplyJacobianRows( const mfem::Vector &stateVariation, mfem::Vector &rowAction ) const { VerifyPrepared(); MFEM_VERIFY( stateVariation.Size() == m_surfaceRows.field_size() && rowAction.Size() == m_surfaceRows.field_size(), "The pressure surface constraint received a Jacobian vector with the wrong size." ); m_constraint.ApplyJacobianRows(m_surfaceState, m_surfaceRows, stateVariation, rowAction); } [[nodiscard]] bool IsPrepared() const noexcept { return m_isPrepared; } [[nodiscard]] const field::FieldBoundaryDofMap &GetSurfaceRows() const noexcept { return m_surfaceRows; } [[nodiscard]] surface::PressureSurfaceDescriptor GetPhysicalCondition() const noexcept { return m_constraint.descriptor(); } private: void VerifyPrepared() const { MFEM_VERIFY(m_isPrepared, "The pressure surface constraint must be prepared before row application."); } field::FieldBoundaryDofMap m_surfaceRows; PressureSurfaceConstraintView m_constraint; mfem::Vector m_surfaceState; bool m_isPrepared{false}; }; } // namespace mean_field::operators