From dc6cf98f950c5eb23c371177eea690e3d2e165a9 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 11 Apr 2026 15:42:22 -0600 Subject: [PATCH 01/67] Trying to establish a more specific design for SystemOfEquation (System). --- .../differentiable_numerics/CMakeLists.txt | 5 +- .../differentiable_numerics/field_store.cpp | 57 ++++- .../differentiable_numerics/field_store.hpp | 34 ++- .../multiphysics_time_integrator.cpp | 163 ++++---------- .../multiphysics_time_integrator.hpp | 39 +--- .../solid_mechanics_system.hpp | 145 ++++++------ ...id_mechanics_with_internal_vars_system.hpp | 213 ++++++++++-------- .../differentiable_numerics/system_base.cpp | 53 +++++ .../differentiable_numerics/system_base.hpp | 51 ++--- ...ed_system_solver.cpp => system_solver.cpp} | 32 +-- ...ed_system_solver.hpp => system_solver.hpp} | 12 +- .../test_multiphysics_time_integrator.cpp | 39 +++- .../tests/test_solid_dynamics.cpp | 45 ++-- .../test_solid_static_with_internal_vars.cpp | 44 ++-- .../tests/test_thermal_static.cpp | 48 ++-- .../tests/test_thermo_mechanics.cpp | 72 +++--- .../thermal_system.hpp | 105 +++++---- .../thermo_mechanics_system.hpp | 168 +++++++------- 18 files changed, 700 insertions(+), 625 deletions(-) create mode 100644 src/smith/differentiable_numerics/system_base.cpp rename src/smith/differentiable_numerics/{coupled_system_solver.cpp => system_solver.cpp} (85%) rename src/smith/differentiable_numerics/{coupled_system_solver.hpp => system_solver.hpp} (89%) diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 91dc826899..75d95a4a68 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -10,7 +10,8 @@ set(differentiable_numerics_sources differentiable_physics.cpp lumped_mass_explicit_newmark_state_advancer.cpp nonlinear_block_solver.cpp - coupled_system_solver.cpp + system_solver.cpp + system_base.cpp nonlinear_solve.cpp evaluate_objective.cpp dirichlet_boundary_conditions.cpp @@ -23,7 +24,7 @@ set(differentiable_numerics_headers state_advancer.hpp reaction.hpp nonlinear_block_solver.hpp - coupled_system_solver.hpp + system_solver.hpp differentiable_physics.hpp timestep_estimator.hpp explicit_dynamic_solve.hpp diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 6790474be9..c2dd6e1beb 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -128,8 +128,19 @@ FieldState FieldStore::getParameter(const std::string& param_name) const void FieldStore::setField(const std::string& field_name, FieldState updated_field) { - size_t field_index = getFieldIndex(field_name); - states_[field_index] = updated_field; + if (to_states_index_.count(field_name)) { + states_[to_states_index_.at(field_name)] = updated_field; + return; + } + if (to_params_index_.count(field_name)) { + params_[to_params_index_.at(field_name)] = updated_field; + return; + } + if (!shape_disp_.empty() && shape_disp_[0].get()->name() == field_name) { + shape_disp_[0] = updated_field; + return; + } + SLIC_ERROR("Field '" << field_name << "' not found in setField"); } FieldState FieldStore::getShapeDisp() const { return shape_disp_[0]; } @@ -213,12 +224,50 @@ void FieldStore::setField(size_t index, FieldState updated_field) { states_[inde void FieldStore::addWeakFormReaction(std::string weak_form_name, std::string field_name) { - weak_form_to_test_field_[weak_form_name] = field_name; + for (auto& kv : weak_form_to_test_field_) { + if (kv.first == weak_form_name) { + kv.second = field_name; + return; + } + } + weak_form_to_test_field_.push_back({weak_form_name, field_name}); } std::string FieldStore::getWeakFormReaction(const std::string& weak_form_name) const { - return weak_form_to_test_field_.at(weak_form_name); + for (const auto& kv : weak_form_to_test_field_) { + if (kv.first == weak_form_name) { + return kv.second; + } + } + SLIC_ERROR("Reaction field not found for weak form " << weak_form_name); + return ""; +} + +const std::vector& FieldStore::getParameterFields() const { return params_; } + +const std::vector& FieldStore::getStateFields() const { return states_; } + +std::vector FieldStore::getOutputFieldStates() const +{ + std::vector output; + for (size_t i = 0; i < states_.size(); ++i) { + if (!is_solve_state_[i]) { + output.push_back(states_[i]); + } + } + return output; +} + +std::vector FieldStore::getReactionInfos() const +{ + std::vector infos; + for (const auto& kv : weak_form_to_test_field_) { + const std::string& weak_form_name = kv.first; + const std::string& field_name = kv.second; + infos.push_back({weak_form_name, &getField(field_name).get()->space()}); + } + return infos; } } // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index fc594a6b55..625a5bb824 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -21,6 +21,14 @@ namespace smith { class DirichletBoundaryConditions; class BoundaryConditionManager; +/** + * @brief Information about a dual field. + */ +struct ReactionInfo { + std::string name; ///< The name of the dual field. + const mfem::ParFiniteElementSpace* space = nullptr; ///< The finite element space of the dual field. +}; + /** * @brief Representation of a field type with a name and an optional unknown index. * @tparam Space The finite element space type. @@ -107,6 +115,7 @@ struct FieldStore { to_unknown_index_[type.name] = num_unknowns_; FieldState new_field = smith::createFieldState(*graph_, Space{}, type.name, mesh_->tag()); states_.push_back(new_field); + is_solve_state_.push_back(true); auto latest_bc = addBoundaryConditions(new_field.get()); ++num_unknowns_; SLIC_ERROR_IF(num_unknowns_ != boundary_conditions_.size(), @@ -183,6 +192,7 @@ struct FieldStore { to_states_index_[name] = states_.size(); states_.push_back(smith::createFieldState(*graph_, Space{}, name, mesh_->tag())); + is_solve_state_.push_back(false); return FieldType(name); } @@ -381,6 +391,27 @@ struct FieldStore { * @brief Get the associated mesh. * @return const std::shared_ptr& The mesh. */ + + /** + * @brief Get the list of all parameter fields. + */ + const std::vector& getParameterFields() const; + + /** + * @brief Get the list of all state fields. + */ + const std::vector& getStateFields() const; + + /** + * @brief Get the list of physical, non-solve state fields suitable for output. + */ + std::vector getOutputFieldStates() const; + + /** + * @brief Get information about reaction fields. + */ + std::vector getReactionInfos() const; + const std::shared_ptr& getMesh() const; /** @@ -396,6 +427,7 @@ struct FieldStore { std::vector shape_disp_; std::vector params_; std::vector states_; + std::vector is_solve_state_; std::map to_states_index_; std::map to_params_index_; @@ -416,7 +448,7 @@ struct FieldStore { std::map> weak_form_name_to_field_indices_; std::map> weak_form_name_to_field_names_; - std::map weak_form_to_test_field_; + std::vector> weak_form_to_test_field_; std::vector, TimeIntegrationMapping>> time_integration_rules_; std::map independent_name_to_rule_index_; diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index dd5c0e0556..eb6f61b281 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -7,7 +7,7 @@ #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/nonlinear_solve.hpp" -#include "smith/differentiable_numerics/coupled_system_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" #include "smith/differentiable_numerics/reaction.hpp" @@ -16,16 +16,9 @@ namespace smith { -MultiphysicsTimeIntegrator::MultiphysicsTimeIntegrator(std::shared_ptr field_store, - const std::vector>& weak_forms, - std::shared_ptr solver, - std::shared_ptr cycle_zero_weak_form, - std::shared_ptr cycle_zero_solver) - : field_store_(field_store), - weak_forms_(weak_forms), - solver_(solver), - cycle_zero_weak_form_(cycle_zero_weak_form), - cycle_zero_solver_(cycle_zero_solver) +MultiphysicsTimeIntegrator::MultiphysicsTimeIntegrator(std::shared_ptr system, + std::shared_ptr cycle_zero_system) + : system_(system), cycle_zero_system_(cycle_zero_system) { } @@ -33,105 +26,67 @@ std::pair, std::vector> MultiphysicsTimeI const TimeInfo& time_info, const FieldState& shape_disp, const std::vector& states, const std::vector& params) const { + (void)shape_disp; std::vector current_states = states; + // Sync FieldStore with (possibly updated) states and params so they are current for solve + system_->field_store->setField(system_->field_store->getShapeDisp().get()->name(), shape_disp); + + for (size_t i = 0; i < current_states.size(); ++i) { + system_->field_store->setField(i, current_states[i]); + } + // Optional: update parameter fields as well? (assuming they are aligned) + SLIC_ERROR_ROOT_IF(params.size() != system_->field_store->getParameterFields().size(), + "Parameter size mismatch in advanceState"); + for (size_t i = 0; i < params.size(); ++i) { + system_->field_store->setField(system_->field_store->getParameterFields()[i].get()->name(), params[i]); + } + // Handle initial acceleration solve at cycle 0 const bool requires_cycle_zero_solve = - std::any_of(field_store_->getTimeIntegrationRules().begin(), field_store_->getTimeIntegrationRules().end(), - [](const auto& rule_and_mapping) { + std::any_of(system_->field_store->getTimeIntegrationRules().begin(), + system_->field_store->getTimeIntegrationRules().end(), [](const auto& rule_and_mapping) { return rule_and_mapping.first && rule_and_mapping.first->requiresInitialAccelerationSolve(); }); - if (time_info.cycle() == 0 && cycle_zero_weak_form_ && requires_cycle_zero_solve) { - for (size_t i = 0; i < current_states.size(); ++i) { - field_store_->setField(i, current_states[i]); - } - - std::string test_field_name = field_store_->getWeakFormReaction(cycle_zero_weak_form_->name()); - std::vector wf_fields = field_store_->getStates(cycle_zero_weak_form_->name()); - - FieldState test_field = field_store_->getField(test_field_name); - size_t test_field_idx_in_wf = invalid_block_index; - for (size_t j = 0; j < wf_fields.size(); ++j) { - if (wf_fields[j].get() == test_field.get()) { - test_field_idx_in_wf = j; - break; - } - } - SLIC_ERROR_IF(test_field_idx_in_wf == invalid_block_index, "Test field '" << test_field_name - << "' not found in cycle-zero weak form '" - << cycle_zero_weak_form_->name() << "'"); - - std::vector wf_ptrs = {cycle_zero_weak_form_.get()}; - std::vector> block_indices = {{test_field_idx_in_wf}}; - - std::vector bcs; - auto all_bcs = field_store_->getBoundaryConditionManagers(); - size_t test_field_unknown_idx = invalid_block_index; - try { - test_field_unknown_idx = field_store_->getUnknownIndex(test_field_name); - } catch (const std::out_of_range&) { - for (const auto& [rule, mapping] : field_store_->getTimeIntegrationRules()) { - static_cast(rule); - if (mapping.primary_name == test_field_name || mapping.history_name == test_field_name || - mapping.dot_name == test_field_name || mapping.ddot_name == test_field_name) { - test_field_unknown_idx = field_store_->getUnknownIndex(mapping.primary_name); - break; - } - } - } - SLIC_ERROR_IF(test_field_unknown_idx == invalid_block_index, - "Could not map cycle-zero test field '" << test_field_name << "' to an independent unknown."); - SLIC_ERROR_IF(test_field_unknown_idx >= all_bcs.size(), - "Cycle-zero test field '" << test_field_name << "' has unknown index " << test_field_unknown_idx - << ", but only " << all_bcs.size() << " BC managers are registered."); - bcs.push_back(all_bcs[test_field_unknown_idx]); - - std::vector> states_vec = {wf_fields}; - std::vector> params_vec = {params}; - - auto& cz_solver = cycle_zero_solver_ ? cycle_zero_solver_ : solver_; - auto result = cz_solver->solve(wf_ptrs, block_indices, shape_disp, states_vec, params_vec, time_info, bcs); - size_t test_field_state_idx = field_store_->getFieldIndex(test_field_name); - current_states[test_field_state_idx] = result[0]; + if (time_info.cycle() == 0 && cycle_zero_system_ && requires_cycle_zero_solve) { + auto cz_unknowns = cycle_zero_system_->solve(time_info); + SLIC_ERROR_IF(cz_unknowns.size() != 1, "Cycle-zero system produced incorrect number of unknowns, expected 1."); + + // Cycle zero system solves for initial acceleration which translates into test field. + // Sync the solved unknowns back into current_states before main solve + // Assuming cycle zero solve gives us the state for test field: + std::string test_field_name = + system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms.front()->name()); + size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); + current_states[test_field_state_idx] = cz_unknowns[0]; + system_->field_store->setField(test_field_state_idx, cz_unknowns[0]); } - // Sync FieldStore with (possibly updated) states - for (size_t i = 0; i < current_states.size(); ++i) { - field_store_->setField(i, current_states[i]); - } - - std::vector primary_unknowns = solve(weak_forms_, *field_store_, solver_.get(), time_info, params); + std::vector primary_unknowns = system_->solve(time_info); // Create states for reaction computation: newly solved primary unknowns + current states std::vector states_for_reactions = current_states; - for (const auto& [rule, mapping] : field_store_->getTimeIntegrationRules()) { - size_t u_idx = field_store_->getFieldIndex(mapping.primary_name); - size_t unknown_idx = field_store_->getUnknownIndex(mapping.primary_name); + for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { + size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); + size_t unknown_idx = system_->field_store->getUnknownIndex(mapping.primary_name); FieldState u_new = primary_unknowns[unknown_idx]; states_for_reactions[u_idx] = u_new; } // Compute reactions using newly solved unknowns but BEFORE time integration state updates - std::vector reactions; - for (const auto& wf : weak_forms_) { - std::vector wf_fields = field_store_->getStatesFromVectors(wf->name(), states_for_reactions, params); - std::string test_field_name = field_store_->getWeakFormReaction(wf->name()); - size_t test_field_idx = field_store_->getFieldIndex(test_field_name); - FieldState test_field = states_for_reactions[test_field_idx]; - reactions.push_back(smith::evaluateWeakForm(wf, time_info, shape_disp, wf_fields, test_field)); - } + std::vector reactions = system_->computeReactions(time_info, states_for_reactions); // Now do time integration to compute corrected velocities/accelerations and update all states - const auto& all_current_states = field_store_->getAllFields(); + const auto& all_current_states = system_->field_store->getAllFields(); std::vector new_states = current_states; for (size_t i = 0; i < current_states.size(); ++i) { new_states[i] = all_current_states[i]; } - for (const auto& [rule, mapping] : field_store_->getTimeIntegrationRules()) { - size_t u_idx = field_store_->getFieldIndex(mapping.primary_name); - size_t unknown_idx = field_store_->getUnknownIndex(mapping.primary_name); + for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { + size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); + size_t unknown_idx = system_->field_store->getUnknownIndex(mapping.primary_name); FieldState u_new = primary_unknowns[unknown_idx]; new_states[u_idx] = u_new; @@ -142,27 +97,27 @@ std::pair, std::vector> MultiphysicsTimeI } if (rule->num_args() >= 3 && !mapping.dot_name.empty()) { - size_t v_idx = field_store_->getFieldIndex(mapping.dot_name); + size_t v_idx = system_->field_store->getFieldIndex(mapping.dot_name); rule_inputs.push_back(current_states[v_idx]); } if (rule->num_args() >= 4 && !mapping.ddot_name.empty()) { - size_t a_idx = field_store_->getFieldIndex(mapping.ddot_name); + size_t a_idx = system_->field_store->getFieldIndex(mapping.ddot_name); rule_inputs.push_back(current_states[a_idx]); } if (!mapping.dot_name.empty()) { - size_t v_idx = field_store_->getFieldIndex(mapping.dot_name); + size_t v_idx = system_->field_store->getFieldIndex(mapping.dot_name); new_states[v_idx] = rule->corrected_dot(time_info, rule_inputs); } if (!mapping.ddot_name.empty()) { - size_t a_idx = field_store_->getFieldIndex(mapping.ddot_name); + size_t a_idx = system_->field_store->getFieldIndex(mapping.ddot_name); new_states[a_idx] = rule->corrected_ddot(time_info, rule_inputs); } if (!mapping.history_name.empty()) { - size_t hist_idx = field_store_->getFieldIndex(mapping.history_name); + size_t hist_idx = system_->field_store->getFieldIndex(mapping.history_name); new_states[hist_idx] = u_new; } } @@ -170,30 +125,4 @@ std::pair, std::vector> MultiphysicsTimeI return {new_states, reactions}; } -std::vector solve(const std::vector>& weak_forms, const FieldStore& field_store, - const CoupledSystemSolver* solver, const TimeInfo& time_info, - const std::vector& params) -{ - std::vector weak_form_names; - for (const auto& wf : weak_forms) { - weak_form_names.push_back(wf->name()); - } - std::vector> index_map = field_store.indexMap(weak_form_names); - - std::vector> inputs; - for (size_t i = 0; i < weak_forms.size(); ++i) { - std::string wf_name = weak_forms[i]->name(); - std::vector fields_for_wk = field_store.getStates(wf_name); - inputs.push_back(fields_for_wk); - } - std::vector> wk_params(weak_forms.size(), params); - - std::vector weak_form_ptrs; - for (auto& p : weak_forms) { - weak_form_ptrs.push_back(p.get()); - } - return solver->solve(weak_form_ptrs, index_map, field_store.getShapeDisp(), inputs, wk_params, time_info, - field_store.getBoundaryConditionManagers()); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 71ce34c2c3..2880435a5a 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -14,45 +14,21 @@ #include "smith/differentiable_numerics/time_integration_rule.hpp" #include "smith/physics/mesh.hpp" #include "smith/differentiable_numerics/field_store.hpp" +#include "smith/differentiable_numerics/system_base.hpp" namespace smith { -class CoupledSystemSolver; +class SystemSolver; class DirichletBoundaryConditions; class BoundaryConditionManager; -/** - * @brief Solve a set of weak forms. - * @param weak_forms List of weak forms to solve. - * @param field_store Field store containing the fields. - * @param solver The solver to use. - * @param time_info Current time information. - * @param params Optional parameter fields. - * @return std::vector The updated state fields. - */ -std::vector solve(const std::vector>& weak_forms, const FieldStore& field_store, - const CoupledSystemSolver* solver, const TimeInfo& time_info, - const std::vector& params = {}); - /** * @brief Time integrator for multiphysics problems, coordinating multiple weak forms. */ class MultiphysicsTimeIntegrator : public StateAdvancer { public: - /** - * @brief Construct a new MultiphysicsTimeIntegrator object. - * @param field_store Field store containing the fields. - * @param weak_forms List of weak forms to coordinate. - * @param solver The block solver to use. - * @param cycle_zero_weak_form Optional weak form for initial acceleration solve at cycle 0. - * @param cycle_zero_solver Optional solver paired with `cycle_zero_weak_form` for the cycle-0 solve. - * If null, the integrator falls back to the main solver supplied here. - */ - MultiphysicsTimeIntegrator(std::shared_ptr field_store, - const std::vector>& weak_forms, - std::shared_ptr solver, - std::shared_ptr cycle_zero_weak_form = nullptr, - std::shared_ptr cycle_zero_solver = nullptr); + MultiphysicsTimeIntegrator(std::shared_ptr system, + std::shared_ptr cycle_zero_system = nullptr); /** * @brief Advance the multiphysics state by one time step. @@ -67,11 +43,8 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { const std::vector& params) const override; private: - std::shared_ptr field_store_; - std::vector> weak_forms_; - std::shared_ptr solver_; - std::shared_ptr cycle_zero_weak_form_; - std::shared_ptr cycle_zero_solver_; + std::shared_ptr system_; + std::shared_ptr cycle_zero_system_; }; } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 2eb9bf7208..369cac7988 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -53,39 +53,11 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr - cycle_zero_solid_weak_form; ///< Cycle-zero weak form for initial acceleration solve. + cycle_zero_solid_weak_form; ///< Typed cycle zero solid mechanics weak form. + std::shared_ptr cycle_zero_system; ///< Cycle-zero system for initial acceleration solve. std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. - /** - * @brief Get the list of all state fields (displacement_solve_state, displacement, velocity, acceleration). - * @return std::vector List of state fields. - */ - std::vector getStateFields() const - { - return {field_store->getField(prefix("displacement_solve_state")), field_store->getField(prefix("displacement")), - field_store->getField(prefix("velocity")), field_store->getField(prefix("acceleration"))}; - } - - /** - * @brief Get the list of physical, non-solve state fields. - * @return std::vector List of physical fields suitable for output. - */ - std::vector getOutputFieldStates() const - { - return {field_store->getField(prefix("displacement")), field_store->getField(prefix("velocity")), - field_store->getField(prefix("acceleration"))}; - } - - /** - * @brief Get information about reaction fields for this system. - * @return List of ReactionInfo structures. - */ - std::vector getReactionInfos() const - { - return {{prefix("reactions"), &field_store->getField(prefix("displacement")).get()->space()}}; - } - /** * @brief Create a DifferentiablePhysics object for this system. * @param physics_name The name of the physics. @@ -93,9 +65,9 @@ struct SolidMechanicsSystem : public SystemBase { */ std::unique_ptr createDifferentiablePhysics(std::string physics_name) { - return std::make_unique(field_store->getMesh(), field_store->graph(), - field_store->getShapeDisp(), getStateFields(), getParameterFields(), - advancer, physics_name, getReactionInfos()); + return std::make_unique( + field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), + field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); } /** @@ -300,6 +272,12 @@ struct SolidMechanicsSystem : public SystemBase { } }; +template +struct SolidMechanicsOptions { + std::string prepend_name{}; + std::shared_ptr cycle_zero_solver{}; +}; + /** * @brief Factory function to build a solid dynamics system with configurable time integration. * @tparam dim Spatial dimension. @@ -309,25 +287,23 @@ struct SolidMechanicsSystem : public SystemBase { * @param mesh The mesh. * @param solver The coupled system solver. * @param disp_time_rule The time integration rule. - * @param prepend_name The name of the physics (used as field prefix). - * @param cycle_zero_solver Optional override for the cycle-zero solve. Defaults to - * `solver->singleBlockSolver(0)`. + * @param options Options for system creation. * @param parameter_types Parameter field types. * @return SolidMechanicsSystem with all components initialized. */ template -SolidMechanicsSystem buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_time_rule, - std::string prepend_name = "", std::shared_ptr cycle_zero_solver = nullptr, +std::shared_ptr> buildSolidMechanicsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_time_rule, + SolidMechanicsOptions options, FieldType... parameter_types) { auto field_store = std::make_shared(mesh, 100); auto prefix = [&](const std::string& name) { - if (prepend_name.empty()) { + if (options.prepend_name.empty()) { return name; } - return prepend_name + "_" + name; + return options.prepend_name + "_" + name; }; // Add shape displacement @@ -345,76 +321,85 @@ SolidMechanicsSystem build auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, prefix("acceleration")); // Add parameters - std::vector parameter_fields; (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); - (parameter_fields.push_back(field_store->getField(prefix("param_" + parameter_types.name))), ...); using SystemType = SolidMechanicsSystem; + auto sys = std::make_shared(); + sys->field_store = field_store; + sys->solver = solver; + sys->disp_bc = disp_bc; + sys->disp_time_rule = disp_time_rule_ptr; // Create solid mechanics weak form (u, u_old, v_old, a_old) std::string force_name = prefix("solid_force"); - auto solid_weak_form = std::make_shared( + sys->solid_weak_form = std::make_shared( force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, FieldType(prefix("param_" + parameter_types.name))...)); - // Create cycle-zero weak form (u, v, a) for initial acceleration solve — 3 states, no u_old - std::string cycle_zero_name = prefix("solid_reaction"); - auto cycle_zero_solid_weak_form = std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + sys->weak_forms = {sys->solid_weak_form}; + + if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { + std::string cycle_zero_name = prefix("solid_reaction"); + sys->cycle_zero_solid_weak_form = std::make_shared( + cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, + FieldType(prefix("param_" + parameter_types.name))...)); + + auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); + SLIC_ERROR_IF(cz_solver == nullptr, + "Could not derive a cycle-zero solver for block 0 from the provided solid mechanics solver."); - if (cycle_zero_solver == nullptr) { - cycle_zero_solver = solver->singleBlockSolver(0); + sys->cycle_zero_system = std::make_shared(); + sys->cycle_zero_system->field_store = field_store; + sys->cycle_zero_system->solver = cz_solver; + sys->cycle_zero_system->weak_forms = {sys->cycle_zero_solid_weak_form}; } - SLIC_ERROR_IF(cycle_zero_solver == nullptr, - "Could not derive a cycle-zero solver for block 0 from the provided solid mechanics solver."); - - // Build advancer - std::vector> weak_forms{solid_weak_form}; - auto advancer = std::make_shared(field_store, weak_forms, solver, - cycle_zero_solid_weak_form, cycle_zero_solver); - - return SystemType{{field_store, solver, advancer, parameter_fields, prepend_name}, - solid_weak_form, - cycle_zero_solid_weak_form, - disp_bc, - disp_time_rule_ptr}; + + sys->advancer = std::make_shared(sys, sys->cycle_zero_system); + + return sys; } /** - * @brief Factory function to build a solid dynamics system with a physics name and parameter fields. + * @brief Factory function to build a solid mechanics system with a physics name and parameter fields. */ template -SolidMechanicsSystem buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_time_rule, - std::string prepend_name, FieldType... parameter_types) +std::shared_ptr> buildSolidMechanicsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, + const std::string& prepend_name, FieldType... parameter_types) { - return buildSolidMechanicsSystem(mesh, solver, disp_time_rule, std::move(prepend_name), nullptr, - parameter_types...); + SolidMechanicsOptions opts; + opts.prepend_name = prepend_name; + return buildSolidMechanicsSystem(mesh, solver, disp_rule, opts, + parameter_types...); } /** - * @brief Factory function to build a solid dynamics system (without physics name). + * @brief Factory function to build a solid mechanics system (without physics name). */ template -SolidMechanicsSystem buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_time_rule, - std::shared_ptr cycle_zero_solver, FieldType... parameter_types) +std::shared_ptr> buildSolidMechanicsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, + std::shared_ptr cycle_zero_solver, FieldType... parameter_types) { - return buildSolidMechanicsSystem(mesh, solver, disp_time_rule, "", cycle_zero_solver, parameter_types...); + SolidMechanicsOptions opts; + opts.cycle_zero_solver = std::move(cycle_zero_solver); + return buildSolidMechanicsSystem(mesh, solver, disp_rule, opts, + parameter_types...); } /** - * @brief Factory function to build a solid dynamics system (without physics name). + * @brief Factory function to build a solid mechanics system (without physics name or specific cycle-zero solver). */ template -SolidMechanicsSystem buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_time_rule, +std::shared_ptr> buildSolidMechanicsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, FieldType... parameter_types) { - return buildSolidMechanicsSystem(mesh, solver, disp_time_rule, "", nullptr, parameter_types...); + return buildSolidMechanicsSystem( + mesh, solver, disp_rule, SolidMechanicsOptions{}, + parameter_types...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 2e52f7fea0..86432fc8bd 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -71,47 +71,15 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { dim, H1, Parameters, H1, H1, StateSpace, parameter_space...>>; - std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. - std::shared_ptr state_weak_form; ///< Internal variable weak form. - std::shared_ptr cycle_zero_solid_weak_form; ///< Cycle-zero weak form. - std::shared_ptr disp_bc; ///< Displacement boundary conditions. - std::shared_ptr state_bc; ///< Internal variable boundary conditions. - std::shared_ptr disp_time_rule; ///< Time integration for displacement. - std::shared_ptr state_time_rule; ///< Time integration for internal variable. - - /** - * @brief Get the list of all state fields (disp_pred, disp, vel, accel, state_pred, state). - * @return std::vector List of state fields. - */ - std::vector getStateFields() const - { - return {field_store->getField(prefix("displacement_solve_state")), - field_store->getField(prefix("displacement")), - field_store->getField(prefix("velocity")), - field_store->getField(prefix("acceleration")), - field_store->getField(prefix("state_solve_state")), - field_store->getField(prefix("state"))}; - } - - /** - * @brief Get the list of physical, non-solve state fields. - * @return std::vector List of physical fields suitable for output. - */ - std::vector getOutputFieldStates() const - { - return {field_store->getField(prefix("displacement")), field_store->getField(prefix("velocity")), - field_store->getField(prefix("acceleration")), field_store->getField(prefix("state"))}; - } - - /** - * @brief Get information about reaction fields for this system. - * @return List of ReactionInfo structures. - */ - std::vector getReactionInfos() const - { - return {{prefix("solid_residual"), &field_store->getField(prefix("displacement")).get()->space()}, - {prefix("state_residual"), &field_store->getField(prefix("state")).get()->space()}}; - } + std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. + std::shared_ptr state_weak_form; ///< Internal variable weak form. + std::shared_ptr + cycle_zero_solid_weak_form; ///< Typed cycle zero solid mechanics weak form. + std::shared_ptr cycle_zero_system; ///< Cycle-zero system. + std::shared_ptr disp_bc; ///< Displacement boundary conditions. + std::shared_ptr state_bc; ///< Internal variable boundary conditions. + std::shared_ptr disp_time_rule; ///< Time integration for displacement. + std::shared_ptr state_time_rule; ///< Time integration for internal variable. /** * @brief Create a DifferentiablePhysics object for this system. @@ -120,9 +88,9 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { */ std::unique_ptr createDifferentiablePhysics(std::string physics_name) { - return std::make_unique(field_store->getMesh(), field_store->graph(), - field_store->getShapeDisp(), getStateFields(), getParameterFields(), - advancer, physics_name, getReactionInfos()); + return std::make_unique( + field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), + field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); } /** @@ -368,34 +336,41 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { } }; +template +struct SolidMechanicsWithInternalVarsOptions { + std::string prepend_name{}; + std::shared_ptr cycle_zero_solver{}; +}; + /** * @brief Factory function to build a solid mechanics system with internal variable. * @param mesh The mesh. * @param solver The coupled system solver. * @param disp_rule The displacement time integration rule. * @param state_rule The internal-variable time integration rule. - * @param prepend_name Optional field-name prefix. - * @param cycle_zero_solver Optional override for the cycle-zero solve. Defaults to - * `solver->singleBlockSolver(0)`. + * @param options System creation options. * @param parameter_types Optional parameter field descriptors. */ template -SolidMechanicsWithInternalVarsSystem -buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, InternalVarTimeRule state_rule, - std::string prepend_name = "", - std::shared_ptr cycle_zero_solver = nullptr, - FieldType... parameter_types) +std::shared_ptr> +buildSolidMechanicsWithInternalVarsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, + InternalVarTimeRule state_rule, + SolidMechanicsWithInternalVarsOptions + options, + FieldType... parameter_types) { auto field_store = std::make_shared(mesh, 100); auto prefix = [&](const std::string& name) { - if (prepend_name.empty()) { + if (options.prepend_name.empty()) { return name; } - return prepend_name + "_" + name; + return options.prepend_name + "_" + name; }; // Add shape displacement @@ -417,16 +392,21 @@ buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::share auto state_old_type = field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, prefix("state")); // 3. Parameters - std::vector parameter_fields; (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); - (parameter_fields.push_back(field_store->getField(prefix("param_" + parameter_types.name))), ...); using SystemType = SolidMechanicsWithInternalVarsSystem; + auto sys = std::make_shared(); + sys->field_store = field_store; + sys->solver = solver; + sys->disp_bc = disp_bc; + sys->state_bc = state_bc; + sys->disp_time_rule = disp_time_rule_ptr; + sys->state_time_rule = state_time_rule_ptr; // 4. Solid weak form: residual for u (inputs: u, u_old, v_old, a_old, alpha, alpha_old, params...) std::string solid_res_name = prefix("solid_residual"); - auto solid_weak_form = std::make_shared( + sys->solid_weak_form = std::make_shared( solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, state_type, state_old_type, @@ -434,53 +414,98 @@ buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::share // 5. State weak form: residual for alpha (inputs: alpha, alpha_old, u, u_old, v_old, a_old, params...) std::string state_res_name = prefix("state_residual"); - auto state_weak_form = std::make_shared( + sys->state_weak_form = std::make_shared( state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, disp_old_type, velo_old_type, accel_old_type, FieldType(prefix("param_" + parameter_types.name))...)); - // 6. Cycle-zero weak form: solve for acceleration (inputs: u, v, a, alpha, params...) - std::string cycle_zero_name = prefix("solid_reaction"); - auto cycle_zero_solid_weak_form = std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - state_type, FieldType(prefix("param_" + parameter_types.name))...)); - - if (cycle_zero_solver == nullptr) { - cycle_zero_solver = solver->singleBlockSolver(0); + sys->weak_forms = {sys->solid_weak_form, sys->state_weak_form}; + + if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { + // 6. Cycle-zero weak form: solve for acceleration (inputs: u, v, a, alpha, params...) + std::string cycle_zero_name = prefix("solid_reaction"); + sys->cycle_zero_solid_weak_form = std::make_shared( + cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, + state_type, FieldType(prefix("param_" + parameter_types.name))...)); + + auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); + SLIC_ERROR_IF(cz_solver == nullptr, + "Could not derive a cycle-zero solver for block 0 from the provided internal-vars solid mechanics " + "solver."); + + sys->cycle_zero_system = std::make_shared(); + sys->cycle_zero_system->field_store = field_store; + sys->cycle_zero_system->solver = cz_solver; + sys->cycle_zero_system->weak_forms = {sys->cycle_zero_solid_weak_form}; } - SLIC_ERROR_IF(cycle_zero_solver == nullptr, - "Could not derive a cycle-zero solver for block 0 from the provided internal-vars solid mechanics " - "solver."); - - // Solver and Advancer - std::vector> weak_forms{solid_weak_form, state_weak_form}; - auto advancer = std::make_shared(field_store, weak_forms, solver, - cycle_zero_solid_weak_form, cycle_zero_solver); - - return SystemType{{field_store, solver, advancer, parameter_fields, prepend_name}, - solid_weak_form, - state_weak_form, - cycle_zero_solid_weak_form, - disp_bc, - state_bc, - disp_time_rule_ptr, - state_time_rule_ptr}; + + sys->advancer = std::make_shared(sys, sys->cycle_zero_system); + + return sys; } /** - * @brief Factory function to build a solid mechanics with internal vars system (without physics name). + * @brief Factory function to build a solid mechanics system with internal variables, physics name and parameters. */ -template -auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, InternalVarTimeRule state_rule, - std::shared_ptr cycle_zero_solver = nullptr, - FieldType... parameter_types) +std::shared_ptr> +buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_rule, InternalVarTimeRule isv_rule, + const std::string& prepend_name, + FieldType... parameter_types) +{ + SolidMechanicsWithInternalVarsOptions + opts; + opts.prepend_name = prepend_name; + return buildSolidMechanicsWithInternalVarsSystem(mesh, solver, disp_rule, isv_rule, opts, + parameter_types...); +} + +/** + * @brief Factory function to build a solid mechanics system with internal variables (without physics name). + */ +template +std::shared_ptr> +buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_rule, InternalVarTimeRule isv_rule, + std::shared_ptr cycle_zero_solver, + FieldType... parameter_types) +{ + SolidMechanicsWithInternalVarsOptions + opts; + opts.cycle_zero_solver = std::move(cycle_zero_solver); + return buildSolidMechanicsWithInternalVarsSystem(mesh, solver, disp_rule, isv_rule, opts, + parameter_types...); +} + +/** + * @brief Factory function index to build a solid mechanics system with internal variables (without physics name or + * specific cz solver). + */ +template +std::shared_ptr> +buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_rule, InternalVarTimeRule isv_rule, + FieldType... parameter_types) { - return buildSolidMechanicsWithInternalVarsSystem(mesh, solver, disp_rule, state_rule, "", - cycle_zero_solver, parameter_types...); + return buildSolidMechanicsWithInternalVarsSystem( + mesh, solver, disp_rule, isv_rule, + SolidMechanicsWithInternalVarsOptions{}, + parameter_types...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/system_base.cpp b/src/smith/differentiable_numerics/system_base.cpp new file mode 100644 index 0000000000..66e7b70884 --- /dev/null +++ b/src/smith/differentiable_numerics/system_base.cpp @@ -0,0 +1,53 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/reaction.hpp" +#include "smith/differentiable_numerics/nonlinear_solve.hpp" + +namespace smith { + +std::vector SystemBase::solve(const TimeInfo& time_info) const +{ + std::vector weak_form_names; + for (const auto& wf : weak_forms) { + weak_form_names.push_back(wf->name()); + } + std::vector> index_map = field_store->indexMap(weak_form_names); + + std::vector> inputs; + for (size_t i = 0; i < weak_forms.size(); ++i) { + std::string wf_name = weak_forms[i]->name(); + std::vector fields_for_wk = field_store->getStates(wf_name); + inputs.push_back(fields_for_wk); + } + auto params = field_store->getParameterFields(); + std::vector> wk_params(weak_forms.size(), params); + + std::vector weak_form_ptrs; + for (auto& p : weak_forms) { + weak_form_ptrs.push_back(p.get()); + } + return solver->solve(weak_form_ptrs, index_map, field_store->getShapeDisp(), inputs, wk_params, time_info, + field_store->getBoundaryConditionManagers()); +} + +std::vector SystemBase::computeReactions(const TimeInfo& time_info, + const std::vector& states_for_reactions) const +{ + std::vector reactions; + auto params = field_store->getParameterFields(); + for (const auto& wf : weak_forms) { + std::vector wf_fields = field_store->getStatesFromVectors(wf->name(), states_for_reactions, params); + std::string test_field_name = field_store->getWeakFormReaction(wf->name()); + size_t test_field_idx = field_store->getFieldIndex(test_field_name); + FieldState test_field = states_for_reactions[test_field_idx]; + reactions.push_back(smith::evaluateWeakForm(wf, time_info, field_store->getShapeDisp(), wf_fields, test_field)); + } + return reactions; +} + +} // namespace smith diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index c6b572a450..018808acdc 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -17,7 +17,7 @@ #include #include "field_state.hpp" #include "field_store.hpp" -#include "coupled_system_solver.hpp" +#include "system_solver.hpp" #include "state_advancer.hpp" #include "smith/physics/common.hpp" #include "mfem.hpp" @@ -27,14 +27,6 @@ namespace smith { template struct Parameters; -/** - * @brief Information about a dual field. - */ -struct ReactionInfo { - std::string name; ///< The name of the dual field. - const mfem::ParFiniteElementSpace* space = nullptr; ///< The finite element space of the dual field. -}; - namespace detail { /// @brief Helper: given an index and a type, always produces the type (used to repeat a type N times via pack @@ -58,33 +50,32 @@ using TimeRuleParams = * @brief Base struct for physics systems containing common members and helper functions. */ struct SystemBase { - std::shared_ptr field_store; ///< Field store managing the system's fields. - std::shared_ptr solver; ///< The solver for the system. - std::shared_ptr advancer; ///< The state advancer. - std::vector parameter_fields; ///< Optional parameter fields. - std::string prepend_name; ///< Optional prepended name for all fields. + // --- equations --- + std::vector> weak_forms; + + // --- infrastructure --- + std::shared_ptr field_store; ///< Field store managing the system's fields. + std::shared_ptr solver; ///< The solver for the system. + std::shared_ptr advancer; ///< The state advancer. + + SystemBase() = default; + virtual ~SystemBase() = default; /** - * @brief Get the list of all parameter fields. - * @return const std::vector& List of parameter fields. + * @brief Solve the system using the internal weak_forms and solver. + * @param time_info Current time information. + * @return std::vector The updated state fields from the solver. */ - const std::vector& getParameterFields() const { return parameter_fields; } + virtual std::vector solve(const TimeInfo& time_info) const; /** - * @brief Helper function to prepend the physics name to a string. - * @param name The name to prepend to. - * @return std::string The prepended name. + * @brief Compute reactions after solving the main state. + * @param time_info Current time information. + * @param states_for_reactions The fields configured for reaction computation. + * @return std::vector Computed reactions across all weak_forms. */ - std::string prefix(const std::string& name) const - { - if (prepend_name.empty()) { - return name; - } - return prepend_name + "_" + name; - } - - /// @brief Metadata for dual outputs exported by this system. - std::vector getReactionInfos() const { return {}; } + virtual std::vector computeReactions(const TimeInfo& time_info, + const std::vector& states_for_reactions) const; }; } // namespace smith diff --git a/src/smith/differentiable_numerics/coupled_system_solver.cpp b/src/smith/differentiable_numerics/system_solver.cpp similarity index 85% rename from src/smith/differentiable_numerics/coupled_system_solver.cpp rename to src/smith/differentiable_numerics/system_solver.cpp index 85599fa745..11d5c95ad7 100644 --- a/src/smith/differentiable_numerics/coupled_system_solver.cpp +++ b/src/smith/differentiable_numerics/system_solver.cpp @@ -4,7 +4,7 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -#include "smith/differentiable_numerics/coupled_system_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/nonlinear_solve.hpp" #include "smith/physics/weak_form.hpp" @@ -18,35 +18,37 @@ namespace smith { -CoupledSystemSolver::CoupledSystemSolver(std::shared_ptr single_solver) +SystemSolver::SystemSolver(std::shared_ptr single_solver) : max_staggered_iterations_(1), exact_staggered_steps_(false) { addSubsystemSolver({}, std::move(single_solver)); } -CoupledSystemSolver::CoupledSystemSolver(int max_staggered_iterations, bool exact_staggered_steps) +SystemSolver::SystemSolver(int max_staggered_iterations, bool exact_staggered_steps) : max_staggered_iterations_(max_staggered_iterations), exact_staggered_steps_(exact_staggered_steps) { SLIC_ERROR_IF(max_staggered_iterations <= 0, "max_staggered_iterations must be > 0"); } -void CoupledSystemSolver::addSubsystemSolver(const std::vector& block_indices, - std::shared_ptr solver, double relaxation_factor) +void SystemSolver::addSubsystemSolver(const std::vector& block_indices, + std::shared_ptr solver, double relaxation_factor) { - SLIC_ERROR_IF(!solver, "CoupledSystemSolver stage solver must be non-null"); + SLIC_ERROR_IF(!solver, "SystemSolver stage solver must be non-null"); SLIC_ERROR_IF(relaxation_factor <= 0.0 || relaxation_factor > 1.0, axom::fmt::format("Stage relaxation_factor {} must be in (0, 1]", relaxation_factor)); stages_.push_back(Stage{block_indices, std::move(solver), relaxation_factor}); } -std::vector CoupledSystemSolver::solve( - const std::vector& residual_evals, const std::vector>& block_indices, - const FieldState& shape_disp, const std::vector>& states, - const std::vector>& params, const TimeInfo& time_info, - const std::vector& bc_managers) const +std::vector SystemSolver::solve(const std::vector& residual_evals, + const std::vector>& block_indices, + const FieldState& shape_disp, + const std::vector>& states, + const std::vector>& params, + const TimeInfo& time_info, + const std::vector& bc_managers) const { - SLIC_ERROR_IF(stages_.empty(), "CoupledSystemSolver has no stages defined."); + SLIC_ERROR_IF(stages_.empty(), "SystemSolver has no stages defined."); size_t num_residuals = residual_evals.size(); std::vector active_stages = stages_; @@ -188,12 +190,12 @@ std::vector CoupledSystemSolver::solve( return final_solutions; } -std::shared_ptr CoupledSystemSolver::singleBlockSolver(size_t block_index) const +std::shared_ptr SystemSolver::singleBlockSolver(size_t block_index) const { constexpr bool exact_staggered_steps = true; for (const auto& stage : stages_) { if (stage.block_indices.empty()) { - auto result = std::make_shared(1, exact_staggered_steps); + auto result = std::make_shared(1, exact_staggered_steps); std::shared_ptr stage_solver = stage.solver; if (const auto* equation_solver = dynamic_cast(stage.solver.get())) { if (auto cloned_solver = equation_solver->cloneFresh()) { @@ -207,7 +209,7 @@ std::shared_ptr CoupledSystemSolver::singleBlockSolver(size auto found = std::find(stage.block_indices.begin(), stage.block_indices.end(), block_index); if (found != stage.block_indices.end()) { - auto result = std::make_shared(1, exact_staggered_steps); + auto result = std::make_shared(1, exact_staggered_steps); std::shared_ptr stage_solver = stage.solver; if (const auto* equation_solver = dynamic_cast(stage.solver.get())) { if (auto cloned_solver = equation_solver->cloneFresh()) { diff --git a/src/smith/differentiable_numerics/coupled_system_solver.hpp b/src/smith/differentiable_numerics/system_solver.hpp similarity index 89% rename from src/smith/differentiable_numerics/coupled_system_solver.hpp rename to src/smith/differentiable_numerics/system_solver.hpp index d0d6914f52..76ccd93d0d 100644 --- a/src/smith/differentiable_numerics/coupled_system_solver.hpp +++ b/src/smith/differentiable_numerics/system_solver.hpp @@ -20,7 +20,7 @@ class NonlinearBlockSolverBase; class BoundaryConditionManager; /// @brief Orchestrates staggered solution for multiphysics systems. -class CoupledSystemSolver { +class SystemSolver { public: /// @brief Represents a single stage in a staggered iteration. struct Stage { @@ -31,18 +31,18 @@ class CoupledSystemSolver { ///< A value of 1.0 (default) means no relaxation (full update). }; - /// @brief Construct a monolithic CoupledSystemSolver from a single block solver. + /// @brief Construct a monolithic SystemSolver from a single block solver. /// @param single_solver The solver to use for all blocks simultaneously. - CoupledSystemSolver(std::shared_ptr single_solver); + SystemSolver(std::shared_ptr single_solver); - /// @brief Construct a CoupledSystemSolver for staggered iteration. + /// @brief Construct a SystemSolver for staggered iteration. /// @param max_staggered_iterations Maximum number of staggered sweeps across all stages. When /// @p exact_staggered_steps is false, the solver may exit early once all stage solvers /// report convergence. /// @param exact_staggered_steps If true, always perform exactly @p max_staggered_iterations /// sweeps with no early-exit convergence check. Useful when a fixed number of /// partitioned-stagger steps is required regardless of residual level. - CoupledSystemSolver(int max_staggered_iterations, bool exact_staggered_steps = false); + SystemSolver(int max_staggered_iterations, bool exact_staggered_steps = false); /// @brief Convenience method to add a solver stage. /// @param block_indices Indices of the blocks to solve. @@ -68,7 +68,7 @@ class CoupledSystemSolver { /// @brief Build a single-block solver from the stage responsible for @p block_index. /// Prefers constructing a fresh solver instance when the underlying stage solver retains rebuildable config. - std::shared_ptr singleBlockSolver(size_t block_index) const; + std::shared_ptr singleBlockSolver(size_t block_index) const; private: int max_staggered_iterations_; ///< Maximum number of staggered iterations. diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 6d8e7d2b2b..5a1f232cdb 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -16,7 +16,7 @@ #include "smith/physics/mesh.hpp" #include "smith/physics/state/state_manager.hpp" -#include "smith/differentiable_numerics/coupled_system_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" #include "smith/differentiable_numerics/field_store.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" @@ -161,16 +161,25 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroUsesBcsForReactionFieldNotUnknownZero) auto displacement_wf = buildScalarDiffusionWeakForm("displacement_main", mesh, field_store, displacement_type); auto cycle_zero_wf = buildScalarDiffusionWeakForm("cycle_zero_displacement", mesh, field_store, displacement_type); - auto main_solver = std::make_shared(std::make_shared()); + auto main_solver = std::make_shared(std::make_shared()); LinearSolverOptions lin_opts{.linear_solver = LinearSolver::SuperLU}; NonlinearSolverOptions nonlin_opts{ .nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1.0e-12, .absolute_tol = 1.0e-12, .max_iterations = 8}; auto cycle_zero_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh); - auto cycle_zero_solver = std::make_shared(cycle_zero_block_solver); + auto cycle_zero_solver = std::make_shared(cycle_zero_block_solver); - MultiphysicsTimeIntegrator advancer(field_store, {temperature_wf, displacement_wf}, main_solver, cycle_zero_wf, - cycle_zero_solver); + auto main_system = std::make_shared(); + main_system->field_store = field_store; + main_system->weak_forms = {temperature_wf, displacement_wf}; + main_system->solver = main_solver; + + auto cz_system = std::make_shared(); + cz_system->field_store = field_store; + cz_system->weak_forms = {cycle_zero_wf}; + cz_system->solver = cycle_zero_solver; + + MultiphysicsTimeIntegrator advancer(main_system, cz_system); auto [new_states, reactions] = advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); @@ -211,11 +220,21 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) auto cycle_zero_wf = buildSecondOrderCycleZeroWeakForm("cycle_zero_acceleration", mesh, field_store, displacement_type, velocity_type, acceleration_type); - auto main_solver = std::make_shared(std::make_shared()); + auto main_solver = std::make_shared(std::make_shared()); auto cycle_zero_block_solver = std::make_shared(); - auto cycle_zero_solver = std::make_shared(cycle_zero_block_solver); + auto cycle_zero_solver = std::make_shared(cycle_zero_block_solver); + + auto main_system = std::make_shared(); + main_system->field_store = field_store; + main_system->weak_forms = {main_wf}; + main_system->solver = main_solver; + + auto cz_system = std::make_shared(); + cz_system->field_store = field_store; + cz_system->weak_forms = {cycle_zero_wf}; + cz_system->solver = cycle_zero_solver; - MultiphysicsTimeIntegrator advancer(field_store, {main_wf}, main_solver, cycle_zero_wf, cycle_zero_solver); + MultiphysicsTimeIntegrator advancer(main_system, cz_system); auto [new_states, reactions] = advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); @@ -227,7 +246,7 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) StateManager::reset(); } -TEST(CoupledSystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) +TEST(SystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) { axom::sidre::DataStore datastore; StateManager::initialize(datastore, "coupled_system_solver_single_block_characterization"); @@ -248,7 +267,7 @@ TEST(CoupledSystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequested auto displacement_wf = buildScalarDiffusionWeakForm("displacement_main", mesh, field_store, displacement_type); auto recording_solver = std::make_shared(); - auto monolithic_solver = std::make_shared(recording_solver); + auto monolithic_solver = std::make_shared(recording_solver); auto derived_single_block_solver = monolithic_solver->singleBlockSolver(0); ASSERT_NE(derived_single_block_solver, nullptr); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index df241483df..2647d44a27 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -7,6 +7,9 @@ #include "gtest/gtest.h" #include "gretl/data_store.hpp" +#include +#include +#include #include "smith/smith_config.hpp" #include "smith/infrastructure/application_manager.hpp" @@ -20,7 +23,7 @@ #include "smith/physics/materials/parameterized_solid_material.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/coupled_system_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" #include "smith/differentiable_numerics/paraview_writer.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" @@ -99,7 +102,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto solid_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto coupled_solver = std::make_shared(solid_block_solver); + auto coupled_solver = std::make_shared(solid_block_solver); auto system = buildSolidMechanicsSystem( mesh, coupled_solver, ImplicitNewmarkSecondOrderTimeIntegrationRule{}, FieldType("bulk"), FieldType("shear")); @@ -114,27 +117,27 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) MaterialType material{.density = 1.0, .K0 = K, .G0 = G}; // Set parameters - auto params = system.getParameterFields(); + auto params = system->field_store->getParameterFields(); params[0].get()->setFromFieldFunction([=](tensor) { return material.K0; }); params[1].get()->setFromFieldFunction([=](tensor) { return material.G0; }); - system.setMaterial(material, mesh->entireBodyName()); + system->setMaterial(material, mesh->entireBodyName()); // Add gravity body force - system.addBodyForce(mesh->entireBodyName(), - [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/, auto... /*params*/) { - tensor b{}; - b[1] = gravity; - return b; - }); + system->addBodyForce(mesh->entireBodyName(), + [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/, auto... /*params*/) { + tensor b{}; + b[1] = gravity; + return b; + }); // Add dummy traction to test compilation - system.addTraction("right", [](double /*time*/, auto /*X*/, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, - auto... /*params*/) { return tensor{}; }); + system->addTraction("right", [](double /*time*/, auto /*X*/, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, + auto... /*params*/) { return tensor{}; }); - auto shape_disp = system.field_store->getShapeDisp(); - auto states = system.getStateFields(); - auto output_states = system.getOutputFieldStates(); + auto shape_disp = system->field_store->getShapeDisp(); + auto states = system->field_store->getStateFields(); + auto output_states = system->field_store->getOutputFieldStates(); std::string pv_dir = "paraview_solid"; auto pv_writer = createParaviewWriter(*mesh, {output_states[0], params[0], params[1]}, pv_dir); @@ -146,8 +149,8 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); - std::tie(states, reactions) = system.advancer->advanceState(t_info, shape_disp, states, params); - output_states = system.getOutputFieldStates(); + std::tie(states, reactions) = system->advancer->advanceState(t_info, shape_disp, states, params); + output_states = system->field_store->getOutputFieldStates(); time += dt_; cycle++; pv_writer.write(m + 1, time, {output_states[0], params[0], params[1]}); @@ -200,13 +203,13 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(solid_block_solver); + auto coupled_solver = std::make_shared(solid_block_solver); auto system = buildSolidMechanicsSystem(mesh, coupled_solver, time_rule, physics_name, FieldType("bulk"), FieldType("shear")); - auto physics = system.createDifferentiablePhysics(physics_name); - auto bcs = system.disp_bc; + auto physics = system->createDifferentiablePhysics(physics_name); + auto bcs = system->disp_bc; bcs->setFixedVectorBCs(mesh->domain("right")); bcs->setVectorBCs(mesh->domain("left"), [](double t, tensor X) { @@ -223,7 +226,7 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptrentireBodyName()); + system->setMaterial(material, mesh->entireBodyName()); auto shape_disp = physics->getShapeDispFieldState(); auto params = physics->getFieldParams(); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 9ada65a4ec..d8e5ddea29 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -103,37 +103,37 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) { auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); + auto coupled_solver = std::make_shared(nonlinear_block_solver); auto system = buildSolidMechanicsWithInternalVarsSystem( mesh, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, "solid_static_with_internal_vars"); // Material and Evolution - system.setMaterial(DamageMaterial{}, mesh->entireBodyName()); - system.addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); + system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); + system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); // Boundary Conditions // Fix bottom face - system.disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + system->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); // Pull top face double pull_rate = 0.05; - system.disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { + system->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { tensor u{}; u[2] = pull_rate * t; return u; }); - auto physics = system.createDifferentiablePhysics("physics"); + auto physics = system->createDifferentiablePhysics("physics"); // Create ParaView writer - auto writer = createParaviewWriter(*mesh, system.getOutputFieldStates(), "solid_state_output"); - writer.write(0, 0.0, system.getOutputFieldStates()); + auto writer = createParaviewWriter(*mesh, system->field_store->getOutputFieldStates(), "solid_state_output"); + writer.write(0, 0.0, system->field_store->getOutputFieldStates()); // Advance multiple steps for (int step = 1; step <= 5; ++step) { physics->advanceTimestep(1.0); - writer.write(step, step * 1.0, system.getOutputFieldStates()); + writer.write(step, step * 1.0, system->field_store->getOutputFieldStates()); SLIC_INFO("Completed step " << step); } } @@ -145,7 +145,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) // Staggered solver: stage 0 solves displacement (block 0), stage 1 solves state (block 1). // Use relaxation_factor = 0.5 on the displacement stage to exercise the relaxation path. - auto staggered_solver = std::make_shared(20); + auto staggered_solver = std::make_shared(20); staggered_solver->addSubsystemSolver({0}, disp_solver, 0.5); staggered_solver->addSubsystemSolver({1}, state_solver, 1.0); @@ -153,18 +153,18 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) mesh, staggered_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, "solid_staggered_relaxation"); - system.setMaterial(DamageMaterial{}, mesh->entireBodyName()); - system.addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); + system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); + system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); - system.disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + system->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); double pull_rate = 0.05; - system.disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { + system->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { tensor u{}; u[2] = pull_rate * t; return u; }); - auto physics = system.createDifferentiablePhysics("physics_relaxed"); + auto physics = system->createDifferentiablePhysics("physics_relaxed"); for (int step = 1; step <= 3; ++step) { physics->advanceTimestep(1.0); SLIC_INFO("Staggered relaxation step " << step << " completed"); @@ -174,20 +174,20 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) { auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); + auto coupled_solver = std::make_shared(nonlinear_block_solver); auto system = buildSolidMechanicsWithInternalVarsSystem( mesh, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, "body_force_test"); - system.setMaterial(DamageMaterial{}, mesh->entireBodyName()); - system.addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); + system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); + system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); // Fix bottom face - system.disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + system->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); // Apply a gravity-like body force in the -z direction double body_force_mag = -0.01; - system.addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { + system->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { tensor f{}; f[2] = body_force_mag; return f; @@ -195,13 +195,13 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) // Apply a traction on the top face in the +z direction double traction_mag = 0.005; - system.addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { + system->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { tensor t{}; t[2] = traction_mag; return t; }); - auto physics = system.createDifferentiablePhysics("physics_bf"); + auto physics = system->createDifferentiablePhysics("physics_bf"); physics->advanceTimestep(1.0); // Check that the displacement field is non-zero (the body force + traction produced deformation) diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 0381cd4f21..66c66809c9 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -9,7 +9,7 @@ #include "smith/differentiable_numerics/thermal_system.hpp" #include "smith/differentiable_numerics/nonlinear_solve.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/coupled_system_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/physics/mesh.hpp" #include "smith/physics/common.hpp" #include "smith/physics/state/state_manager.hpp" @@ -50,36 +50,37 @@ struct ThermalStaticFixture : public testing::Test { auto linear_options = LinearSolverOptions(); auto nonlinear_block_solver = buildNonlinearBlockSolver(solver_options, linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); - auto thermal_system = buildThermalSystem<2, temp_order>(mesh, coupled_solver); + auto coupled_solver = std::make_shared(nonlinear_block_solver); + auto thermal_system = + buildThermalSystem<2, temp_order>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. // heat_flux is the physical flux (Fourier's law): q = -k * grad(T). // The system negates it to form the weak form integral(k * grad(T) . grad(v)). - thermal_system.setMaterial( + thermal_system->setMaterial( [=](auto /*temperature*/, auto grad_temperature) { return smith::tuple{0.0, -k * grad_temperature}; }, "entire_body"); - thermal_system.addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/) { + thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/) { auto x = X[0]; auto y = X[1]; return 2.0 * k * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); }); - thermal_system.temperature_bc->template setScalarBCs<2>(mesh->entireBoundary(), - [](double /*t*/, tensor /*X*/) { return 0.0; }); + thermal_system->temperature_bc->template setScalarBCs<2>(mesh->entireBoundary(), + [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = thermal_system.advancer->advanceState( - t_info, thermal_system.field_store->getShapeDisp(), thermal_system.field_store->getAllFields(), - thermal_system.getParameterFields()); + auto [new_states, reactions] = thermal_system->advancer->advanceState( + t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), + thermal_system->field_store->getParameterFields()); for (size_t i = 0; i < new_states.size(); ++i) { - thermal_system.field_store->setField(i, new_states[i]); + thermal_system->field_store->setField(i, new_states[i]); } - auto temperature = thermal_system.field_store->getField(thermal_system.prefix("temperature")); + auto temperature = thermal_system->field_store->getField("temperature"); auto exact_sol_func = [](const mfem::Vector& X, mfem::Vector& T) { double x = X(0); @@ -137,17 +138,18 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); auto nonlinear_block_solver = buildNonlinearBlockSolver(solver_options, linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); + auto coupled_solver = std::make_shared(nonlinear_block_solver); FieldType> conductivity_param("conductivity"); auto thermal_system = buildThermalSystem<2, 1>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}, "", conductivity_param); // Set the conductivity parameter field to k=1.0 - thermal_system.parameter_fields[0].get()->setFromFieldFunction([](tensor) { return 1.0; }); + thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [](tensor) { return 1.0; }); // Material uses the parameter field for conductivity - thermal_system.setMaterial( + thermal_system->setMaterial( [](auto /*temperature*/, auto grad_temperature, auto k_param) { auto k = get<0>(k_param); return smith::tuple{0.0, -k * grad_temperature}; @@ -156,25 +158,25 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) // Use DependsOn to specify that the heat source depends only on the temperature states (indices 0,1), // not on the parameter field - thermal_system.addHeatSource(DependsOn<0, 1>{}, "entire_body", [=](auto /*t*/, auto X, auto /*T*/) { + thermal_system->addHeatSource(DependsOn<0, 1>{}, "entire_body", [=](auto /*t*/, auto X, auto /*T*/) { auto x = X[0]; auto y = X[1]; return 2.0 * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); }); - thermal_system.temperature_bc->template setScalarBCs<2>(mesh->entireBoundary(), - [](double /*t*/, tensor /*X*/) { return 0.0; }); + thermal_system->temperature_bc->template setScalarBCs<2>(mesh->entireBoundary(), + [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = thermal_system.advancer->advanceState( - t_info, thermal_system.field_store->getShapeDisp(), thermal_system.field_store->getAllFields(), - thermal_system.getParameterFields()); + auto [new_states, reactions] = thermal_system->advancer->advanceState( + t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), + thermal_system->field_store->getParameterFields()); for (size_t i = 0; i < new_states.size(); ++i) { - thermal_system.field_store->setField(i, new_states[i]); + thermal_system->field_store->setField(i, new_states[i]); } - auto temperature = thermal_system.field_store->getField(thermal_system.prefix("temperature")); + auto temperature = thermal_system->field_store->getField("temperature"); auto exact_sol_func = [](const mfem::Vector& X, mfem::Vector& T) { double x = X(0); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp index aa8b95dcf9..3b218dce44 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp @@ -14,7 +14,7 @@ #include "smith/physics/mesh.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/coupled_system_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/thermo_mechanics_system.hpp" #include "smith/differentiable_numerics/paraview_writer.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" @@ -91,18 +91,18 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); FieldType> youngs_modulus("youngs_modulus"); auto system = buildThermoMechanicsSystem( - mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, + mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, "thermo", youngs_modulus); - auto physics = system.createDifferentiablePhysics("thermo_physics"); - const auto& solid_dual_space = physics->dual(system.prefix("solid_force")).space(); - const auto& solid_state_space = physics->state(system.prefix("displacement")).space(); - const auto& thermal_dual_space = physics->dual(system.prefix("thermal_flux")).space(); - const auto& thermal_state_space = physics->state(system.prefix("temperature")).space(); + auto physics = system->createDifferentiablePhysics("thermo_physics"); + const auto& solid_dual_space = physics->dual("thermo_solid_force").space(); + const auto& solid_state_space = physics->state("thermo_displacement").space(); + const auto& thermal_dual_space = physics->dual("thermo_thermal_flux").space(); + const auto& thermal_state_space = physics->state("thermo_temperature").space(); EXPECT_EQ(physics->dualNames().size(), 2); - EXPECT_EQ(physics->dualNames()[0], system.prefix("solid_force")); - EXPECT_EQ(physics->dualNames()[1], system.prefix("thermal_flux")); + EXPECT_EQ(physics->dualNames()[0], "thermo_solid_force"); + EXPECT_EQ(physics->dualNames()[1], "thermo_thermal_flux"); EXPECT_EQ(solid_dual_space.GetMesh(), solid_state_space.GetMesh()); EXPECT_STREQ(solid_dual_space.FEColl()->Name(), solid_state_space.FEColl()->Name()); EXPECT_EQ(solid_dual_space.GetVDim(), solid_state_space.GetVDim()); @@ -124,22 +124,23 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); FieldType> youngs_modulus("youngs_modulus"); auto system = buildThermoMechanicsSystem( - mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, + mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, "thermo", youngs_modulus); GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - system.setMaterial(material, mesh_->entireBodyName()); - system.parameter_fields[0].get()->setFromFieldFunction([=](smith::tensor) { return 100.0; }); - system.disp_bc->setFixedVectorBCs(mesh_->domain("left")); - system.temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + system->setMaterial(material, mesh_->entireBodyName()); + system->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [=](smith::tensor) { return 100.0; }); + system->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + system->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - system.addSolidTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { + system->addSolidTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { auto traction = 0.0 * X; traction[0] = -0.015; return traction; }); - auto physics = system.createDifferentiablePhysics("thermo_physics"); + auto physics = system->createDifferentiablePhysics("thermo_physics"); // Run forward double dt = 1.0; @@ -153,7 +154,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) gretl::set_as_objective(obj); obj.data_store().back_prop(); - auto param_sens = system.getParameterFields()[0].get_dual(); + auto param_sens = system->field_store->getParameterFields()[0].get_dual(); EXPECT_TRUE(param_sens->Norml2() > 0.0); } @@ -163,46 +164,47 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) constexpr double lateral_body_force = 2.5e-5; constexpr double thermal_source = 1.0; - auto run_problem = [&](const std::string& label, std::shared_ptr coupled_solver) { + auto run_problem = [&](const std::string& label, std::shared_ptr coupled_solver) { GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; FieldType> youngs_modulus("youngs_modulus"); auto system = buildThermoMechanicsSystem( mesh_, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, youngs_modulus); - system.setMaterial(material, mesh_->entireBodyName()); - system.parameter_fields[0].get()->setFromFieldFunction([=](smith::tensor) { return 100.0; }); - system.disp_bc->setFixedVectorBCs(mesh_->domain("left")); - system.temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - system.temperature_bc->setFixedScalarBCs(mesh_->domain("right")); - system.addSolidTraction("right", [=](auto, auto X, auto... /*args*/) { + system->setMaterial(material, mesh_->entireBodyName()); + system->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [=](smith::tensor) { return 100.0; }); + system->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + system->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + system->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); + system->addSolidTraction("right", [=](auto, auto X, auto... /*args*/) { auto traction = 0.0 * X; traction[0] = -compressive_traction; return traction; }); - system.addSolidBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto... /*args*/) { + system->addSolidBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto... /*args*/) { auto force = 0.0 * X; force[1] = lateral_body_force; return force; }); - system.addHeatSource(mesh_->entireBodyName(), - [=](auto, auto, auto, auto, auto, auto, auto, auto) { return thermal_source; }); + system->addHeatSource(mesh_->entireBodyName(), + [=](auto, auto, auto, auto, auto, auto, auto, auto) { return thermal_source; }); SLIC_INFO_ROOT("Starting " << label << " thermo-mechanics solve"); double dt = 1.0; double time = 0.0; - auto shape_disp = system.field_store->getShapeDisp(); - auto states = system.getStateFields(); - auto params = system.getParameterFields(); + auto shape_disp = system->field_store->getShapeDisp(); + auto states = system->field_store->getStateFields(); + auto params = system->field_store->getParameterFields(); std::vector reactions; for (size_t step = 0; step < 1; ++step) { std::tie(states, reactions) = - system.advancer->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + system->advancer->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } - return std::make_pair(mfem::Vector(*states[system.field_store->getFieldIndex("displacement_solve_state")].get()), - mfem::Vector(*states[system.field_store->getFieldIndex("temperature_solve_state")].get())); + return std::make_pair(mfem::Vector(*states[system->field_store->getFieldIndex("displacement_solve_state")].get()), + mfem::Vector(*states[system->field_store->getFieldIndex("temperature_solve_state")].get())); }; smith::LinearSolverOptions monolithic_lin_opts{.linear_solver = smith::LinearSolver::GMRES, @@ -223,7 +225,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) .print_level = 2}; auto monolithic_block_solver = buildNonlinearBlockSolver(monolithic_nonlin_opts, monolithic_lin_opts, *mesh_); - auto monolithic_result = run_problem("monolithic", std::make_shared(monolithic_block_solver)); + auto monolithic_result = run_problem("monolithic", std::make_shared(monolithic_block_solver)); bool monolithic_converged = monolithic_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); int monolithic_iterations = monolithic_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); @@ -231,7 +233,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) smith::StateManager::reset(); this->SetUp(); - auto staggered_coupled_solver = std::make_shared(10); + auto staggered_coupled_solver = std::make_shared(10); smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, .preconditioner = smith::Preconditioner::HypreAMG, diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 126491a676..157a90623d 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -49,30 +49,6 @@ struct ThermalSystem : public SystemBase { std::shared_ptr temperature_bc; ///< Temperature boundary conditions. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - /** - * @brief Get the list of all state fields (temperature_solve_state, temperature). - * @return std::vector List of state fields. - */ - std::vector getStateFields() const - { - return {field_store->getField(prefix("temperature_solve_state")), field_store->getField(prefix("temperature"))}; - } - - /** - * @brief Get the list of physical, non-solve state fields. - * @return std::vector List of physical fields suitable for output. - */ - std::vector getOutputFieldStates() const { return {field_store->getField(prefix("temperature"))}; } - - /** - * @brief Get information about reaction fields for this system. - * @return List of ReactionInfo structures. - */ - std::vector getReactionInfos() const - { - return {{prefix("thermal_flux"), &field_store->getField(prefix("temperature")).get()->space()}}; - } - /** * @brief Create a DifferentiablePhysics object for this system. * @param physics_name The name of the physics. @@ -80,9 +56,9 @@ struct ThermalSystem : public SystemBase { */ std::unique_ptr createDifferentiablePhysics(std::string physics_name) { - return std::make_unique(field_store->getMesh(), field_store->graph(), - field_store->getShapeDisp(), getStateFields(), getParameterFields(), - advancer, physics_name, getReactionInfos()); + return std::make_unique( + field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), + field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); } /** @@ -186,6 +162,11 @@ struct ThermalSystem : public SystemBase { } }; +template +struct ThermalOptions { + std::string prepend_name{}; +}; + /** * @brief Factory function to build a thermal system. * @tparam dim Spatial dimension. @@ -195,22 +176,23 @@ struct ThermalSystem : public SystemBase { * @param mesh The mesh. * @param solver The coupled system solver. * @param temp_rule The time integration rule for temperature. - * @param prepend_name The name of the physics (used as field prefix). + * @param options System creation options. * @param parameter_types Parameter field types. * @return ThermalSystem with all components initialized. */ template -ThermalSystem buildThermalSystem( - std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_rule, - std::string prepend_name = "", FieldType... parameter_types) +std::shared_ptr> buildThermalSystem( + std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_rule, + ThermalOptions options, + FieldType... parameter_types) { auto field_store = std::make_shared(mesh, 100); auto prefix = [&](const std::string& name) { - if (prepend_name.empty()) { + if (options.prepend_name.empty()) { return name; } - return prepend_name + "_" + name; + return options.prepend_name + "_" + name; }; FieldType> shape_disp_type(prefix("shape_displacement")); @@ -222,36 +204,61 @@ ThermalSystem buildThe auto temperature_old_type = field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, prefix("temperature")); - std::vector parameter_fields; (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); - (parameter_fields.push_back(field_store->getField(prefix("param_" + parameter_types.name))), ...); + + using SystemType = ThermalSystem; + auto sys = std::make_shared(); + sys->field_store = field_store; + sys->solver = solver; + sys->temperature_bc = temperature_bc; + sys->temperature_time_rule = temperature_time_rule; std::string thermal_flux_name = prefix("thermal_flux"); - auto thermal_weak_form = std::make_shared< - typename ThermalSystem::ThermalWeakFormType>( + sys->thermal_weak_form = std::make_shared( thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, FieldType(prefix("param_" + parameter_types.name))...)); - std::vector> weak_forms{thermal_weak_form}; - auto advancer = std::make_shared(field_store, weak_forms, solver); + sys->weak_forms = {sys->thermal_weak_form}; - return ThermalSystem{ - {field_store, solver, advancer, parameter_fields, prepend_name}, - thermal_weak_form, - temperature_bc, - temperature_time_rule}; + sys->advancer = std::make_shared(sys, nullptr); + + return sys; } /** - * @brief Factory function to build a thermal system with default quasi-static rule (backward compatible). + * @brief Factory function to build a thermal system with default quasi-static rule. */ template -auto buildThermalSystem(std::shared_ptr mesh, std::shared_ptr solver, - std::string prepend_name = "", FieldType... parameter_types) +std::shared_ptr> +buildThermalSystem( + std::shared_ptr mesh, std::shared_ptr solver, + ThermalOptions options, + FieldType... parameter_types) +{ + return buildThermalSystem( + mesh, solver, QuasiStaticFirstOrderTimeIntegrationRule{}, options, parameter_types...); +} + +template +std::shared_ptr> buildThermalSystem( + std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_time_rule, + const std::string& prepend_name, FieldType... parameter_types) +{ + ThermalOptions opts; + opts.prepend_name = prepend_name; + return buildThermalSystem(mesh, solver, temp_time_rule, + opts, parameter_types...); +} + +template +std::shared_ptr> buildThermalSystem( + std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_time_rule, + FieldType... parameter_types) { - return buildThermalSystem(mesh, solver, QuasiStaticFirstOrderTimeIntegrationRule{}, prepend_name, - parameter_types...); + return buildThermalSystem( + mesh, solver, temp_time_rule, ThermalOptions{}, + parameter_types...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index 49b83f8ffd..30f61eacb7 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -66,36 +66,13 @@ struct ThermoMechanicsSystem : public SystemBase { std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr thermal_weak_form; ///< Thermal weak form. - std::shared_ptr cycle_zero_weak_form; ///< Cycle-zero weak form. + std::shared_ptr cycle_zero_weak_form; ///< Typed cycle zero weak form. + std::shared_ptr cycle_zero_system; ///< Cycle-zero system. std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr temperature_bc; ///< Temperature boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration for displacement. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - /** - * @brief Get the list of all state fields (disp_pred, disp, vel, accel, temp_pred, temp). - * @return std::vector List of state fields. - */ - std::vector getStateFields() const - { - return {field_store->getField(prefix("displacement_solve_state")), - field_store->getField(prefix("displacement")), - field_store->getField(prefix("velocity")), - field_store->getField(prefix("acceleration")), - field_store->getField(prefix("temperature_solve_state")), - field_store->getField(prefix("temperature"))}; - } - - /** - * @brief Get information about reaction fields for this system. - * @return List of ReactionInfo structures. - */ - std::vector getReactionInfos() const - { - return {{prefix("solid_force"), &field_store->getField(prefix("displacement")).get()->space()}, - {prefix("thermal_flux"), &field_store->getField(prefix("temperature")).get()->space()}}; - } - /** * @brief Create a DifferentiablePhysics object for this system. * @param physics_name The name of the physics. @@ -103,9 +80,9 @@ struct ThermoMechanicsSystem : public SystemBase { */ std::unique_ptr createDifferentiablePhysics(std::string physics_name) { - return std::make_unique(field_store->getMesh(), field_store->graph(), - field_store->getShapeDisp(), getStateFields(), getParameterFields(), - advancer, physics_name, getReactionInfos()); + return std::make_unique( + field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), + field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); } /** @@ -359,32 +336,40 @@ struct ThermoMechanicsSystem : public SystemBase { } }; +template +struct ThermoMechanicsOptions { + std::string prepend_name{}; + std::shared_ptr cycle_zero_solver{}; +}; + /** * @brief Factory function to build a thermo-mechanical system. * @param mesh The mesh. * @param solver The coupled system solver. * @param disp_rule The displacement time integration rule. * @param temp_rule The temperature time integration rule. - * @param prepend_name Optional field-name prefix. - * @param cycle_zero_solver Optional override for the cycle-zero solve. Defaults to - * `solver->singleBlockSolver(0)`. + * @param options Options defining fields prepends or sub-solvers. * @param parameter_types Optional parameter field descriptors. */ template -ThermoMechanicsSystem -buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, std::string prepend_name = "", - std::shared_ptr cycle_zero_solver = nullptr, - FieldType... parameter_types) +std::shared_ptr< + ThermoMechanicsSystem> +buildThermoMechanicsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, + TemperatureTimeRule temp_rule, + ThermoMechanicsOptions + options, + FieldType... parameter_types) { auto field_store = std::make_shared(mesh, 100); auto prefix = [&](const std::string& name) { - if (prepend_name.empty()) { + if (options.prepend_name.empty()) { return name; } - return prepend_name + "_" + name; + return options.prepend_name + "_" + name; }; FieldType> shape_disp_type(prefix("shape_displacement")); @@ -405,16 +390,21 @@ buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptraddDependent(temperature_type, FieldStore::TimeDerivative::VAL, prefix("temperature")); - std::vector parameter_fields; (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); - (parameter_fields.push_back(field_store->getField(prefix("param_" + parameter_types.name))), ...); using SystemType = ThermoMechanicsSystem; + auto sys = std::make_shared(); + sys->field_store = field_store; + sys->solver = solver; + sys->disp_bc = disp_bc; + sys->temperature_bc = temperature_bc; + sys->disp_time_rule = disp_time_rule_ptr; + sys->temperature_time_rule = temperature_time_rule_ptr; // Solid mechanics weak form (u, u_old, v_old, a_old, temp, temp_old, params...) std::string solid_force_name = prefix("solid_force"); - auto solid_weak_form = std::make_shared( + sys->solid_weak_form = std::make_shared( solid_force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(solid_force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, temperature_type, temperature_old_type, @@ -422,39 +412,35 @@ buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr( + sys->thermal_weak_form = std::make_shared( thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, disp_type, disp_old_type, velo_old_type, accel_old_type, FieldType(prefix("param_" + parameter_types.name))...)); - // Cycle-zero weak form (u, v, a, temp, temp_old, params...) - std::string cycle_zero_name = prefix("solid_reaction"); - auto cycle_zero_weak_form = std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - temperature_type, temperature_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + sys->weak_forms = {sys->solid_weak_form, sys->thermal_weak_form}; + + if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { + std::string cycle_zero_name = prefix("solid_reaction"); + sys->cycle_zero_weak_form = std::make_shared( + cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, + temperature_type, temperature_old_type, + FieldType(prefix("param_" + parameter_types.name))...)); - if (cycle_zero_solver == nullptr) { - cycle_zero_solver = solver->singleBlockSolver(0); + auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); + SLIC_ERROR_IF(cz_solver == nullptr, + "Could not derive a cycle-zero solver for block 0 from the provided thermo-mechanics solver."); + + sys->cycle_zero_system = std::make_shared(); + sys->cycle_zero_system->field_store = field_store; + sys->cycle_zero_system->solver = cz_solver; + sys->cycle_zero_system->weak_forms = {sys->cycle_zero_weak_form}; } - SLIC_ERROR_IF(cycle_zero_solver == nullptr, - "Could not derive a cycle-zero solver for block 0 from the provided thermo-mechanics solver."); - - // Build solver and advancer - std::vector> weak_forms{solid_weak_form, thermal_weak_form}; - auto advancer = std::make_shared(field_store, weak_forms, solver, cycle_zero_weak_form, - cycle_zero_solver); - - return SystemType{{field_store, solver, advancer, parameter_fields, prepend_name}, - solid_weak_form, - thermal_weak_form, - cycle_zero_weak_form, - disp_bc, - temperature_bc, - disp_time_rule_ptr, - temperature_time_rule_ptr}; + + sys->advancer = std::make_shared(sys, sys->cycle_zero_system); + + return sys; } /** @@ -462,12 +448,17 @@ buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr -auto buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, std::string prepend_name, - FieldType... parameter_types) +std::shared_ptr< + ThermoMechanicsSystem> +buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, + const std::string& prepend_name, FieldType... parameter_types) { - return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, - std::move(prepend_name), nullptr, parameter_types...); + ThermoMechanicsOptions + opts; + opts.prepend_name = prepend_name; + return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, opts, parameter_types...); } /** @@ -475,13 +466,18 @@ auto buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr -auto buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, - std::shared_ptr cycle_zero_solver, - FieldType... parameter_types) +std::shared_ptr< + ThermoMechanicsSystem> +buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, + std::shared_ptr cycle_zero_solver, + FieldType... parameter_types) { - return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, "", - cycle_zero_solver, parameter_types...); + ThermoMechanicsOptions + opts; + opts.cycle_zero_solver = std::move(cycle_zero_solver); + return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, opts, parameter_types...); } /** @@ -489,12 +485,18 @@ auto buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr -auto buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, - FieldType... parameter_types) +std::shared_ptr< + ThermoMechanicsSystem> +buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, + FieldType... parameter_types) { - return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, "", nullptr, - parameter_types...); + return buildThermoMechanicsSystem( + mesh, solver, disp_rule, temp_rule, + ThermoMechanicsOptions{}, + parameter_types...); } } // namespace smith From 2e1778f3d640407ccf4aecd15a76bc0d775ae78f Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 11 Apr 2026 17:21:25 -0600 Subject: [PATCH 02/67] Fix multiphysics_time_integrator loose ends --- .../multiphysics_time_integrator.cpp | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index eb6f61b281..d02fb551fb 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -26,7 +26,6 @@ std::pair, std::vector> MultiphysicsTimeI const TimeInfo& time_info, const FieldState& shape_disp, const std::vector& states, const std::vector& params) const { - (void)shape_disp; std::vector current_states = states; // Sync FieldStore with (possibly updated) states and params so they are current for solve @@ -51,7 +50,6 @@ std::pair, std::vector> MultiphysicsTimeI if (time_info.cycle() == 0 && cycle_zero_system_ && requires_cycle_zero_solve) { auto cz_unknowns = cycle_zero_system_->solve(time_info); - SLIC_ERROR_IF(cz_unknowns.size() != 1, "Cycle-zero system produced incorrect number of unknowns, expected 1."); // Cycle zero system solves for initial acceleration which translates into test field. // Sync the solved unknowns back into current_states before main solve @@ -59,8 +57,9 @@ std::pair, std::vector> MultiphysicsTimeI std::string test_field_name = system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms.front()->name()); size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); - current_states[test_field_state_idx] = cz_unknowns[0]; - system_->field_store->setField(test_field_state_idx, cz_unknowns[0]); + size_t cz_unknown_idx = cycle_zero_system_->field_store->getUnknownIndex(test_field_name); + current_states[test_field_state_idx] = cz_unknowns[cz_unknown_idx]; + system_->field_store->setField(test_field_state_idx, cz_unknowns[cz_unknown_idx]); } std::vector primary_unknowns = system_->solve(time_info); From 513972b8cd574c279918b63e02af60f2a21d5e07 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 11 Apr 2026 19:46:36 -0600 Subject: [PATCH 03/67] Decouple StateAdvancer from SystemBase, add helper functions. --- .../differentiable_physics.hpp | 19 +++++ .../multiphysics_time_integrator.hpp | 16 ++++ .../solid_mechanics_system.hpp | 57 +------------ ...id_mechanics_with_internal_vars_system.hpp | 79 +------------------ .../differentiable_numerics/system_base.hpp | 2 +- .../tests/test_solid_dynamics.cpp | 11 +-- .../test_solid_static_with_internal_vars.cpp | 12 +-- .../tests/test_thermal_static.cpp | 8 +- .../tests/test_thermo_mechanics.cpp | 12 +-- .../thermal_system.hpp | 48 +---------- .../thermo_mechanics_system.hpp | 73 +---------------- 11 files changed, 64 insertions(+), 273 deletions(-) diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index 1820e95036..e079ee9f2d 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -16,6 +16,7 @@ #include "smith/physics/base_physics.hpp" #include "smith/differentiable_numerics/field_state.hpp" #include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include #include @@ -197,4 +198,22 @@ class DifferentiablePhysics : public BasePhysics { 0; ///< previous cycle, saved to reconstruct the start of step time used in computing reaction forces }; +template +std::unique_ptr makeDifferentiablePhysics(std::shared_ptr system, + std::shared_ptr advancer, + const std::string& physics_name) +{ + return std::make_unique( + system->field_store->getMesh(), system->field_store->graph(), system->field_store->getShapeDisp(), + system->field_store->getStateFields(), system->field_store->getParameterFields(), std::move(advancer), + physics_name, system->field_store->getReactionInfos()); +} + +template +std::unique_ptr makeDifferentiablePhysics(std::shared_ptr system, + const std::string& physics_name) +{ + return makeDifferentiablePhysics(system, makeAdvancer(system), physics_name); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 2880435a5a..5d029f3524 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -47,4 +47,20 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { std::shared_ptr cycle_zero_system_; }; +inline std::shared_ptr makeAdvancer(std::shared_ptr system, + std::shared_ptr cycle_zero_system = nullptr) +{ + return std::make_shared(std::move(system), std::move(cycle_zero_system)); +} + +template +std::shared_ptr makeAdvancer(std::shared_ptr system) +{ + if constexpr (requires { system->cycle_zero_system; }) { + return makeAdvancer(system, system->cycle_zero_system); + } else { + return makeAdvancer(system, nullptr); + } +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 369cac7988..01eaa4c6fc 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -58,17 +58,6 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. - /** - * @brief Create a DifferentiablePhysics object for this system. - * @param physics_name The name of the physics. - * @return std::unique_ptr The differentiable physics object. - */ - std::unique_ptr createDifferentiablePhysics(std::string physics_name) - { - return std::make_unique( - field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), - field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); - } /** * @brief Set the material model for a domain, defining integrals for the solid weak form. @@ -350,56 +339,12 @@ std::shared_ptrcycle_zero_system = std::make_shared(); - sys->cycle_zero_system->field_store = field_store; + sys->cycle_zero_system = std::make_shared(field_store); sys->cycle_zero_system->solver = cz_solver; sys->cycle_zero_system->weak_forms = {sys->cycle_zero_solid_weak_form}; } - sys->advancer = std::make_shared(sys, sys->cycle_zero_system); - return sys; } -/** - * @brief Factory function to build a solid mechanics system with a physics name and parameter fields. - */ -template -std::shared_ptr> buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, - const std::string& prepend_name, FieldType... parameter_types) -{ - SolidMechanicsOptions opts; - opts.prepend_name = prepend_name; - return buildSolidMechanicsSystem(mesh, solver, disp_rule, opts, - parameter_types...); -} - -/** - * @brief Factory function to build a solid mechanics system (without physics name). - */ -template -std::shared_ptr> buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, - std::shared_ptr cycle_zero_solver, FieldType... parameter_types) -{ - SolidMechanicsOptions opts; - opts.cycle_zero_solver = std::move(cycle_zero_solver); - return buildSolidMechanicsSystem(mesh, solver, disp_rule, opts, - parameter_types...); -} - -/** - * @brief Factory function to build a solid mechanics system (without physics name or specific cycle-zero solver). - */ -template -std::shared_ptr> buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, - FieldType... parameter_types) -{ - return buildSolidMechanicsSystem( - mesh, solver, disp_rule, SolidMechanicsOptions{}, - parameter_types...); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 86432fc8bd..03cc870b5a 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -81,17 +81,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { std::shared_ptr disp_time_rule; ///< Time integration for displacement. std::shared_ptr state_time_rule; ///< Time integration for internal variable. - /** - * @brief Create a DifferentiablePhysics object for this system. - * @param physics_name The name of the physics. - * @return std::unique_ptr The differentiable physics object. - */ - std::unique_ptr createDifferentiablePhysics(std::string physics_name) - { - return std::make_unique( - field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), - field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); - } + /** * @brief Set the material model for the solid mechanics part. @@ -435,77 +425,12 @@ buildSolidMechanicsWithInternalVarsSystem( "Could not derive a cycle-zero solver for block 0 from the provided internal-vars solid mechanics " "solver."); - sys->cycle_zero_system = std::make_shared(); - sys->cycle_zero_system->field_store = field_store; + sys->cycle_zero_system = std::make_shared(field_store); sys->cycle_zero_system->solver = cz_solver; sys->cycle_zero_system->weak_forms = {sys->cycle_zero_solid_weak_form}; } - sys->advancer = std::make_shared(sys, sys->cycle_zero_system); - return sys; } -/** - * @brief Factory function to build a solid mechanics system with internal variables, physics name and parameters. - */ -template -std::shared_ptr> -buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, InternalVarTimeRule isv_rule, - const std::string& prepend_name, - FieldType... parameter_types) -{ - SolidMechanicsWithInternalVarsOptions - opts; - opts.prepend_name = prepend_name; - return buildSolidMechanicsWithInternalVarsSystem(mesh, solver, disp_rule, isv_rule, opts, - parameter_types...); -} - -/** - * @brief Factory function to build a solid mechanics system with internal variables (without physics name). - */ -template -std::shared_ptr> -buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, InternalVarTimeRule isv_rule, - std::shared_ptr cycle_zero_solver, - FieldType... parameter_types) -{ - SolidMechanicsWithInternalVarsOptions - opts; - opts.cycle_zero_solver = std::move(cycle_zero_solver); - return buildSolidMechanicsWithInternalVarsSystem(mesh, solver, disp_rule, isv_rule, opts, - parameter_types...); -} - -/** - * @brief Factory function index to build a solid mechanics system with internal variables (without physics name or - * specific cz solver). - */ -template -std::shared_ptr> -buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, InternalVarTimeRule isv_rule, - FieldType... parameter_types) -{ - return buildSolidMechanicsWithInternalVarsSystem( - mesh, solver, disp_rule, isv_rule, - SolidMechanicsWithInternalVarsOptions{}, - parameter_types...); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index 018808acdc..ca84cf672e 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -56,9 +56,9 @@ struct SystemBase { // --- infrastructure --- std::shared_ptr field_store; ///< Field store managing the system's fields. std::shared_ptr solver; ///< The solver for the system. - std::shared_ptr advancer; ///< The state advancer. SystemBase() = default; + explicit SystemBase(std::shared_ptr fs) : field_store(std::move(fs)) {} virtual ~SystemBase() = default; /** diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 2647d44a27..56e50e919d 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -104,8 +104,8 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto coupled_solver = std::make_shared(solid_block_solver); auto system = buildSolidMechanicsSystem( - mesh, coupled_solver, ImplicitNewmarkSecondOrderTimeIntegrationRule{}, FieldType("bulk"), - FieldType("shear")); + mesh, coupled_solver, ImplicitNewmarkSecondOrderTimeIntegrationRule{}, {}, + FieldType("bulk"), FieldType("shear")); static constexpr double gravity = -9.0; @@ -149,7 +149,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); - std::tie(states, reactions) = system->advancer->advanceState(t_info, shape_disp, states, params); + std::tie(states, reactions) = makeAdvancer(system)->advanceState(t_info, shape_disp, states, params); output_states = system->field_store->getOutputFieldStates(); time += dt_; cycle++; @@ -204,11 +204,12 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(solid_block_solver); - auto system = buildSolidMechanicsSystem(mesh, coupled_solver, time_rule, physics_name, + auto system = buildSolidMechanicsSystem(mesh, coupled_solver, time_rule, + {.prepend_name = physics_name}, FieldType("bulk"), FieldType("shear")); - auto physics = system->createDifferentiablePhysics(physics_name); + auto physics = makeDifferentiablePhysics(system, physics_name); auto bcs = system->disp_bc; bcs->setFixedVectorBCs(mesh->domain("right")); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index d8e5ddea29..866be1fa25 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -106,7 +106,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto system = buildSolidMechanicsWithInternalVarsSystem( mesh, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, - "solid_static_with_internal_vars"); + {.prepend_name = "solid_static_with_internal_vars"}); // Material and Evolution system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); @@ -125,7 +125,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) return u; }); - auto physics = system->createDifferentiablePhysics("physics"); + auto physics = makeDifferentiablePhysics(system, "physics"); // Create ParaView writer auto writer = createParaviewWriter(*mesh, system->field_store->getOutputFieldStates(), "solid_state_output"); @@ -151,7 +151,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto system = buildSolidMechanicsWithInternalVarsSystem( mesh, staggered_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, - "solid_staggered_relaxation"); + {.prepend_name = "solid_staggered_relaxation"}); system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); @@ -164,7 +164,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) return u; }); - auto physics = system->createDifferentiablePhysics("physics_relaxed"); + auto physics = makeDifferentiablePhysics(system, "physics_relaxed"); for (int step = 1; step <= 3; ++step) { physics->advanceTimestep(1.0); SLIC_INFO("Staggered relaxation step " << step << " completed"); @@ -177,7 +177,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto system = buildSolidMechanicsWithInternalVarsSystem( mesh, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, - "body_force_test"); + {.prepend_name = "body_force_test"}); system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); @@ -201,7 +201,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) return t; }); - auto physics = system->createDifferentiablePhysics("physics_bf"); + auto physics = makeDifferentiablePhysics(system, "physics_bf"); physics->advanceTimestep(1.0); // Check that the displacement field is non-zero (the body force + traction produced deformation) diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 66c66809c9..622ad93c58 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -52,7 +52,7 @@ struct ThermalStaticFixture : public testing::Test { auto coupled_solver = std::make_shared(nonlinear_block_solver); auto thermal_system = - buildThermalSystem<2, temp_order>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}); + buildThermalSystem<2, temp_order>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}, {}); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -72,7 +72,7 @@ struct ThermalStaticFixture : public testing::Test { [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = thermal_system->advancer->advanceState( + auto [new_states, reactions] = makeAdvancer(thermal_system)->advanceState( t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); @@ -141,7 +141,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto coupled_solver = std::make_shared(nonlinear_block_solver); FieldType> conductivity_param("conductivity"); - auto thermal_system = buildThermalSystem<2, 1>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}, "", + auto thermal_system = buildThermalSystem<2, 1>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}, {}, conductivity_param); // Set the conductivity parameter field to k=1.0 @@ -168,7 +168,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = thermal_system->advancer->advanceState( + auto [new_states, reactions] = makeAdvancer(thermal_system)->advanceState( t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp index 3b218dce44..3f8588fc2f 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp @@ -92,9 +92,9 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI FieldType> youngs_modulus("youngs_modulus"); auto system = buildThermoMechanicsSystem( mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, "thermo", youngs_modulus); + BackwardEulerFirstOrderTimeIntegrationRule{}, {.prepend_name = "thermo"}, youngs_modulus); - auto physics = system->createDifferentiablePhysics("thermo_physics"); + auto physics = makeDifferentiablePhysics(system, "thermo_physics"); const auto& solid_dual_space = physics->dual("thermo_solid_force").space(); const auto& solid_state_space = physics->state("thermo_displacement").space(); const auto& thermal_dual_space = physics->dual("thermo_thermal_flux").space(); @@ -125,7 +125,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) FieldType> youngs_modulus("youngs_modulus"); auto system = buildThermoMechanicsSystem( mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, "thermo", youngs_modulus); + BackwardEulerFirstOrderTimeIntegrationRule{}, {.prepend_name = "thermo"}, youngs_modulus); GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; system->setMaterial(material, mesh_->entireBodyName()); @@ -140,7 +140,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) return traction; }); - auto physics = system->createDifferentiablePhysics("thermo_physics"); + auto physics = makeDifferentiablePhysics(system, "thermo_physics"); // Run forward double dt = 1.0; @@ -169,7 +169,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) FieldType> youngs_modulus("youngs_modulus"); auto system = buildThermoMechanicsSystem( mesh_, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, youngs_modulus); + BackwardEulerFirstOrderTimeIntegrationRule{}, {}, youngs_modulus); system->setMaterial(material, mesh_->entireBodyName()); system->field_store->getParameterFields()[0].get()->setFromFieldFunction( [=](smith::tensor) { return 100.0; }); @@ -199,7 +199,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) std::vector reactions; for (size_t step = 0; step < 1; ++step) { std::tie(states, reactions) = - system->advancer->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + makeAdvancer(system)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 157a90623d..9c1d6ce4d9 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -49,17 +49,7 @@ struct ThermalSystem : public SystemBase { std::shared_ptr temperature_bc; ///< Temperature boundary conditions. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - /** - * @brief Create a DifferentiablePhysics object for this system. - * @param physics_name The name of the physics. - * @return std::unique_ptr The differentiable physics object. - */ - std::unique_ptr createDifferentiablePhysics(std::string physics_name) - { - return std::make_unique( - field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), - field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); - } + /** * @brief Set the thermal material model for a domain. @@ -221,44 +211,8 @@ std::shared_ptrweak_forms = {sys->thermal_weak_form}; - sys->advancer = std::make_shared(sys, nullptr); return sys; } -/** - * @brief Factory function to build a thermal system with default quasi-static rule. - */ -template -std::shared_ptr> -buildThermalSystem( - std::shared_ptr mesh, std::shared_ptr solver, - ThermalOptions options, - FieldType... parameter_types) -{ - return buildThermalSystem( - mesh, solver, QuasiStaticFirstOrderTimeIntegrationRule{}, options, parameter_types...); -} - -template -std::shared_ptr> buildThermalSystem( - std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_time_rule, - const std::string& prepend_name, FieldType... parameter_types) -{ - ThermalOptions opts; - opts.prepend_name = prepend_name; - return buildThermalSystem(mesh, solver, temp_time_rule, - opts, parameter_types...); -} - -template -std::shared_ptr> buildThermalSystem( - std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_time_rule, - FieldType... parameter_types) -{ - return buildThermalSystem( - mesh, solver, temp_time_rule, ThermalOptions{}, - parameter_types...); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index 30f61eacb7..ad8b3e0d85 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -73,17 +73,7 @@ struct ThermoMechanicsSystem : public SystemBase { std::shared_ptr disp_time_rule; ///< Time integration for displacement. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - /** - * @brief Create a DifferentiablePhysics object for this system. - * @param physics_name The name of the physics. - * @return std::unique_ptr The differentiable physics object. - */ - std::unique_ptr createDifferentiablePhysics(std::string physics_name) - { - return std::make_unique( - field_store->getMesh(), field_store->graph(), field_store->getShapeDisp(), field_store->getStateFields(), - field_store->getParameterFields(), advancer, physics_name, field_store->getReactionInfos()); - } + /** * @brief Set the material model for a domain, defining integrals for solid and thermal weak forms. @@ -432,71 +422,12 @@ buildThermoMechanicsSystem( SLIC_ERROR_IF(cz_solver == nullptr, "Could not derive a cycle-zero solver for block 0 from the provided thermo-mechanics solver."); - sys->cycle_zero_system = std::make_shared(); - sys->cycle_zero_system->field_store = field_store; + sys->cycle_zero_system = std::make_shared(field_store); sys->cycle_zero_system->solver = cz_solver; sys->cycle_zero_system->weak_forms = {sys->cycle_zero_weak_form}; } - sys->advancer = std::make_shared(sys, sys->cycle_zero_system); - return sys; } -/** - * @brief Factory function to build a thermo-mechanical system with a physics name and parameter fields. - */ -template -std::shared_ptr< - ThermoMechanicsSystem> -buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, - const std::string& prepend_name, FieldType... parameter_types) -{ - ThermoMechanicsOptions - opts; - opts.prepend_name = prepend_name; - return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, opts, parameter_types...); -} - -/** - * @brief Factory function to build a thermo-mechanical system (without physics name). - */ -template -std::shared_ptr< - ThermoMechanicsSystem> -buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, - std::shared_ptr cycle_zero_solver, - FieldType... parameter_types) -{ - ThermoMechanicsOptions - opts; - opts.cycle_zero_solver = std::move(cycle_zero_solver); - return buildThermoMechanicsSystem(mesh, solver, disp_rule, temp_rule, opts, parameter_types...); -} - -/** - * @brief Factory function to build a thermo-mechanical system (without physics name). - */ -template -std::shared_ptr< - ThermoMechanicsSystem> -buildThermoMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_rule, TemperatureTimeRule temp_rule, - FieldType... parameter_types) -{ - return buildThermoMechanicsSystem( - mesh, solver, disp_rule, temp_rule, - ThermoMechanicsOptions{}, - parameter_types...); -} - } // namespace smith From d8706916ea9fce1d4dbc2bb63e9086e3d75f1e01 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 11 Apr 2026 23:17:58 -0600 Subject: [PATCH 04/67] Try to cleanup construction of systems. --- .../differentiable_numerics/field_store.cpp | 13 +++- .../differentiable_numerics/field_store.hpp | 22 ++++-- .../solid_mechanics_system.hpp | 52 ++++++-------- ...id_mechanics_with_internal_vars_system.hpp | 71 +++++++++--------- .../differentiable_numerics/system_base.hpp | 13 +++- .../test_multiphysics_time_integrator.cpp | 9 ++- .../tests/test_solid_dynamics.cpp | 3 +- .../tests/test_thermal_static.cpp | 14 ++-- .../thermal_system.hpp | 43 +++++------ .../thermo_mechanics_system.hpp | 72 +++++++++---------- 10 files changed, 162 insertions(+), 150 deletions(-) diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index c2dd6e1beb..5cf77fc930 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -11,12 +11,21 @@ namespace smith { -FieldStore::FieldStore(std::shared_ptr mesh, size_t storage_size) +FieldStore::FieldStore(std::shared_ptr mesh, size_t storage_size, std::string prepend_name) : mesh_(mesh), - graph_(std::make_shared(std::make_unique(storage_size))) + graph_(std::make_shared(std::make_unique(storage_size))), + prepend_name_(std::move(prepend_name)) { } +std::string FieldStore::prefix(const std::string& base) const +{ + if (prepend_name_.empty()) { + return base; + } + return prepend_name_ + "_" + base; +} + std::shared_ptr FieldStore::addBoundaryConditions(FEFieldPtr field) { boundary_conditions_.push_back(std::make_shared(mesh_->mfemParMesh(), field->space())); diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 625a5bb824..6ea91aaf38 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -54,8 +54,18 @@ struct FieldStore { * @brief Construct a new FieldStore object. * @param mesh The mesh associated with the fields. * @param storage_size Initial storage size for fields (default: 50). + * @param prepend_name Namespace prefix applied by @c prefix(). Empty means no prefix. */ - FieldStore(std::shared_ptr mesh, size_t storage_size = 50); + FieldStore(std::shared_ptr mesh, size_t storage_size = 50, std::string prepend_name = ""); + + /** + * @brief Apply this store's namespace prefix to a base name. + * + * Returns @p base unchanged when the store was constructed with an empty prepend name, + * otherwise returns @c prepend_name_ + "_" + base. Factories use this to namespace + * weak form, field, and parameter names consistently without re-implementing the rule. + */ + std::string prefix(const std::string& base) const; /** * @brief Enum for different types of time derivatives. @@ -74,8 +84,9 @@ struct FieldStore { * @param type The field type specification. */ template - void addShapeDisp(FieldType type) + void addShapeDisp(FieldType& type) { + type.name = prefix(type.name); shape_disp_.push_back(smith::createFieldState(*graph_, Space{}, type.name, mesh_->tag())); } @@ -85,8 +96,9 @@ struct FieldStore { * @param type The field type specification. */ template - void addParameter(FieldType type) + void addParameter(FieldType& type) { + type.name = prefix(type.name); to_params_index_[type.name] = params_.size(); params_.push_back(smith::createFieldState(*graph_, Space{}, type.name, mesh_->tag())); } @@ -110,6 +122,7 @@ struct FieldStore { std::shared_ptr addIndependent(FieldType& type, std::shared_ptr time_rule) { + type.name = prefix(type.name); type.unknown_index = static_cast(num_unknowns_); to_states_index_[type.name] = states_.size(); to_unknown_index_[type.name] = num_unknowns_; @@ -173,7 +186,7 @@ struct FieldStore { SLIC_ERROR("Unsupported TimeDerivative"); } - std::string name = name_override.empty() ? independent_field.name + suffix : name_override; + std::string name = name_override.empty() ? independent_field.name + suffix : prefix(name_override); if (independent_name_to_rule_index_.count(independent_field.name)) { size_t rule_idx = independent_name_to_rule_index_.at(independent_field.name); @@ -423,6 +436,7 @@ struct FieldStore { private: std::shared_ptr mesh_; std::shared_ptr graph_; + std::string prepend_name_; std::vector shape_disp_; std::vector params_; diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 01eaa4c6fc..74243c0fce 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -39,6 +39,8 @@ namespace smith { template struct SolidMechanicsSystem : public SystemBase { + using SystemBase::SystemBase; + static_assert(DisplacementTimeRule::num_states == 4, "SolidMechanicsSystem requires a 4-state time integration rule"); /// using @@ -58,7 +60,6 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. - /** * @brief Set the material model for a domain, defining integrals for the solid weak form. * @tparam MaterialType The material model type. @@ -286,62 +287,55 @@ std::shared_ptr options, FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100); - - auto prefix = [&](const std::string& name) { - if (options.prepend_name.empty()) { - return name; - } - return options.prepend_name + "_" + name; - }; + auto field_store = std::make_shared(mesh, 100, options.prepend_name); // Add shape displacement - FieldType> shape_disp_type(prefix("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); field_store->addShapeDisp(shape_disp_type); // Add displacement as independent (unknown) with time integration rule auto disp_time_rule_ptr = std::make_shared(disp_time_rule); - FieldType> disp_type(prefix("displacement_solve_state")); + FieldType> disp_type("displacement_solve_state"); auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); // Add dependent fields for time integration history - auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, prefix("displacement")); - auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, prefix("velocity")); - auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, prefix("acceleration")); + auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); + auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); + auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); // Add parameters - (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); using SystemType = SolidMechanicsSystem; - auto sys = std::make_shared(); - sys->field_store = field_store; - sys->solver = solver; - sys->disp_bc = disp_bc; - sys->disp_time_rule = disp_time_rule_ptr; // Create solid mechanics weak form (u, u_old, v_old, a_old) - std::string force_name = prefix("solid_force"); - sys->solid_weak_form = std::make_shared( + std::string force_name = field_store->prefix("solid_force"); + auto solid_weak_form = std::make_shared( force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + parameter_types...)); - sys->weak_forms = {sys->solid_weak_form}; + auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); + sys->disp_bc = disp_bc; + sys->disp_time_rule = disp_time_rule_ptr; + sys->solid_weak_form = solid_weak_form; if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - std::string cycle_zero_name = prefix("solid_reaction"); + std::string cycle_zero_name = field_store->prefix("solid_reaction"); sys->cycle_zero_solid_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + parameter_types...)); auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); SLIC_ERROR_IF(cz_solver == nullptr, "Could not derive a cycle-zero solver for block 0 from the provided solid mechanics solver."); - sys->cycle_zero_system = std::make_shared(field_store); - sys->cycle_zero_system->solver = cz_solver; - sys->cycle_zero_system->weak_forms = {sys->cycle_zero_solid_weak_form}; + sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); } return sys; diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 03cc870b5a..75790cd756 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -44,6 +44,8 @@ template struct SolidMechanicsWithInternalVarsSystem : public SystemBase { + using SystemBase::SystemBase; + static_assert(DisplacementTimeRule::num_states == 4, "SolidMechanicsWithInternalVarsSystem requires a 4-state displacement rule"); static_assert(InternalVarTimeRule::num_states == 2, @@ -81,8 +83,6 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { std::shared_ptr disp_time_rule; ///< Time integration for displacement. std::shared_ptr state_time_rule; ///< Time integration for internal variable. - - /** * @brief Set the material model for the solid mechanics part. * @@ -354,80 +354,73 @@ buildSolidMechanicsWithInternalVarsSystem( options, FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100); - - auto prefix = [&](const std::string& name) { - if (options.prepend_name.empty()) { - return name; - } - return options.prepend_name + "_" + name; - }; + auto field_store = std::make_shared(mesh, 100, options.prepend_name); // Add shape displacement - FieldType> shape_disp_type(prefix("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); field_store->addShapeDisp(shape_disp_type); // 1. Displacement fields (4-state second-order) auto disp_time_rule_ptr = std::make_shared(disp_rule); - FieldType> disp_type(prefix("displacement_solve_state")); + FieldType> disp_type("displacement_solve_state"); auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); - auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, prefix("displacement")); - auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, prefix("velocity")); - auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, prefix("acceleration")); + auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); + auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); + auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); // 2. Internal variable fields (2-state first-order) auto state_time_rule_ptr = std::make_shared(state_rule); - FieldType state_type(prefix("state_solve_state")); + FieldType state_type("state_solve_state"); auto state_bc = field_store->addIndependent(state_type, state_time_rule_ptr); - auto state_old_type = field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, prefix("state")); + auto state_old_type = field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); // 3. Parameters - (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); using SystemType = SolidMechanicsWithInternalVarsSystem; - auto sys = std::make_shared(); - sys->field_store = field_store; - sys->solver = solver; - sys->disp_bc = disp_bc; - sys->state_bc = state_bc; - sys->disp_time_rule = disp_time_rule_ptr; - sys->state_time_rule = state_time_rule_ptr; // 4. Solid weak form: residual for u (inputs: u, u_old, v_old, a_old, alpha, alpha_old, params...) - std::string solid_res_name = prefix("solid_residual"); - sys->solid_weak_form = std::make_shared( + std::string solid_res_name = field_store->prefix("solid_residual"); + auto solid_weak_form = std::make_shared( solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, - state_type, state_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + state_type, state_old_type, parameter_types...)); // 5. State weak form: residual for alpha (inputs: alpha, alpha_old, u, u_old, v_old, a_old, params...) - std::string state_res_name = prefix("state_residual"); - sys->state_weak_form = std::make_shared( + std::string state_res_name = field_store->prefix("state_residual"); + auto state_weak_form = std::make_shared( state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, disp_old_type, - velo_old_type, accel_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + velo_old_type, accel_old_type, parameter_types...)); - sys->weak_forms = {sys->solid_weak_form, sys->state_weak_form}; + auto sys = std::make_shared(field_store, solver, + std::vector>{solid_weak_form, state_weak_form}); + sys->disp_bc = disp_bc; + sys->state_bc = state_bc; + sys->disp_time_rule = disp_time_rule_ptr; + sys->state_time_rule = state_time_rule_ptr; + sys->solid_weak_form = solid_weak_form; + sys->state_weak_form = state_weak_form; if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { // 6. Cycle-zero weak form: solve for acceleration (inputs: u, v, a, alpha, params...) - std::string cycle_zero_name = prefix("solid_reaction"); + std::string cycle_zero_name = field_store->prefix("solid_reaction"); sys->cycle_zero_solid_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - state_type, FieldType(prefix("param_" + parameter_types.name))...)); + state_type, parameter_types...)); auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); SLIC_ERROR_IF(cz_solver == nullptr, "Could not derive a cycle-zero solver for block 0 from the provided internal-vars solid mechanics " "solver."); - sys->cycle_zero_system = std::make_shared(field_store); - sys->cycle_zero_system->solver = cz_solver; - sys->cycle_zero_system->weak_forms = {sys->cycle_zero_solid_weak_form}; + sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); } return sys; diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index ca84cf672e..950a5a6c77 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -58,7 +58,11 @@ struct SystemBase { std::shared_ptr solver; ///< The solver for the system. SystemBase() = default; - explicit SystemBase(std::shared_ptr fs) : field_store(std::move(fs)) {} + explicit SystemBase(std::shared_ptr fs, std::shared_ptr sol = nullptr, + std::vector> wfs = {}) + : weak_forms(std::move(wfs)), field_store(std::move(fs)), solver(std::move(sol)) + { + } virtual ~SystemBase() = default; /** @@ -78,4 +82,11 @@ struct SystemBase { const std::vector& states_for_reactions) const; }; +inline std::shared_ptr makeSubSystem(std::shared_ptr field_store, + std::shared_ptr solver, + std::vector> weak_forms) +{ + return std::make_shared(std::move(field_store), std::move(solver), std::move(weak_forms)); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 5a1f232cdb..69248556a2 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -143,7 +143,8 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroUsesBcsForReactionFieldNotUnknownZero) [](std::vector nodes, int) { return allNodesOnBoundary(nodes, 1.0); }); auto field_store = std::make_shared(mesh, 20); - field_store->addShapeDisp(FieldType>("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); + field_store->addShapeDisp(shape_disp_type); auto quasi_static = std::make_shared(); FieldType> temperature_type("temperature"); @@ -204,7 +205,8 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) "integrator_mesh"); auto field_store = std::make_shared(mesh, 20); - field_store->addShapeDisp(FieldType>("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); + field_store->addShapeDisp(shape_disp_type); auto quasi_static = std::make_shared(); FieldType> displacement_type("displacement_solve_state"); @@ -255,7 +257,8 @@ TEST(SystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) "single_block_characterization_mesh"); auto field_store = std::make_shared(mesh, 20); - field_store->addShapeDisp(FieldType>("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); + field_store->addShapeDisp(shape_disp_type); auto quasi_static = std::make_shared(); FieldType> temperature_type("temperature"); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 56e50e919d..7025350095 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -204,8 +204,7 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(solid_block_solver); - auto system = buildSolidMechanicsSystem(mesh, coupled_solver, time_rule, - {.prepend_name = physics_name}, + auto system = buildSolidMechanicsSystem(mesh, coupled_solver, time_rule, {.prepend_name = physics_name}, FieldType("bulk"), FieldType("shear")); diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 622ad93c58..29891cf573 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -72,9 +72,10 @@ struct ThermalStaticFixture : public testing::Test { [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system)->advanceState( - t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), - thermal_system->field_store->getParameterFields()); + auto [new_states, reactions] = makeAdvancer(thermal_system) + ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), + thermal_system->field_store->getAllFields(), + thermal_system->field_store->getParameterFields()); for (size_t i = 0; i < new_states.size(); ++i) { thermal_system->field_store->setField(i, new_states[i]); @@ -168,9 +169,10 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system)->advanceState( - t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), - thermal_system->field_store->getParameterFields()); + auto [new_states, reactions] = makeAdvancer(thermal_system) + ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), + thermal_system->field_store->getAllFields(), + thermal_system->field_store->getParameterFields()); for (size_t i = 0; i < new_states.size(); ++i) { thermal_system->field_store->setField(i, new_states[i]); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 9c1d6ce4d9..3ec49df88e 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -38,6 +38,8 @@ namespace smith { template struct ThermalSystem : public SystemBase { + using SystemBase::SystemBase; + static_assert(TemperatureTimeRule::num_states == 2, "ThermalSystem requires a 2-state time integration rule"); /// @brief using for ThermalWeakFormType @@ -49,8 +51,6 @@ struct ThermalSystem : public SystemBase { std::shared_ptr temperature_bc; ///< Temperature boundary conditions. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - - /** * @brief Set the thermal material model for a domain. * @@ -176,41 +176,36 @@ std::shared_ptr options, FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100); + auto field_store = std::make_shared(mesh, 100, options.prepend_name); - auto prefix = [&](const std::string& name) { - if (options.prepend_name.empty()) { - return name; - } - return options.prepend_name + "_" + name; - }; - - FieldType> shape_disp_type(prefix("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); field_store->addShapeDisp(shape_disp_type); auto temperature_time_rule = std::make_shared(temp_rule); - FieldType> temperature_type(prefix("temperature_solve_state")); + FieldType> temperature_type("temperature_solve_state"); auto temperature_bc = field_store->addIndependent(temperature_type, temperature_time_rule); auto temperature_old_type = - field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, prefix("temperature")); + field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); - (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); using SystemType = ThermalSystem; - auto sys = std::make_shared(); - sys->field_store = field_store; - sys->solver = solver; - sys->temperature_bc = temperature_bc; - sys->temperature_time_rule = temperature_time_rule; - std::string thermal_flux_name = prefix("thermal_flux"); - sys->thermal_weak_form = std::make_shared( + std::string thermal_flux_name = field_store->prefix("thermal_flux"); + auto thermal_weak_form = std::make_shared( thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); - - sys->weak_forms = {sys->thermal_weak_form}; + parameter_types...)); + auto sys = + std::make_shared(field_store, solver, std::vector>{thermal_weak_form}); + sys->temperature_bc = temperature_bc; + sys->temperature_time_rule = temperature_time_rule; + sys->thermal_weak_form = thermal_weak_form; return sys; } diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index ad8b3e0d85..838878bf3a 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -41,6 +41,8 @@ template struct ThermoMechanicsSystem : public SystemBase { + using SystemBase::SystemBase; + static_assert(DisplacementTimeRule::num_states == 4, "ThermoMechanicsSystem requires a 4-state displacement time rule"); static_assert(TemperatureTimeRule::num_states == 2, "ThermoMechanicsSystem requires a 2-state temperature time rule"); @@ -73,8 +75,6 @@ struct ThermoMechanicsSystem : public SystemBase { std::shared_ptr disp_time_rule; ///< Time integration for displacement. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - - /** * @brief Set the material model for a domain, defining integrals for solid and thermal weak forms. * @@ -353,78 +353,70 @@ buildThermoMechanicsSystem( options, FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100); + auto field_store = std::make_shared(mesh, 100, options.prepend_name); - auto prefix = [&](const std::string& name) { - if (options.prepend_name.empty()) { - return name; - } - return options.prepend_name + "_" + name; - }; - - FieldType> shape_disp_type(prefix("shape_displacement")); + FieldType> shape_disp_type("shape_displacement"); field_store->addShapeDisp(shape_disp_type); // Displacement fields (4-state second-order) auto disp_time_rule_ptr = std::make_shared(disp_rule); - FieldType> disp_type(prefix("displacement_solve_state")); + FieldType> disp_type("displacement_solve_state"); auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); - auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, prefix("displacement")); - auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, prefix("velocity")); - auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, prefix("acceleration")); + auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); + auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); + auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); // Temperature fields (2-state first-order) auto temperature_time_rule_ptr = std::make_shared(temp_rule); - FieldType> temperature_type(prefix("temperature_solve_state")); + FieldType> temperature_type("temperature_solve_state"); auto temperature_bc = field_store->addIndependent(temperature_type, temperature_time_rule_ptr); auto temperature_old_type = - field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, prefix("temperature")); + field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); - (field_store->addParameter(FieldType(prefix("param_" + parameter_types.name))), ...); + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); using SystemType = ThermoMechanicsSystem; - auto sys = std::make_shared(); - sys->field_store = field_store; - sys->solver = solver; - sys->disp_bc = disp_bc; - sys->temperature_bc = temperature_bc; - sys->disp_time_rule = disp_time_rule_ptr; - sys->temperature_time_rule = temperature_time_rule_ptr; // Solid mechanics weak form (u, u_old, v_old, a_old, temp, temp_old, params...) - std::string solid_force_name = prefix("solid_force"); - sys->solid_weak_form = std::make_shared( + std::string solid_force_name = field_store->prefix("solid_force"); + auto solid_weak_form = std::make_shared( solid_force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(solid_force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, temperature_type, temperature_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + accel_old_type, temperature_type, temperature_old_type, parameter_types...)); // Thermal weak form (temp, temp_old, u, u_old, v_old, a_old, params...) - std::string thermal_flux_name = prefix("thermal_flux"); - sys->thermal_weak_form = std::make_shared( + std::string thermal_flux_name = field_store->prefix("thermal_flux"); + auto thermal_weak_form = std::make_shared( thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, - disp_type, disp_old_type, velo_old_type, accel_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + disp_type, disp_old_type, velo_old_type, accel_old_type, parameter_types...)); - sys->weak_forms = {sys->solid_weak_form, sys->thermal_weak_form}; + auto sys = std::make_shared(field_store, solver, + std::vector>{solid_weak_form, thermal_weak_form}); + sys->disp_bc = disp_bc; + sys->temperature_bc = temperature_bc; + sys->disp_time_rule = disp_time_rule_ptr; + sys->temperature_time_rule = temperature_time_rule_ptr; + sys->solid_weak_form = solid_weak_form; + sys->thermal_weak_form = thermal_weak_form; if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - std::string cycle_zero_name = prefix("solid_reaction"); + std::string cycle_zero_name = field_store->prefix("solid_reaction"); sys->cycle_zero_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - temperature_type, temperature_old_type, - FieldType(prefix("param_" + parameter_types.name))...)); + temperature_type, temperature_old_type, parameter_types...)); auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); SLIC_ERROR_IF(cz_solver == nullptr, "Could not derive a cycle-zero solver for block 0 from the provided thermo-mechanics solver."); - sys->cycle_zero_system = std::make_shared(field_store); - sys->cycle_zero_system->solver = cz_solver; - sys->cycle_zero_system->weak_forms = {sys->cycle_zero_weak_form}; + sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_weak_form}); } return sys; From 7ec5a4660eba33a741a4b46b4bd86775f8176d01 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 11 Apr 2026 23:35:04 -0600 Subject: [PATCH 05/67] Document and update gretl tracking capabilities --- gretl | 2 +- gretl_notracking.md | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 gretl_notracking.md diff --git a/gretl b/gretl index fbbbee58b3..6cc8b367ea 160000 --- a/gretl +++ b/gretl @@ -1 +1 @@ -Subproject commit fbbbee58b3d3b97771b107ca6e015a40dcf5e0f6 +Subproject commit 6cc8b367ea9bbe15e293e72203420b093885e34b diff --git a/gretl_notracking.md b/gretl_notracking.md new file mode 100644 index 0000000000..4b631576fd --- /dev/null +++ b/gretl_notracking.md @@ -0,0 +1,37 @@ +# Gretl Graph Tracking Toggle (Phase 5) + +## Overview +This document captures the subtleties and design decisions surrounding the ability to temporarily disable computational graph tracking in `gretl`. This is primarily useful for computing output quantities (like stress visualization) or running preconditioners where we need `gretl::State` for the forward evaluation syntax, but we do not want to record the operations in the autodiff graph or compute adjoints. + +## Mechanism +- **`DataStore::is_tracking()`**: A boolean flag on the `DataStore` that controls whether new states are registered. +- **`ScopedGraphDisable`**: An RAII helper that sets `is_tracking() = false` on construction and restores the previous value on destruction. + +## Subtleties of Untracked States + +1. **Step Index**: When `create_state` is called while tracking is disabled, the returned `State` has its `step` index set to `std::numeric_limits::max()`. This sentinel value indicates the state is untracked. +2. **Primal Storage**: For tracked states, the primal value is stored in the `DataStore` (either directly or flushed to disk via checkpointer) and accessed via `data_store().get_primal(step)`. For an untracked state, `step` is invalid, so the primal value is kept alive purely by the `std::shared_ptr` inside the `State` object itself (`primal_` in `StateBase`). +3. **Dual Values**: Untracked states do not have dual values. Calling `get_dual()` or `set_dual()` on an untracked state will trigger a `gretl_assert`. +4. **Evaluation and VJP**: Untracked states do not record `eval` or `vjp` closures in the `DataStore`. They evaluate their forward value immediately during `create_state` and never participate in `back_prop()`. + +## Open Decisions / Considerations + +### 1. Mixing Untracked Upstreams with Tracked Downstreams +**Current Behavior:** `create_state` asserts if tracking is enabled but any of the provided upstream states are untracked (step == max). +**Why:** A tracked state needs to compute its VJP during backprop. The VJP closure generally expects all upstream states to have duals so it can add sensitivities to them. If an upstream state is untracked, it has no dual, and `get_dual()` would assert. +**Decision to make:** Should we allow untracked states to be used as inputs to tracked states? +- *Option A (Current):* Strictly forbid it. If a user needs to use an untracked value in a tracked computation, they must extract the raw primal value (`state.get()`) and either capture it by value in a custom lambda, or create a new tracked parameter state from it. +- *Option B (Treat as Constants):* Allow it, treating the untracked state as a constant. The challenge is the VJP signature: `vjp(..., upstream_dual...)`. If an upstream is untracked, we would need to pass a dummy dual that absorbs the sensitivity without writing to the `DataStore`. This complicates the `create_state` variadic template metaprogramming. + +### 2. UpstreamState Wrapper +**Current Behavior:** `UpstreamState` wraps a `DataStore*` and a `step`. For untracked states, we had to add a branch in `UpstreamState::get()` to access `dataStore_->any_primal(step_)`. However, `any_primal` assumes the step is valid. +**Issue:** `UpstreamState` doesn't currently hold a reference to the `shared_ptr` primal. So if an untracked state is passed to `UpstreamStates`, it loses access to its data. +**Decision to make:** How should `UpstreamState` handle untracked states? +- *Option A:* If we stick to Decision 1 Option A (forbid mixing), `UpstreamState` will *never* see an untracked state, because they are only created inside tracked `create_state` calls. If `create_state` is untracked, it executes the raw `eval` lambda immediately using the `State` objects directly, not the `UpstreamState` wrapper. Thus, `UpstreamState` doesn't need to support untracked states. +- *Option B:* If we want `UpstreamState` to support untracked states, it must either hold the `shared_ptr` or we must store untracked primals somewhere in the `DataStore` (e.g., a separate list of untracked values). + +### 3. Cloning vs Creating +When tracking is disabled, `clone_state` also evaluates the forward pass immediately. It requires the `eval` function to be passed in. + +## Recommendation +For now, the strictest path (forbidding untracked states as inputs to tracked states) is the safest and requires the least amount of invasive plumbing in the metaprogramming of `create_state`. If a user is in a "tracking disabled" region, all intermediate states they create are untracked. They cannot "re-enter" the tracked graph by passing those untracked states back into tracked states. If they must, they should extract the primal values and inject them as new tracked parameters. From 25645723eb43c5e8f25b223f10fb7438eed35a33 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 11 Apr 2026 23:44:57 -0600 Subject: [PATCH 06/67] Update gretl_notracking.md with the Graph-Resident but Ephemeral plan --- gretl_notracking.md | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/gretl_notracking.md b/gretl_notracking.md index 4b631576fd..4e134b31b1 100644 --- a/gretl_notracking.md +++ b/gretl_notracking.md @@ -1,37 +1,37 @@ # Gretl Graph Tracking Toggle (Phase 5) ## Overview -This document captures the subtleties and design decisions surrounding the ability to temporarily disable computational graph tracking in `gretl`. This is primarily useful for computing output quantities (like stress visualization) or running preconditioners where we need `gretl::State` for the forward evaluation syntax, but we do not want to record the operations in the autodiff graph or compute adjoints. +This document captures the design decisions for the ability to temporarily disable computational graph tracking in `gretl`. This is primarily useful for computing output quantities (like stress visualization) or running preconditioners where we need `gretl::State` for the forward evaluation syntax, but we do not want to record the operations in the autodiff graph or compute adjoints. -## Mechanism -- **`DataStore::is_tracking()`**: A boolean flag on the `DataStore` that controls whether new states are registered. +## The "Graph-Resident but Ephemeral" Plan (Current Direction) + +Instead of completely bypassing graph registration (which breaks `step` indexing and prevents mixing), the new plan is to register "untracked" states in the graph so they receive a valid `step` index, but treat them as **"Stop-Gradient" / Ephemeral states**. + +### Mechanism +- **`DataStore::is_tracking()`**: A boolean flag on the `DataStore` that controls the behavior of newly created states. - **`ScopedGraphDisable`**: An RAII helper that sets `is_tracking() = false` on construction and restores the previous value on destruction. -## Subtleties of Untracked States +### Properties of Untracked States -1. **Step Index**: When `create_state` is called while tracking is disabled, the returned `State` has its `step` index set to `std::numeric_limits::max()`. This sentinel value indicates the state is untracked. -2. **Primal Storage**: For tracked states, the primal value is stored in the `DataStore` (either directly or flushed to disk via checkpointer) and accessed via `data_store().get_primal(step)`. For an untracked state, `step` is invalid, so the primal value is kept alive purely by the `std::shared_ptr` inside the `State` object itself (`primal_` in `StateBase`). -3. **Dual Values**: Untracked states do not have dual values. Calling `get_dual()` or `set_dual()` on an untracked state will trigger a `gretl_assert`. -4. **Evaluation and VJP**: Untracked states do not record `eval` or `vjp` closures in the `DataStore`. They evaluate their forward value immediately during `create_state` and never participate in `back_prop()`. +1. **Graph Presence**: When `create_state` is called while tracking is disabled, the state *is* added to the graph. It receives a valid `step` index, and it is added to `states_` and `upstreamSteps_`. +2. **VJP Skipping (Stop-Gradient)**: The core optimization is that we **never** call the `vjp` function for these untracked states during back-propagation. We can either register a no-op VJP or explicitly skip them in the reverse pass loop. This saves all the computational cost of propagating sensitivities through the untracked region. +3. **Duals**: Because they have a valid `step`, their duals can be safely allocated in `duals_`. If a tracked downstream depends on an untracked upstream, the downstream's VJP can safely add into the untracked state's dual. The dual accumulates, but is simply ignored (since the untracked state's VJP is never called to read it). +4. **Checkpointing & Memory Management**: These states are intentionally excluded from the dynamic checkpointing strategy. They are not scheduled for recomputation or swap-to-disk. + - Their primal value memory is tied directly to their external usage. + - They will be instantly ejected from memory (primal freed) when there are no outside handles left (i.e., when the `shared_ptr`'s `use_count()` drops, tracked via `wild_count()`). -## Open Decisions / Considerations +## Open Design Decisions & Subtleties -### 1. Mixing Untracked Upstreams with Tracked Downstreams -**Current Behavior:** `create_state` asserts if tracking is enabled but any of the provided upstream states are untracked (step == max). -**Why:** A tracked state needs to compute its VJP during backprop. The VJP closure generally expects all upstream states to have duals so it can add sensitivities to them. If an upstream state is untracked, it has no dual, and `get_dual()` would assert. -**Decision to make:** Should we allow untracked states to be used as inputs to tracked states? -- *Option A (Current):* Strictly forbid it. If a user needs to use an untracked value in a tracked computation, they must extract the raw primal value (`state.get()`) and either capture it by value in a custom lambda, or create a new tracked parameter state from it. -- *Option B (Treat as Constants):* Allow it, treating the untracked state as a constant. The challenge is the VJP signature: `vjp(..., upstream_dual...)`. If an upstream is untracked, we would need to pass a dummy dual that absorbs the sensitivity without writing to the `DataStore`. This complicates the `create_state` variadic template metaprogramming. +### The "Primal Availability" Catch +If an untracked state is purely an output (like stress visualization), ejecting its primal when the user's handle goes out of scope is perfectly safe. -### 2. UpstreamState Wrapper -**Current Behavior:** `UpstreamState` wraps a `DataStore*` and a `step`. For untracked states, we had to add a branch in `UpstreamState::get()` to access `dataStore_->any_primal(step_)`. However, `any_primal` assumes the step is valid. -**Issue:** `UpstreamState` doesn't currently hold a reference to the `shared_ptr` primal. So if an untracked state is passed to `UpstreamStates`, it loses access to its data. -**Decision to make:** How should `UpstreamState` handle untracked states? -- *Option A:* If we stick to Decision 1 Option A (forbid mixing), `UpstreamState` will *never* see an untracked state, because they are only created inside tracked `create_state` calls. If `create_state` is untracked, it executes the raw `eval` lambda immediately using the `State` objects directly, not the `UpstreamState` wrapper. Thus, `UpstreamState` doesn't need to support untracked states. -- *Option B:* If we want `UpstreamState` to support untracked states, it must either hold the `shared_ptr` or we must store untracked primals somewhere in the `DataStore` (e.g., a separate list of untracked values). +However, if we **mix untracked upstreams with tracked downstreams**, a subtlety arises during back-propagation: +- The tracked downstream's VJP will likely need to read the untracked state's *primal* value to compute the derivative (`inputs[i].get()`). +- If the untracked state is excluded from checkpointing/recomputation, and the user has let their external `State` handle go out of scope, the primal will have been eagerly freed. Calling `get()` during the reverse pass will crash. -### 3. Cloning vs Creating -When tracking is disabled, `clone_state` also evaluates the forward pass immediately. It requires the `eval` function to be passed in. +**How do we handle the memory lifetime of an untracked upstream that has a tracked downstream?** +* **Option A (Implicit Promotion/Pinning):** When an untracked state is added as an upstream to a *tracked* state, the graph increments an internal `tracked_usage_count`. Eager freeing is only allowed if `wild_count() == 0` AND `tracked_usage_count == 0`. This effectively pins the untracked primal in memory for the duration of the backprop. +* **Option B (User Responsibility):** Rely on the user to hold the `State` handle alive until `back_prop()` is called. If they let it go out of scope, it's an error. +* **Option C (Fallback Checkpointing):** Do not completely remove them from the checkpointing view. Just flag them as "no-vjp". The checkpointer would treat them like constant parameters that need to be kept alive if downstreams need them. -## Recommendation -For now, the strictest path (forbidding untracked states as inputs to tracked states) is the safest and requires the least amount of invasive plumbing in the metaprogramming of `create_state`. If a user is in a "tracking disabled" region, all intermediate states they create are untracked. They cannot "re-enter" the tracked graph by passing those untracked states back into tracked states. If they must, they should extract the primal values and inject them as new tracked parameters. +Option A or C seems the most robust, as it maintains the `gretl` philosophy of the graph managing data availability invisibly to the user. Option A is essentially treating the untracked state as a persistent parameter if it crosses the boundary back into the tracked graph. From ed004cd1bbe36886aafdb7f5a99fd1c46bdb034e Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sun, 12 Apr 2026 00:18:23 -0600 Subject: [PATCH 07/67] Update gretl_notracking.md with the finalized Stop-Gradient approach --- gretl_notracking.md | 38 +++++++++++++++----------------------- 1 file changed, 15 insertions(+), 23 deletions(-) diff --git a/gretl_notracking.md b/gretl_notracking.md index 4e134b31b1..d4a5817d12 100644 --- a/gretl_notracking.md +++ b/gretl_notracking.md @@ -1,37 +1,29 @@ # Gretl Graph Tracking Toggle (Phase 5) ## Overview -This document captures the design decisions for the ability to temporarily disable computational graph tracking in `gretl`. This is primarily useful for computing output quantities (like stress visualization) or running preconditioners where we need `gretl::State` for the forward evaluation syntax, but we do not want to record the operations in the autodiff graph or compute adjoints. +This document captures the design decisions for the ability to temporarily disable computational graph tracking in `gretl`. This is primarily useful for computing output quantities (like stress visualization) or running preconditioners where we need `gretl::State` for the forward evaluation syntax, but we do not want to compute adjoints or propagate sensitivities through these operations. -## The "Graph-Resident but Ephemeral" Plan (Current Direction) +## The "Stop-Gradient" Plan (Finalized) -Instead of completely bypassing graph registration (which breaks `step` indexing and prevents mixing), the new plan is to register "untracked" states in the graph so they receive a valid `step` index, but treat them as **"Stop-Gradient" / Ephemeral states**. +Instead of completely bypassing graph registration (which breaks `step` indexing, memory management, and prevents mixing), we register states created in an "untracked" context normally in the graph, but replace their Vector-Jacobian Product (VJP) function with a **no-op**. + +They effectively act as "Stop-Gradient" or `.detach()` nodes in the computational graph. ### Mechanism - **`DataStore::is_tracking()`**: A boolean flag on the `DataStore` that controls the behavior of newly created states. - **`ScopedGraphDisable`**: An RAII helper that sets `is_tracking() = false` on construction and restores the previous value on destruction. -### Properties of Untracked States +### Properties of "Untracked" (Stop-Gradient) States 1. **Graph Presence**: When `create_state` is called while tracking is disabled, the state *is* added to the graph. It receives a valid `step` index, and it is added to `states_` and `upstreamSteps_`. -2. **VJP Skipping (Stop-Gradient)**: The core optimization is that we **never** call the `vjp` function for these untracked states during back-propagation. We can either register a no-op VJP or explicitly skip them in the reverse pass loop. This saves all the computational cost of propagating sensitivities through the untracked region. -3. **Duals**: Because they have a valid `step`, their duals can be safely allocated in `duals_`. If a tracked downstream depends on an untracked upstream, the downstream's VJP can safely add into the untracked state's dual. The dual accumulates, but is simply ignored (since the untracked state's VJP is never called to read it). -4. **Checkpointing & Memory Management**: These states are intentionally excluded from the dynamic checkpointing strategy. They are not scheduled for recomputation or swap-to-disk. - - Their primal value memory is tied directly to their external usage. - - They will be instantly ejected from memory (primal freed) when there are no outside handles left (i.e., when the `shared_ptr`'s `use_count()` drops, tracked via `wild_count()`). - -## Open Design Decisions & Subtleties - -### The "Primal Availability" Catch -If an untracked state is purely an output (like stress visualization), ejecting its primal when the user's handle goes out of scope is perfectly safe. - -However, if we **mix untracked upstreams with tracked downstreams**, a subtlety arises during back-propagation: -- The tracked downstream's VJP will likely need to read the untracked state's *primal* value to compute the derivative (`inputs[i].get()`). -- If the untracked state is excluded from checkpointing/recomputation, and the user has let their external `State` handle go out of scope, the primal will have been eagerly freed. Calling `get()` during the reverse pass will crash. +2. **Forward Evaluation & Checkpointing**: The forward pass executes exactly the same way. The state participates in dynamic checkpointing logic if necessary. +3. **VJP Skipping (Stop-Gradient)**: The core optimization is that we assign a **no-op VJP closure** to these states during creation. When back-propagation reaches this state, the no-op VJP is called, which simply returns immediately without accumulating any sensitivities into its upstream dependencies. +4. **Duals & Mixing**: Because they have a `step`, a tracked downstream can safely use them as an input. The downstream's VJP will accumulate derivatives into this untracked state's dual, which is perfectly safe. The dual exists and accumulates, but the sensitivity chain dies there because the untracked state's own VJP is a no-op. -**How do we handle the memory lifetime of an untracked upstream that has a tracked downstream?** -* **Option A (Implicit Promotion/Pinning):** When an untracked state is added as an upstream to a *tracked* state, the graph increments an internal `tracked_usage_count`. Eager freeing is only allowed if `wild_count() == 0` AND `tracked_usage_count == 0`. This effectively pins the untracked primal in memory for the duration of the backprop. -* **Option B (User Responsibility):** Rely on the user to hold the `State` handle alive until `back_prop()` is called. If they let it go out of scope, it's an error. -* **Option C (Fallback Checkpointing):** Do not completely remove them from the checkpointing view. Just flag them as "no-vjp". The checkpointer would treat them like constant parameters that need to be kept alive if downstreams need them. +### Advantages +- **Simplicity**: No changes to `DataStore` memory management, dynamic checkpointing, or primal/dual indexing logic. +- **Mixing Support**: Tracked states and untracked states can be mixed seamlessly. +- **Performance**: Achieves the primary user goal—skipping the costly back-propagation of operations they don't care about (e.g., preconditioners, output processing). -Option A or C seems the most robust, as it maintains the `gretl` philosophy of the graph managing data availability invisibly to the user. Option A is essentially treating the untracked state as a persistent parameter if it crosses the boundary back into the tracked graph. +### Subtleties +Because the forward pass and dynamic checkpointing remain essentially unchanged, operations inside a `ScopedGraphDisable` block will still consume memory in the checkpointer's active working set and will still be recomputed if evicted. The savings are strictly in the backward pass. From 35a8df8c0fadb75244f1179469277601e8f9319d Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sun, 12 Apr 2026 11:20:50 -0600 Subject: [PATCH 08/67] Update gretl_notracking.md reflecting removal of ScopedGraphDisable --- gretl_notracking.md | 36 ++++++++++++++++++++++++++---------- 1 file changed, 26 insertions(+), 10 deletions(-) diff --git a/gretl_notracking.md b/gretl_notracking.md index d4a5817d12..84f664bfc3 100644 --- a/gretl_notracking.md +++ b/gretl_notracking.md @@ -1,29 +1,45 @@ -# Gretl Graph Tracking Toggle (Phase 5) +# Gretl Stop-Gradient Feature (Phase 5) ## Overview -This document captures the design decisions for the ability to temporarily disable computational graph tracking in `gretl`. This is primarily useful for computing output quantities (like stress visualization) or running preconditioners where we need `gretl::State` for the forward evaluation syntax, but we do not want to compute adjoints or propagate sensitivities through these operations. +This document captures the design decisions for the ability to compute output quantities (like stress visualization) or run preconditioners using `gretl::State` for the forward evaluation syntax, without computing adjoints or propagating sensitivities backward through these operations. ## The "Stop-Gradient" Plan (Finalized) -Instead of completely bypassing graph registration (which breaks `step` indexing, memory management, and prevents mixing), we register states created in an "untracked" context normally in the graph, but replace their Vector-Jacobian Product (VJP) function with a **no-op**. +To achieve this without breaking the graph's internal indexing or dynamic checkpointing logic, we register states normally in the graph, but replace their Vector-Jacobian Product (VJP) function with a **no-op**. They effectively act as "Stop-Gradient" or `.detach()` nodes in the computational graph. ### Mechanism -- **`DataStore::is_tracking()`**: A boolean flag on the `DataStore` that controls the behavior of newly created states. -- **`ScopedGraphDisable`**: An RAII helper that sets `is_tracking() = false` on construction and restores the previous value on destruction. +- **`DataStore::gradients_enabled()`**: A boolean flag on the `DataStore` that controls whether newly created states record their actual VJP closure or a no-op. +- **`DataStore::set_gradients_enabled(bool)`**: A simple setter to toggle this behavior on or off. -### Properties of "Untracked" (Stop-Gradient) States +### Example Usage +```cpp +data_store.set_gradients_enabled(false); -1. **Graph Presence**: When `create_state` is called while tracking is disabled, the state *is* added to the graph. It receives a valid `step` index, and it is added to `states_` and `upstreamSteps_`. +// This state is added to the graph, but its VJP is replaced with a no-op +auto s_out = create_state( + [](const double&) { return 0.0; }, + [](const double& a, const double& b) { return a * b; }, + [](const double&, const double&, const double&, double&, double&, const double&) { + // This will never be called during back_prop() + }, + s1, s2); + +data_store.set_gradients_enabled(true); +``` + +### Properties of "Stop-Gradient" States + +1. **Graph Presence**: The state *is* added to the graph normally. It receives a valid `step` index, and it is added to `states_` and `upstreamSteps_`. 2. **Forward Evaluation & Checkpointing**: The forward pass executes exactly the same way. The state participates in dynamic checkpointing logic if necessary. 3. **VJP Skipping (Stop-Gradient)**: The core optimization is that we assign a **no-op VJP closure** to these states during creation. When back-propagation reaches this state, the no-op VJP is called, which simply returns immediately without accumulating any sensitivities into its upstream dependencies. -4. **Duals & Mixing**: Because they have a `step`, a tracked downstream can safely use them as an input. The downstream's VJP will accumulate derivatives into this untracked state's dual, which is perfectly safe. The dual exists and accumulates, but the sensitivity chain dies there because the untracked state's own VJP is a no-op. +4. **Duals & Mixing**: Because they have a `step`, a tracked downstream can safely use them as an input. The downstream's VJP will accumulate derivatives into this stop-gradient state's dual, which is perfectly safe. The dual exists and accumulates, but the sensitivity chain dies there because the stop-gradient state's own VJP is a no-op. ### Advantages - **Simplicity**: No changes to `DataStore` memory management, dynamic checkpointing, or primal/dual indexing logic. -- **Mixing Support**: Tracked states and untracked states can be mixed seamlessly. +- **Mixing Support**: Tracked states and stop-gradient states can be mixed seamlessly. - **Performance**: Achieves the primary user goal—skipping the costly back-propagation of operations they don't care about (e.g., preconditioners, output processing). ### Subtleties -Because the forward pass and dynamic checkpointing remain essentially unchanged, operations inside a `ScopedGraphDisable` block will still consume memory in the checkpointer's active working set and will still be recomputed if evicted. The savings are strictly in the backward pass. +Because the forward pass and dynamic checkpointing remain essentially unchanged, operations between `set_gradients_enabled(false)` and `set_gradients_enabled(true)` will still consume memory in the checkpointer's active working set and will still be recomputed if evicted. The savings are strictly in the backward pass. From 533b1e2fe5678894335f9d9ecf06010d511bf0d2 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sun, 12 Apr 2026 14:05:00 -0600 Subject: [PATCH 09/67] Update gretl_notracking.md with the no-recompute optimization --- gretl_notracking.md | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/gretl_notracking.md b/gretl_notracking.md index 84f664bfc3..d6abd608f3 100644 --- a/gretl_notracking.md +++ b/gretl_notracking.md @@ -1,7 +1,7 @@ # Gretl Stop-Gradient Feature (Phase 5) ## Overview -This document captures the design decisions for the ability to compute output quantities (like stress visualization) or run preconditioners using `gretl::State` for the forward evaluation syntax, without computing adjoints or propagating sensitivities backward through these operations. +This document captures the design decisions for the ability to compute output quantities (like stress visualization) or run preconditioners/iterative solvers using `gretl::State` for the forward evaluation syntax, without computing adjoints or propagating sensitivities backward through these operations. ## The "Stop-Gradient" Plan (Finalized) @@ -13,33 +13,37 @@ They effectively act as "Stop-Gradient" or `.detach()` nodes in the computationa - **`DataStore::gradients_enabled()`**: A boolean flag on the `DataStore` that controls whether newly created states record their actual VJP closure or a no-op. - **`DataStore::set_gradients_enabled(bool)`**: A simple setter to toggle this behavior on or off. -### Example Usage +### Important Optimization for Checkpointing +If gradients are disabled, `gretl` skips calling `fetch_state_data()` during the backprop pass. This prevents costly dynamic recomputations of evicted states just to feed a no-op VJP. + +### Example Usage: Picard Iteration +A key use-case is performing intermediate iterative solves without tracking every step, then performing one final tracked step to capture parameter sensitivities. + ```cpp data_store.set_gradients_enabled(false); -// This state is added to the graph, but its VJP is replaced with a no-op -auto s_out = create_state( - [](const double&) { return 0.0; }, - [](const double& a, const double& b) { return a * b; }, - [](const double&, const double&, const double&, double&, double&, const double&) { - // This will never be called during back_prop() - }, - s1, s2); +// Iterate without tracking gradients (stop-gradient nodes) +for (int i = 0; i < 10; ++i) { + x = create_state(..., x, p); +} +// Re-enable gradients for the final step data_store.set_gradients_enabled(true); + +// One final iteration to connect the parameter sensitivity +auto x_final = create_state(..., x, p); ``` +During `back_prop()`, `x_final` will correctly pass gradient information to `p`. However, the gradient passed to the 10th intermediate step `x` will hit a no-op VJP and stop, saving us from recomputing or propagating derivatives through the 10 loop iterations. + ### Properties of "Stop-Gradient" States 1. **Graph Presence**: The state *is* added to the graph normally. It receives a valid `step` index, and it is added to `states_` and `upstreamSteps_`. 2. **Forward Evaluation & Checkpointing**: The forward pass executes exactly the same way. The state participates in dynamic checkpointing logic if necessary. -3. **VJP Skipping (Stop-Gradient)**: The core optimization is that we assign a **no-op VJP closure** to these states during creation. When back-propagation reaches this state, the no-op VJP is called, which simply returns immediately without accumulating any sensitivities into its upstream dependencies. +3. **VJP Skipping (Stop-Gradient)**: The core optimization is that we assign a **no-op VJP closure** to these states during creation. When back-propagation reaches this state, the no-op VJP is skipped, preventing recomputations. 4. **Duals & Mixing**: Because they have a `step`, a tracked downstream can safely use them as an input. The downstream's VJP will accumulate derivatives into this stop-gradient state's dual, which is perfectly safe. The dual exists and accumulates, but the sensitivity chain dies there because the stop-gradient state's own VJP is a no-op. ### Advantages - **Simplicity**: No changes to `DataStore` memory management, dynamic checkpointing, or primal/dual indexing logic. - **Mixing Support**: Tracked states and stop-gradient states can be mixed seamlessly. -- **Performance**: Achieves the primary user goal—skipping the costly back-propagation of operations they don't care about (e.g., preconditioners, output processing). - -### Subtleties -Because the forward pass and dynamic checkpointing remain essentially unchanged, operations between `set_gradients_enabled(false)` and `set_gradients_enabled(true)` will still consume memory in the checkpointer's active working set and will still be recomputed if evicted. The savings are strictly in the backward pass. +- **Performance**: Achieves the primary user goal—skipping the costly back-propagation (and checkpointer recomputations) of operations they don't care about. From 6797361386cfea2752b33001349fb429a37892d4 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 13 Apr 2026 14:48:36 -0600 Subject: [PATCH 10/67] We are trying to usable stress output as an option at the end of solid dynamic steps. --- gretl | 2 +- .../dirichlet_boundary_conditions.cpp | 14 ++ .../dirichlet_boundary_conditions.hpp | 7 + .../differentiable_numerics/field_store.cpp | 92 ++++++++++--- .../differentiable_numerics/field_store.hpp | 126 ++++++++++-------- .../multiphysics_time_integrator.cpp | 80 +++++++++-- .../multiphysics_time_integrator.hpp | 14 +- .../solid_mechanics_system.hpp | 111 ++++++++++++++- ...id_mechanics_with_internal_vars_system.hpp | 35 +++-- .../differentiable_numerics/system_base.cpp | 2 +- .../test_multiphysics_time_integrator.cpp | 6 +- .../tests/test_solid_dynamics.cpp | 7 +- .../thermo_mechanics_system.hpp | 34 ++++- 13 files changed, 414 insertions(+), 116 deletions(-) diff --git a/gretl b/gretl index 6cc8b367ea..c1d8af52ac 160000 --- a/gretl +++ b/gretl @@ -1 +1 @@ -Subproject commit 6cc8b367ea9bbe15e293e72203420b093885e34b +Subproject commit c1d8af52ac7c26ddf30a52b8cbfd4ddf977c7a91 diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp index 0834dc9785..0ee42cdd0d 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp @@ -6,6 +6,7 @@ #include "smith/physics/mesh.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" +#include "smith/physics/boundary_conditions/boundary_condition_manager.hpp" namespace smith { @@ -20,4 +21,17 @@ DirichletBoundaryConditions::DirichletBoundaryConditions(const Mesh& mesh, mfem: { } +void DirichletBoundaryConditions::setZeroBCsMatchingDofs(const BoundaryConditionManager& source) +{ + const auto& true_dofs = source.allEssentialTrueDofs(); + if (true_dofs.Size() == 0) { + return; + } + int vdim = space_.GetVDim(); + mfem::Vector zero_vec(vdim); + zero_vec = 0.0; + auto zero_coef = std::make_shared(zero_vec); + bcs_.addEssentialByTrueDofs(true_dofs, zero_coef, space_); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp index dde451722b..f32a786cc5 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp @@ -161,6 +161,13 @@ class DirichletBoundaryConditions { /// @brief Return the smith BoundaryConditionManager const smith::BoundaryConditionManager& getBoundaryConditionManager() const { return bcs_; } + /// @brief Constrain the same true DOFs as @p source, but with zero prescribed values. + /// + /// Used for the cycle-zero acceleration BC: the constrained DOF set mirrors the displacement BC + /// (same mesh nodes, same components), but the prescribed acceleration is zero everywhere. + /// Must be called after the user has finished calling @c set*BCs on @p source. + void setZeroBCsMatchingDofs(const BoundaryConditionManager& source); + private: smith::BoundaryConditionManager bcs_; ///< boundary condition manager that does the heavy lifting mfem::ParFiniteElementSpace& space_; ///< save the space for the field which will be constrained diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 5cf77fc930..6b715b1eab 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -28,8 +28,12 @@ std::string FieldStore::prefix(const std::string& base) const std::shared_ptr FieldStore::addBoundaryConditions(FEFieldPtr field) { - boundary_conditions_.push_back(std::make_shared(mesh_->mfemParMesh(), field->space())); - return boundary_conditions_.back(); + return std::make_shared(mesh_->mfemParMesh(), field->space()); +} + +void FieldStore::shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc) +{ + zero_mirror_sources_[name] = std::move(source_bc); } void FieldStore::addWeakFormUnknownArg(std::string weak_form_name, std::string argument_name, size_t argument_index) @@ -67,40 +71,92 @@ void FieldStore::printMap() std::vector> FieldStore::indexMap(const std::vector& residual_names) const { - std::vector> block_indices(residual_names.size()); + // Build a local column space: each residual in the subsystem contributes one local column, + // corresponding to its "self" diagonal unknown. The self-unknown is preferably the residual's + // reaction (test) field if that field appears in the unknown-arg list for this weak form; + // otherwise fall back on the first unknown argument (handles cases like the cycle-zero + // acceleration solve, where the reaction field is a dependent/history field). + std::map global_state_to_local_col; + for (size_t res_i = 0; res_i < residual_names.size(); ++res_i) { + const std::string& res_name = residual_names[res_i]; + size_t global_state_idx = invalid_block_index; + + std::string reaction_name; + for (const auto& kv : weak_form_to_test_field_) { + if (kv.first == res_name) { + reaction_name = kv.second; + break; + } + } + + // Check if the reaction field is one of the registered unknown args for this weak form. + bool reaction_is_unknown = false; + if (!reaction_name.empty() && weak_form_name_to_unknown_name_index_.count(res_name)) { + for (const auto& label : weak_form_name_to_unknown_name_index_.at(res_name)) { + if (label.field_name == reaction_name) { + reaction_is_unknown = true; + break; + } + } + } + + if (reaction_is_unknown) { + global_state_idx = to_states_index_.at(reaction_name); + } else { + const auto& arg_info = weak_form_name_to_unknown_name_index_.at(res_name); + SLIC_ERROR_IF(arg_info.empty(), + "Weak form '" << res_name << "' has no unknown arguments; cannot build index map."); + global_state_idx = to_states_index_.at(arg_info.front().field_name); + } + global_state_to_local_col[global_state_idx] = res_i; + } + std::vector> block_indices(residual_names.size()); for (size_t res_i = 0; res_i < residual_names.size(); ++res_i) { std::vector& res_indices = block_indices[res_i]; - res_indices = std::vector(num_unknowns_, invalid_block_index); + res_indices = std::vector(residual_names.size(), invalid_block_index); const std::string& res_name = residual_names[res_i]; const auto& arg_info = weak_form_name_to_unknown_name_index_.at(res_name); for (const auto& field_name_and_arg_index : arg_info) { - const std::string field_name = field_name_and_arg_index.field_name; - size_t unknown_index = to_unknown_index_.at(field_name); - SLIC_ASSERT(unknown_index < num_unknowns_); - res_indices[unknown_index] = field_name_and_arg_index.field_index_in_residual; + size_t global_state_index = to_states_index_.at(field_name_and_arg_index.field_name); + auto it = global_state_to_local_col.find(global_state_index); + if (it != global_state_to_local_col.end()) { + res_indices[it->second] = field_name_and_arg_index.field_index_in_residual; + } + // else: field belongs to a different subsystem; treat as fixed input here. } } return block_indices; } -std::vector FieldStore::getBoundaryConditionManagers() const +std::vector +FieldStore::getBoundaryConditionManagers(const std::vector& weak_form_names) { std::vector bcs; - for (auto& bc : boundary_conditions_) { - bcs.push_back(&bc->getBoundaryConditionManager()); + for (const auto& wf_name : weak_form_names) { + const std::string reaction_name = getWeakFormReaction(wf_name); + + // Lazily materialize any pending zero-mirror BCs on first access. + auto mirror_it = zero_mirror_sources_.find(reaction_name); + if (mirror_it != zero_mirror_sources_.end()) { + auto zero_bc = addBoundaryConditions(getField(reaction_name).get()); + zero_bc->setZeroBCsMatchingDofs(mirror_it->second->getBoundaryConditionManager()); + boundary_conditions_[reaction_name] = std::move(zero_bc); + zero_mirror_sources_.erase(mirror_it); + } + + auto it = boundary_conditions_.find(reaction_name); + if (it != boundary_conditions_.end()) { + bcs.push_back(&it->second->getBoundaryConditionManager()); + } else { + bcs.push_back(nullptr); + } } return bcs; } -std::shared_ptr FieldStore::getBoundaryConditions(size_t unknown_index) const -{ - SLIC_ERROR_IF(unknown_index >= boundary_conditions_.size(), "Unknown index out of bounds for boundary conditions"); - return boundary_conditions_[unknown_index]; -} - size_t FieldStore::getFieldIndex(const std::string& field_name) const { if (to_states_index_.count(field_name)) { @@ -227,8 +283,6 @@ FieldStore::getTimeIntegrationRules() const return time_integration_rules_; } -size_t FieldStore::getUnknownIndex(const std::string& field_name) const { return to_unknown_index_.at(field_name); } - void FieldStore::setField(size_t index, FieldState updated_field) { states_[index] = updated_field; } void FieldStore::addWeakFormReaction(std::string weak_form_name, std::string field_name) diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 6ea91aaf38..d4c57e38a0 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -30,7 +30,15 @@ struct ReactionInfo { }; /** - * @brief Representation of a field type with a name and an optional unknown index. + * @brief Representation of a field type with a name and a flag indicating whether it is an + * active Jacobian unknown in the current weak-form context. + * + * @c is_unknown is intentionally a per-instance flag, not a global property. The same field + * may be a Jacobian variable in one weak form (e.g. displacement in the main solid solve) and + * a fixed input in another (e.g. displacement in the stress projection). Callers control this + * by passing the same @c FieldType object (set by @c addIndependent) or a plain copy with the + * default @c is_unknown = false. + * * @tparam Space The finite element space type. * @tparam Time The time integration type (unused by default). */ @@ -39,11 +47,11 @@ struct FieldType { /** * @brief Construct a new FieldType object. * @param n Name of the field. - * @param unknown_index_ Index of the unknown in the solver (default: -1). + * @param is_unknown_ Whether this field is a Jacobian unknown in the current context. */ - FieldType(std::string n, int unknown_index_ = -1) : name(n), unknown_index(unknown_index_) {} - std::string name; ///< Name of the field. - int unknown_index; ///< Index of the unknown in the solver. + FieldType(std::string n, bool is_unknown_ = false) : name(n), is_unknown(is_unknown_) {} + std::string name; ///< Name of the field. + bool is_unknown = false; ///< True if this field is a Jacobian variable in the current weak-form context. }; /** @@ -106,14 +114,13 @@ struct FieldStore { /** * @brief Add an independent field (a solver unknown) to the store. * - * Registers the field as an unknown and assigns it an index in the solver's block structure. - * The @p type argument is mutated in-place: its @c unknown_index is set to the assigned index, - * so the same @c FieldType object can later be passed to @c createSpaces to tell the - * weak form that this argument is an active unknown (i.e. the Jacobian should be computed - * with respect to it). + * Registers the field as an unknown by setting @c type.is_unknown = true, so the same + * @c FieldType object can later be passed to @c createSpaces to mark this argument + * as an active Jacobian variable. Also creates a boundary-condition slot keyed by field + * name that callers can populate after this call returns. * * @tparam Space The finite element space type. - * @param type The field type specification; @c type.unknown_index is set on return. + * @param type The field type specification; @c type.is_unknown is set to @c true on return. * @param time_rule The time integration rule governing how this unknown and its dependents * are related across time steps. * @return std::shared_ptr The boundary conditions for this field. @@ -123,16 +130,13 @@ struct FieldStore { std::shared_ptr time_rule) { type.name = prefix(type.name); - type.unknown_index = static_cast(num_unknowns_); + type.is_unknown = true; to_states_index_[type.name] = states_.size(); - to_unknown_index_[type.name] = num_unknowns_; FieldState new_field = smith::createFieldState(*graph_, Space{}, type.name, mesh_->tag()); states_.push_back(new_field); is_solve_state_.push_back(true); auto latest_bc = addBoundaryConditions(new_field.get()); - ++num_unknowns_; - SLIC_ERROR_IF(num_unknowns_ != boundary_conditions_.size(), - "Inconcistency between num unknowns and boundary condition size"); + boundary_conditions_[type.name] = latest_bc; SLIC_ERROR_IF(!time_rule, "Invalid time_rule"); @@ -154,23 +158,17 @@ struct FieldStore { * (predicted_value, stored_old_value). * * **Return value:** a @c FieldType whose @c name is the name of the newly registered - * field. This object is intentionally returned (rather than discarded) so that callers can - * pass it directly to @c createSpaces when assembling the weak-form argument list. The - * position at which a @c FieldType appears in that argument list determines which slot of the - * lambda the field occupies at quadrature-point evaluation time, which is how the time - * integration rules reconstruct quantities such as - * @f$ \dot{\alpha} = (\alpha_\text{predicted} - \alpha_\text{old}) / \Delta t @f$. - * - * The field name is derived automatically from the independent field's name plus a suffix that - * reflects the derivative level (@c _old for VALUE, @c _dot_old for DOT, - * @c _ddot_old for DDOT), unless @p name_override is supplied. + * field and @c is_unknown is @c false. This object is intentionally returned (rather than + * discarded) so that callers can pass it directly to @c createSpaces when assembling the + * weak-form argument list. To make it the Jacobian variable for a specific weak form (e.g. + * the cycle-zero acceleration solve), copy the returned object and set @c is_unknown = true + * before passing to @c createSpaces. * * @tparam Space The finite element space type (must match the independent field). * @param independent_field The @c FieldType of the independent (predicted) field. * @param derivative Which time-derivative level this history field stores. * @param name_override If non-empty, use this as the field name instead of the auto-generated one. - * @return FieldType Type descriptor for the newly created dependent field; pass this to - * @c createSpaces to register it as a weak-form argument. + * @return FieldType Type descriptor for the newly created dependent field. */ template auto addDependent(FieldType independent_field, TimeDerivative derivative, std::string name_override = "") @@ -225,12 +223,6 @@ struct FieldStore { */ void addWeakFormArg(std::string weak_form_name, std::string argument_name, size_t argument_index); - /** - * @brief Get the number of unknowns in the field store. - * @return size_t Number of unknowns. - */ - size_t getNumUnknowns() const { return num_unknowns_; } - /** * @brief Register the reaction (test) field for a weak form. * @@ -256,9 +248,17 @@ struct FieldStore { * This is the primary setup method for constructing a weak form. It: * 1. Registers @p reaction_field_name as the reaction/test field via @c addWeakFormReaction. * 2. Iterates over every @c FieldType in @p types (in order), registering each as an input - * argument to the weak form and recording whether it is an active unknown. - * 3. Returns the ordered vector of finite element spaces, which can be passed directly to - * the @c TimeDiscretizedWeakForm constructor without creating a named temporary. + * argument. If @c type.is_unknown is @c true, the field is also registered as an active + * Jacobian unknown for this weak form. + * 3. Returns the ordered vector of finite element spaces. + * + * A field may have @c is_unknown = true in one weak form and @c false in another (e.g. + * displacement is a Jacobian variable in the main solid solve but a fixed input in the stress + * projection). Callers control this by passing the @c FieldType returned from @c addIndependent + * (has @c is_unknown = true) or a plain copy constructed from the field name (has @c is_unknown + * = false). Similarly, a dependent field can be made the Jacobian variable for a specific weak + * form (e.g. acceleration in the cycle-zero solve) by copying the returned @c FieldType and + * setting @c is_unknown = true. * * @param weak_form_name Name of the weak form being constructed. * @param reaction_field_name Name of the test/reaction field (may differ from the first input). @@ -276,7 +276,7 @@ struct FieldStore { auto register_field = [&](auto type) { spaces.push_back(&getField(type.name).get()->space()); addWeakFormArg(weak_form_name, type.name, arg_num); - if (type.unknown_index >= 0) { + if (type.is_unknown) { addWeakFormUnknownArg(weak_form_name, type.name, arg_num); } ++arg_num; @@ -316,17 +316,33 @@ struct FieldStore { std::vector> indexMap(const std::vector& residual_names) const; /** - * @brief Get the boundary condition managers for all independent fields. - * @return std::vector List of boundary condition managers. + * @brief Get the boundary condition managers for the given weak forms, one per residual row. + * + * For each weak form in @p weak_form_names the reaction (test) field name is looked up, and + * the corresponding @c BoundaryConditionManager is returned. If no BC was registered for + * that field, @c nullptr is returned for that slot (the solver skips null entries). + * + * Zero-mirror BCs registered via @c shareBoundaryConditions are materialized lazily on the + * first call to this method (so the user's @c set*BCs calls on the source BC are visible). + * + * @param weak_form_names Ordered list of weak form names whose BCs are needed. + * @return std::vector One entry per weak form, in order. */ - std::vector getBoundaryConditionManagers() const; + std::vector + getBoundaryConditionManagers(const std::vector& weak_form_names); /** - * @brief Get the Dirichlet boundary conditions for an independent field by its unknown index. - * @param unknown_index The unknown index of the independent field. - * @return std::shared_ptr The boundary conditions. + * @brief Register a zero-valued mirror BC for @p name, sharing the constrained DOF set of @p source_bc. + * + * Used for fields whose constrained DOFs must match a reference field (e.g. acceleration mirrors + * displacement), but whose prescribed BC value is always zero. The zero BC is materialized + * lazily on the first call to @c getBoundaryConditionManagers so that any @c set*BCs calls the + * caller makes on @p source_bc after this method returns are reflected in the mirror. + * + * @param name Field name to associate with the zero BC. + * @param source_bc BC object whose constrained DOF set is mirrored (e.g. the displacement BC). */ - std::shared_ptr getBoundaryConditions(size_t unknown_index) const; + void shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc); /** * @brief Get the internal index of a field by name. @@ -335,13 +351,6 @@ struct FieldStore { */ size_t getFieldIndex(const std::string& field_name) const; - /** - * @brief Get the unknown index of a field by name. - * @param field_name Name of the field. - * @return size_t Unknown index of the field. - */ - size_t getUnknownIndex(const std::string& field_name) const; - /** * @brief Get a FieldState by name. * @param field_name Name of the field. @@ -446,9 +455,16 @@ struct FieldStore { std::map to_states_index_; std::map to_params_index_; - size_t num_unknowns_ = 0; - std::map to_unknown_index_; - std::vector> boundary_conditions_; + /// Boundary conditions keyed by field name. Populated by @c addIndependent (for primary + /// unknowns). Zero-mirror BCs are added lazily by @c getBoundaryConditionManagers when + /// entries from @c zero_mirror_sources_ are materialized. + std::map> boundary_conditions_; + + /// Pending zero-mirror BCs: maps a field name to the source @c DirichletBoundaryConditions + /// whose constrained DOF set it should copy (with zero prescribed values). Entries are + /// materialized and moved to @c boundary_conditions_ on the first call to + /// @c getBoundaryConditionManagers. + std::map> zero_mirror_sources_; struct FieldLabel { std::string field_name; diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index d02fb551fb..8471ac5213 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -22,6 +22,11 @@ MultiphysicsTimeIntegrator::MultiphysicsTimeIntegrator(std::shared_ptr system) +{ + post_solve_systems_.push_back(std::move(system)); +} + std::pair, std::vector> MultiphysicsTimeIntegrator::advanceState( const TimeInfo& time_info, const FieldState& shape_disp, const std::vector& states, const std::vector& params) const @@ -51,31 +56,69 @@ std::pair, std::vector> MultiphysicsTimeI if (time_info.cycle() == 0 && cycle_zero_system_ && requires_cycle_zero_solve) { auto cz_unknowns = cycle_zero_system_->solve(time_info); - // Cycle zero system solves for initial acceleration which translates into test field. - // Sync the solved unknowns back into current_states before main solve - // Assuming cycle zero solve gives us the state for test field: + // Cycle zero system solves for the initial acceleration, but by convention the solved value + // is returned through the first (and only) block of the cycle-zero subsystem — the weak form + // uses an aliased unknown trial space that matches the acceleration test space. Copy that + // single result into the acceleration state slot for the main solve. + SLIC_ERROR_ROOT_IF(cz_unknowns.size() != 1, + "Cycle zero system is expected to be a single-block solve producing one unknown"); std::string test_field_name = system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms.front()->name()); size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); - size_t cz_unknown_idx = cycle_zero_system_->field_store->getUnknownIndex(test_field_name); - current_states[test_field_state_idx] = cz_unknowns[cz_unknown_idx]; - system_->field_store->setField(test_field_state_idx, cz_unknowns[cz_unknown_idx]); + current_states[test_field_state_idx] = cz_unknowns[0]; + system_->field_store->setField(test_field_state_idx, cz_unknowns[0]); } std::vector primary_unknowns = system_->solve(time_info); + // Build a map from the main system's unknown names to their position in primary_unknowns. + // Entries in the shared FieldStore's time integration rules that belong to post-solve + // subsystems (e.g. stress projection) are NOT present here and must be skipped by downstream + // lookups that walk getTimeIntegrationRules(). + std::map main_unknown_name_to_local_idx; + for (size_t i = 0; i < system_->weak_forms.size(); ++i) { + const std::string wf_name = system_->weak_forms[i]->name(); + const std::string reaction_name = system_->field_store->getWeakFormReaction(wf_name); + main_unknown_name_to_local_idx[reaction_name] = i; + } + // Create states for reaction computation: newly solved primary unknowns + current states std::vector states_for_reactions = current_states; for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { + auto it = main_unknown_name_to_local_idx.find(mapping.primary_name); + if (it == main_unknown_name_to_local_idx.end()) { + continue; // rule belongs to a post-solve subsystem, not the main solve + } size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); - size_t unknown_idx = system_->field_store->getUnknownIndex(mapping.primary_name); - FieldState u_new = primary_unknowns[unknown_idx]; + FieldState u_new = primary_unknowns[it->second]; states_for_reactions[u_idx] = u_new; } // Compute reactions using newly solved unknowns but BEFORE time integration state updates std::vector reactions = system_->computeReactions(time_info, states_for_reactions); + // Sync field_store with newly solved primary unknowns so post-solve systems (e.g. stress + // projection) read the current displacement rather than the pre-solve snapshot. + for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { + auto it = main_unknown_name_to_local_idx.find(mapping.primary_name); + if (it == main_unknown_name_to_local_idx.end()) { + continue; + } + size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); + system_->field_store->setField(u_idx, primary_unknowns[it->second]); + } + + // Solve post-solve systems (e.g. stress projection for output) and sync their results back + // into the shared field_store so getAllFields() returns the updated values for new_states. + for (const auto& ps : post_solve_systems_) { + auto ps_unknowns = ps->solve(time_info); + for (size_t i = 0; i < ps->weak_forms.size(); ++i) { + const std::string reaction_name = ps->field_store->getWeakFormReaction(ps->weak_forms[i]->name()); + size_t u_idx = ps->field_store->getFieldIndex(reaction_name); + ps->field_store->setField(u_idx, ps_unknowns[i]); + } + } + // Now do time integration to compute corrected velocities/accelerations and update all states const auto& all_current_states = system_->field_store->getAllFields(); std::vector new_states = current_states; @@ -84,9 +127,12 @@ std::pair, std::vector> MultiphysicsTimeI } for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { + auto it = main_unknown_name_to_local_idx.find(mapping.primary_name); + if (it == main_unknown_name_to_local_idx.end()) { + continue; // rule belongs to a post-solve subsystem, not the main solve + } size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); - size_t unknown_idx = system_->field_store->getUnknownIndex(mapping.primary_name); - FieldState u_new = primary_unknowns[unknown_idx]; + FieldState u_new = primary_unknowns[it->second]; new_states[u_idx] = u_new; std::vector rule_inputs; @@ -121,6 +167,20 @@ std::pair, std::vector> MultiphysicsTimeI } } + // Copy solve-state → history for post-solve fields (e.g. stress_solve_state → stress). + // The main loop skipped these rules; their primary fields are already correct in new_states + // (populated from all_current_states above), so only the history field needs updating. + for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { + if (main_unknown_name_to_local_idx.count(mapping.primary_name)) { + continue; // already handled by main time integration loop above + } + if (!mapping.history_name.empty()) { + size_t primary_idx = system_->field_store->getFieldIndex(mapping.primary_name); + size_t hist_idx = system_->field_store->getFieldIndex(mapping.history_name); + new_states[hist_idx] = new_states[primary_idx]; + } + } + return {new_states, reactions}; } diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 5d029f3524..fee55fa82b 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -30,6 +30,9 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { MultiphysicsTimeIntegrator(std::shared_ptr system, std::shared_ptr cycle_zero_system = nullptr); + /// @brief Register a system to be solved after the main solve and reaction computation. + void addPostSolveSystem(std::shared_ptr system); + /** * @brief Advance the multiphysics state by one time step. * @param time_info Current time information. @@ -45,21 +48,22 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { private: std::shared_ptr system_; std::shared_ptr cycle_zero_system_; + std::vector> post_solve_systems_; }; -inline std::shared_ptr makeAdvancer(std::shared_ptr system, - std::shared_ptr cycle_zero_system = nullptr) +inline std::shared_ptr makeAdvancer(std::shared_ptr system, + std::shared_ptr cycle_zero_system = nullptr) { return std::make_shared(std::move(system), std::move(cycle_zero_system)); } template -std::shared_ptr makeAdvancer(std::shared_ptr system) +std::shared_ptr makeAdvancer(std::shared_ptr system) { if constexpr (requires { system->cycle_zero_system; }) { - return makeAdvancer(system, system->cycle_zero_system); + return makeAdvancer(std::static_pointer_cast(system), system->cycle_zero_system); } else { - return makeAdvancer(system, nullptr); + return makeAdvancer(std::static_pointer_cast(system), nullptr); } } diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 74243c0fce..78fccb2a91 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -53,6 +53,14 @@ struct SolidMechanicsSystem : public SystemBase { TimeDiscretizedWeakForm, Parameters, H1, H1, parameter_space...>>; + /// L2 projection weak form for PK1 stress output (dim*dim components). + /// Args: (stress_unknown, u, u_old, v_old, a_old, params...). The stress_unknown is the + /// Jacobian variable so the L2 mass matrix diagonal can be built against it. + using StressOutputWeakFormType = + TimeDiscretizedWeakForm, + Parameters, H1, H1, H1, + H1, parameter_space...>>; + std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr cycle_zero_solid_weak_form; ///< Typed cycle zero solid mechanics weak form. @@ -60,6 +68,9 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. + std::shared_ptr stress_weak_form; ///< Stress projection weak form (nullptr if disabled). + std::shared_ptr stress_output_system; ///< Post-solve system for stress projection. + /** * @brief Set the material model for a domain, defining integrals for the solid weak form. * @tparam MaterialType The material model type. @@ -88,6 +99,26 @@ struct SolidMechanicsSystem : public SystemBase { return smith::tuple{get(a) * material.density, pk_stress}; }); + + // Stress output projection: L2 projection of PK1 stress onto an L2 piecewise-constant field. + // Residual: ∫ test · (stress_unknown - pk_stress(u)) dx = 0. + // Args: (stress_unknown, u, u_old, v_old, a_old, params...). stress_unknown is the Jacobian + // variable so the solver builds the mass matrix against it, and the (- pk_stress) term + // becomes the RHS. + if (stress_weak_form) { + stress_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto stress, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); + + typename MaterialType::State state; + auto pk_stress = material(state, get(u_current), params...); + + // Flatten dim x dim stress tensor into dim*dim vector, subtract from current stress unknown + auto pk_flat = make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); + return smith::tuple{get(stress) - pk_flat, tensor{}}; + }); + } } /** @@ -266,6 +297,8 @@ template cycle_zero_solver{}; + bool enable_stress_output = false; + std::shared_ptr stress_output_solver{}; }; /** @@ -326,18 +359,84 @@ std::shared_ptrrequiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("solid_reaction"); + // At cycle 0, u and v are given; solve for a. Make acceleration (arg 2) the Jacobian + // variable by setting is_unknown=true on the copy. Displacement is a fixed input here + // even though disp_type.is_unknown=true from addIndependent, so re-wrap with is_unknown=false. + auto accel_as_unknown = accel_old_type; + accel_as_unknown.is_unknown = true; + FieldType> disp_cz_input(disp_type.name); sys->cycle_zero_solid_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - parameter_types...)); - - auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); - SLIC_ERROR_IF(cz_solver == nullptr, - "Could not derive a cycle-zero solver for block 0 from the provided solid mechanics solver."); + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, parameter_types...)); + // Share displacement BCs with acceleration: constrained acceleration DOFs = constrained + // displacement DOFs (if u is pinned, all its time derivatives are also zero). + field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); + + std::shared_ptr cz_solver; + if (options.cycle_zero_solver) { + cz_solver = options.cycle_zero_solver; + } else { + // The cycle-zero solve is a linear mass-matrix system — one Newton step suffices. + // Never inherit the main solid solver (often TrustRegion, tuned for nonlinear mechanics). + NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); + } sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); } + if (options.enable_stress_output) { + // Register L2 stress field (dim*dim components, quasi-static first-order rule) + auto stress_time_rule = std::make_shared(); + FieldType> stress_type("stress_solve_state"); + field_store->addIndependent(stress_type, stress_time_rule); + field_store->addDependent(stress_type, FieldStore::TimeDerivative::VAL, "stress"); + + // Create stress projection weak form. Arg list: (stress_unknown, u, u_old, v_old, a_old, params...). + // The stress field is the Jacobian unknown for this subsystem. disp_type is passed as a fixed + // INPUT (not a Jacobian unknown); since disp_type.is_unknown=true from addIndependent, re-wrap + // it as a plain FieldType (is_unknown=false) before passing to createSpaces. + FieldType> disp_as_input(disp_type.name); + std::string stress_name = field_store->prefix("stress_projection"); + sys->stress_weak_form = std::make_shared( + stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), + field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, + velo_old_type, accel_old_type, parameter_types...)); + + std::shared_ptr stress_solver; + if (options.stress_output_solver) { + stress_solver = options.stress_output_solver; + } else { + // L2 projection is a linear system — one Newton step suffices. + // Never inherit the main solid solver (often TrustRegion, tuned for nonlinear mechanics). + NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions stress_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + stress_solver = std::make_shared(buildNonlinearBlockSolver(stress_nonlin, stress_lin, *mesh)); + } + + sys->stress_output_system = makeSubSystem(field_store, stress_solver, {sys->stress_weak_form}); + } + return sys; } diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 75790cd756..cf12f2e6d5 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -408,17 +408,36 @@ buildSolidMechanicsWithInternalVarsSystem( sys->state_weak_form = state_weak_form; if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - // 6. Cycle-zero weak form: solve for acceleration (inputs: u, v, a, alpha, params...) + // Cycle-zero: solve for acceleration (u, v, alpha given; a is the Jacobian unknown). std::string cycle_zero_name = field_store->prefix("solid_reaction"); + auto accel_as_unknown = accel_old_type; + accel_as_unknown.is_unknown = true; + FieldType> disp_cz_input(disp_type.name); + FieldType state_cz_input(state_type.name); sys->cycle_zero_solid_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - state_type, parameter_types...)); - - auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); - SLIC_ERROR_IF(cz_solver == nullptr, - "Could not derive a cycle-zero solver for block 0 from the provided internal-vars solid mechanics " - "solver."); + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, state_cz_input, parameter_types...)); + // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). + field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); + + std::shared_ptr cz_solver; + if (options.cycle_zero_solver) { + cz_solver = options.cycle_zero_solver; + } else { + NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); + } sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); } diff --git a/src/smith/differentiable_numerics/system_base.cpp b/src/smith/differentiable_numerics/system_base.cpp index 66e7b70884..0ef2198005 100644 --- a/src/smith/differentiable_numerics/system_base.cpp +++ b/src/smith/differentiable_numerics/system_base.cpp @@ -32,7 +32,7 @@ std::vector SystemBase::solve(const TimeInfo& time_info) const weak_form_ptrs.push_back(p.get()); } return solver->solve(weak_form_ptrs, index_map, field_store->getShapeDisp(), inputs, wk_params, time_info, - field_store->getBoundaryConditionManagers()); + field_store->getBoundaryConditionManagers(weak_form_names)); } std::vector SystemBase::computeReactions(const TimeInfo& time_info, diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 69248556a2..94ece37443 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -152,8 +152,8 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroUsesBcsForReactionFieldNotUnknownZero) FieldType> displacement_type("displacement"); auto displacement_bc = field_store->addIndependent(displacement_type, quasi_static); - ASSERT_EQ(field_store->getUnknownIndex("temperature"), 0); - ASSERT_EQ(field_store->getUnknownIndex("displacement"), 1); + ASSERT_TRUE(temperature_type.is_unknown); + ASSERT_TRUE(displacement_type.is_unknown); temperature_bc->setScalarBCs<2>(mesh->domain("left"), [](double, tensor) { return 0.0; }); displacement_bc->setScalarBCs<2>(mesh->domain("right"), [](double, tensor) { return 1.0; }); @@ -281,7 +281,7 @@ TEST(SystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) const std::vector> states = {field_store->getStates("temperature_main"), field_store->getStates("displacement_main")}; const std::vector> params(residuals.size()); - const auto bc_managers = field_store->getBoundaryConditionManagers(); + const auto bc_managers = field_store->getBoundaryConditionManagers(residual_names); auto solved_states = derived_single_block_solver->solve(residuals, block_indices, field_store->getShapeDisp(), states, params, TimeInfo(0.0, 1.0, 0), bc_managers); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 7025350095..c1f4eed43f 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -104,7 +104,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto coupled_solver = std::make_shared(solid_block_solver); auto system = buildSolidMechanicsSystem( - mesh, coupled_solver, ImplicitNewmarkSecondOrderTimeIntegrationRule{}, {}, + mesh, coupled_solver, ImplicitNewmarkSecondOrderTimeIntegrationRule{}, {.enable_stress_output = true}, FieldType("bulk"), FieldType("shear")); static constexpr double gravity = -9.0; @@ -147,9 +147,12 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) size_t cycle = 0; std::vector reactions; + auto advancer = makeAdvancer(system); + advancer->addPostSolveSystem(system->stress_output_system); + for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); - std::tie(states, reactions) = makeAdvancer(system)->advanceState(t_info, shape_disp, states, params); + std::tie(states, reactions) = advancer->advanceState(t_info, shape_disp, states, params); output_states = system->field_store->getOutputFieldStates(); time += dt_; cycle++; diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index 838878bf3a..a1c6259805 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -407,14 +407,36 @@ buildThermoMechanicsSystem( if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("solid_reaction"); + // At cycle 0, u and v are given; solve for a. Make acceleration (arg 2) the Jacobian + // variable. Displacement and temperature are fixed inputs here. + auto accel_as_unknown = accel_old_type; + accel_as_unknown.is_unknown = true; + FieldType> disp_cz_input(disp_type.name); + FieldType> temp_cz_input(temperature_type.name); sys->cycle_zero_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_type, velo_old_type, accel_old_type, - temperature_type, temperature_old_type, parameter_types...)); - - auto cz_solver = options.cycle_zero_solver ? options.cycle_zero_solver : solver->singleBlockSolver(0); - SLIC_ERROR_IF(cz_solver == nullptr, - "Could not derive a cycle-zero solver for block 0 from the provided thermo-mechanics solver."); + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, temp_cz_input, temperature_old_type, parameter_types...)); + // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). + field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); + + std::shared_ptr cz_solver; + if (options.cycle_zero_solver) { + cz_solver = options.cycle_zero_solver; + } else { + NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); + } sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_weak_form}); } From 9f9d51c5ffd89cc39faa71888159f0a29a7e1f69 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 13 Apr 2026 15:04:58 -0600 Subject: [PATCH 11/67] Fix style.: --- .../differentiable_numerics/field_store.cpp | 7 +-- .../differentiable_numerics/field_store.hpp | 6 +-- .../solid_mechanics_system.hpp | 44 +++++++++---------- ...id_mechanics_with_internal_vars_system.hpp | 14 +++--- .../thermo_mechanics_system.hpp | 14 +++--- 5 files changed, 42 insertions(+), 43 deletions(-) diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 6b715b1eab..a1b5ddb21f 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -31,7 +31,8 @@ std::shared_ptr FieldStore::addBoundaryConditions(F return std::make_shared(mesh_->mfemParMesh(), field->space()); } -void FieldStore::shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc) +void FieldStore::shareBoundaryConditions(const std::string& name, + std::shared_ptr source_bc) { zero_mirror_sources_[name] = std::move(source_bc); } @@ -131,8 +132,8 @@ std::vector> FieldStore::indexMap(const std::vector -FieldStore::getBoundaryConditionManagers(const std::vector& weak_form_names) +std::vector FieldStore::getBoundaryConditionManagers( + const std::vector& weak_form_names) { std::vector bcs; for (const auto& wf_name : weak_form_names) { diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index d4c57e38a0..35ca9ee387 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -50,7 +50,7 @@ struct FieldType { * @param is_unknown_ Whether this field is a Jacobian unknown in the current context. */ FieldType(std::string n, bool is_unknown_ = false) : name(n), is_unknown(is_unknown_) {} - std::string name; ///< Name of the field. + std::string name; ///< Name of the field. bool is_unknown = false; ///< True if this field is a Jacobian variable in the current weak-form context. }; @@ -328,8 +328,8 @@ struct FieldStore { * @param weak_form_names Ordered list of weak form names whose BCs are needed. * @return std::vector One entry per weak form, in order. */ - std::vector - getBoundaryConditionManagers(const std::vector& weak_form_names); + std::vector getBoundaryConditionManagers( + const std::vector& weak_form_names); /** * @brief Register a zero-valued mirror BC for @p name, sharing the constrained DOF set of @p source_bc. diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 78fccb2a91..8c95c21433 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -56,10 +56,9 @@ struct SolidMechanicsSystem : public SystemBase { /// L2 projection weak form for PK1 stress output (dim*dim components). /// Args: (stress_unknown, u, u_old, v_old, a_old, params...). The stress_unknown is the /// Jacobian variable so the L2 mass matrix diagonal can be built against it. - using StressOutputWeakFormType = - TimeDiscretizedWeakForm, - Parameters, H1, H1, H1, - H1, parameter_space...>>; + using StressOutputWeakFormType = TimeDiscretizedWeakForm< + dim, L2<0, dim * dim>, + Parameters, H1, H1, H1, H1, parameter_space...>>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr @@ -106,18 +105,17 @@ struct SolidMechanicsSystem : public SystemBase { // variable so the solver builds the mass matrix against it, and the (- pk_stress) term // becomes the RHS. if (stress_weak_form) { - stress_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto stress, auto u, auto u_old, auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); - - typename MaterialType::State state; - auto pk_stress = material(state, get(u_current), params...); - - // Flatten dim x dim stress tensor into dim*dim vector, subtract from current stress unknown - auto pk_flat = make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); - return smith::tuple{get(stress) - pk_flat, tensor{}}; - }); + stress_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto stress, auto u, auto u_old, + auto v_old, auto a_old, auto... params) { + auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); + + typename MaterialType::State state; + auto pk_stress = material(state, get(u_current), params...); + + // Flatten dim x dim stress tensor into dim*dim vector, subtract from current stress unknown + auto pk_flat = make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); + return smith::tuple{get(stress) - pk_flat, tensor{}}; + }); } } @@ -367,8 +365,8 @@ std::shared_ptr> disp_cz_input(disp_type.name); sys->cycle_zero_solid_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, parameter_types...)); + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, + parameter_types...)); // Share displacement BCs with acceleration: constrained acceleration DOFs = constrained // displacement DOFs (if u is pinned, all its time derivatives are also zero). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); @@ -385,11 +383,11 @@ std::shared_ptr(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); } diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index cf12f2e6d5..7ae61fd614 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -416,8 +416,8 @@ buildSolidMechanicsWithInternalVarsSystem( FieldType state_cz_input(state_type.name); sys->cycle_zero_solid_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, state_cz_input, parameter_types...)); + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, + state_cz_input, parameter_types...)); // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); @@ -431,11 +431,11 @@ buildSolidMechanicsWithInternalVarsSystem( .max_iterations = 2, .print_level = 0}; LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); } diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index a1c6259805..f91ec535e8 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -415,8 +415,8 @@ buildThermoMechanicsSystem( FieldType> temp_cz_input(temperature_type.name); sys->cycle_zero_weak_form = std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, temp_cz_input, temperature_old_type, parameter_types...)); + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, + temp_cz_input, temperature_old_type, parameter_types...)); // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); @@ -430,11 +430,11 @@ buildThermoMechanicsSystem( .max_iterations = 2, .print_level = 0}; LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); } From 6371ba50ee4d5abccb91b89e6e8989f2277fb30d Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 13 Apr 2026 17:58:02 -0600 Subject: [PATCH 12/67] Get solid mechanics problems passing, with style. --- .../differentiable_numerics/field_store.cpp | 8 ++++ .../differentiable_numerics/field_store.hpp | 10 +++++ .../solid_mechanics_system.hpp | 31 +++++++++----- ...id_mechanics_with_internal_vars_system.hpp | 30 +++++++++----- .../thermo_mechanics_system.hpp | 40 +++++++++++-------- 5 files changed, 80 insertions(+), 39 deletions(-) diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index a1b5ddb21f..2ee5dec775 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -286,6 +286,11 @@ FieldStore::getTimeIntegrationRules() const void FieldStore::setField(size_t index, FieldState updated_field) { states_[index] = updated_field; } +void FieldStore::markWeakFormInternal(const std::string& weak_form_name) +{ + internal_weak_forms_.insert(weak_form_name); +} + void FieldStore::addWeakFormReaction(std::string weak_form_name, std::string field_name) { for (auto& kv : weak_form_to_test_field_) { @@ -328,6 +333,9 @@ std::vector FieldStore::getReactionInfos() const std::vector infos; for (const auto& kv : weak_form_to_test_field_) { const std::string& weak_form_name = kv.first; + if (internal_weak_forms_.count(weak_form_name)) { + continue; + } const std::string& field_name = kv.second; infos.push_back({weak_form_name, &getField(field_name).get()->space()}); } diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 35ca9ee387..4d6883b3ff 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -12,6 +12,7 @@ #include "smith/physics/mesh.hpp" #include +#include #include #include #include @@ -235,6 +236,14 @@ struct FieldStore { */ void addWeakFormReaction(std::string weak_form_name, std::string field_name); + /** + * @brief Mark a weak form as internal so it is excluded from getReactionInfos(). + * + * Use this for subsystem forms (e.g. cycle-zero acceleration solve) that should not be + * exposed as user-visible reactions in DifferentiablePhysics. + */ + void markWeakFormInternal(const std::string& weak_form_name); + /** * @brief Get the name of the reaction (test) field for a weak form. * @param weak_form_name Name of the weak form. @@ -479,6 +488,7 @@ struct FieldStore { std::map> weak_form_name_to_field_names_; std::vector> weak_form_to_test_field_; + std::set internal_weak_forms_; ///< weak forms excluded from getReactionInfos() (subsystem-internal) std::vector, TimeIntegrationMapping>> time_integration_rules_; std::map independent_name_to_rule_index_; diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 8c95c21433..d34ac2ceb1 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -91,13 +91,15 @@ struct SolidMechanicsSystem : public SystemBase { }); // Add to cycle-zero weak form (at cycle 0, u and v are given, solve for a) - cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v_old*/, auto a, auto... params) { - typename MaterialType::State state; - auto pk_stress = material(state, get(u), params...); - - return smith::tuple{get(a) * material.density, pk_stress}; - }); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBodyIntegral( + domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v_old*/, auto a, auto... params) { + typename MaterialType::State state; + auto pk_stress = material(state, get(u), params...); + + return smith::tuple{get(a) * material.density, pk_stress}; + }); + } // Stress output projection: L2 projection of PK1 stress onto an L2 piecewise-constant field. // Residual: ∫ test · (stress_unknown - pk_stress(u)) dx = 0. @@ -275,19 +277,25 @@ struct SolidMechanicsSystem : public SystemBase { template void addCycleZeroBodySourceImpl(const std::string& name, IntegrandType f, std::index_sequence) { - cycle_zero_solid_weak_form->addBodySource(DependsOn(Is)...>{}, name, f); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBodySource(DependsOn(Is)...>{}, name, f); + } } template void addCycleZeroBoundaryFluxImpl(const std::string& name, IntegrandType f, std::index_sequence) { - cycle_zero_solid_weak_form->addBoundaryFlux(DependsOn(Is)...>{}, name, f); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBoundaryFlux(DependsOn(Is)...>{}, name, f); + } } template void addCycleZeroBoundaryIntegralImpl(const std::string& name, IntegrandType f, std::index_sequence) { - cycle_zero_solid_weak_form->addBoundaryIntegral(DependsOn(Is)...>{}, name, f); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBoundaryIntegral(DependsOn(Is)...>{}, name, f); + } } }; @@ -344,7 +352,7 @@ std::shared_ptr; // Create solid mechanics weak form (u, u_old, v_old, a_old) - std::string force_name = field_store->prefix("solid_force"); + std::string force_name = field_store->prefix("reactions"); auto solid_weak_form = std::make_shared( force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, @@ -367,6 +375,7 @@ std::shared_ptrgetMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, parameter_types...)); + field_store->markWeakFormInternal(cycle_zero_name); // Share displacement BCs with acceleration: constrained acceleration DOFs = constrained // displacement DOFs (if u is pinned, all its time derivatives are also zero). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 7ae61fd614..3bb5d60d95 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -111,14 +111,15 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { }); // Cycle-zero: u and v are given, solve for a; alpha at initial condition - cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { - auto alpha_current = alpha; // at cycle 0, use initial alpha - typename MaterialType::State state; - auto pk_stress = material(state, get(u), get(alpha_current), params...); - - return smith::tuple{get(a) * material.density, pk_stress}; - }); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBodyIntegral( + domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { + auto alpha_current = alpha; // at cycle 0, use initial alpha + typename MaterialType::State state; + auto pk_stress = material(state, get(u), get(alpha_current), params...); + return smith::tuple{get(a) * material.density, pk_stress}; + }); + } } /** @@ -310,19 +311,25 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addCycleZeroBodySourceImpl(const std::string& name, IntegrandType f, std::index_sequence) { - cycle_zero_solid_weak_form->addBodySource(DependsOn(Is)...>{}, name, f); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBodySource(DependsOn(Is)...>{}, name, f); + } } template void addCycleZeroBoundaryFluxImpl(const std::string& name, IntegrandType f, std::index_sequence) { - cycle_zero_solid_weak_form->addBoundaryFlux(DependsOn(Is)...>{}, name, f); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBoundaryFlux(DependsOn(Is)...>{}, name, f); + } } template void addCycleZeroBoundaryIntegralImpl(const std::string& name, IntegrandType f, std::index_sequence) { - cycle_zero_solid_weak_form->addBoundaryIntegral(DependsOn(Is)...>{}, name, f); + if (cycle_zero_solid_weak_form) { + cycle_zero_solid_weak_form->addBoundaryIntegral(DependsOn(Is)...>{}, name, f); + } } }; @@ -418,6 +425,7 @@ buildSolidMechanicsWithInternalVarsSystem( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, state_cz_input, parameter_types...)); + field_store->markWeakFormInternal(cycle_zero_name); // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index f91ec535e8..0d3003f025 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -116,15 +116,18 @@ struct ThermoMechanicsSystem : public SystemBase { }); // Cycle-zero: u and v are given, solve for a; temperature at initial condition - cycle_zero_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto v, auto a, - auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), - get(T), params...); - return smith::tuple{get(a) * material.density, pk}; - }); + // Only register if the displacement time rule requires an initial acceleration solve. + if (cycle_zero_weak_form) { + cycle_zero_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto v, auto a, + auto temperature, auto temperature_old, auto... params) { + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), + get(T), params...); + return smith::tuple{get(a) * material.density, pk}; + }); + } } /** @@ -296,14 +299,16 @@ struct ThermoMechanicsSystem : public SystemBase { { addSolidTraction(DependsOn(MainIs)...>{}, domain_name, flux_function); - auto captured_temp_rule = temperature_time_rule; - cycle_zero_weak_form->addBoundaryFlux( - DependsOn(CycleZeroIs)...>{}, domain_name, - [=](auto t_info, auto X, auto n, auto u, auto v, auto a, auto temperature, auto temperature_old, - auto... params) { - auto [current_T, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - return flux_function(t_info.time(), X, n, u, v, a, current_T, T_dot, params...); - }); + if (cycle_zero_weak_form) { + auto captured_temp_rule = temperature_time_rule; + cycle_zero_weak_form->addBoundaryFlux( + DependsOn(CycleZeroIs)...>{}, domain_name, + [=](auto t_info, auto X, auto n, auto u, auto v, auto a, auto temperature, auto temperature_old, + auto... params) { + auto [current_T, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); + return flux_function(t_info.time(), X, n, u, v, a, current_T, T_dot, params...); + }); + } } template @@ -417,6 +422,7 @@ buildThermoMechanicsSystem( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, temp_cz_input, temperature_old_type, parameter_types...)); + field_store->markWeakFormInternal(cycle_zero_name); // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); From 0e9ddbdb113ab93d0e87e7cb6a85daae5bba93f5 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 15 Apr 2026 14:24:27 -0600 Subject: [PATCH 13/67] Trying to implement system combine functionality. --- .../differentiable_numerics/CMakeLists.txt | 3 + .../combined_system.cpp | 62 +++ .../combined_system.hpp | 142 ++++++ .../differentiable_numerics/coupling_spec.hpp | 83 ++++ .../differentiable_numerics/field_store.cpp | 8 + .../differentiable_numerics/field_store.hpp | 2 + .../multiphysics_time_integrator.hpp | 19 +- .../solid_mechanics_system.hpp | 214 ++++++--- ...id_mechanics_with_internal_vars_system.hpp | 311 +++++++++---- .../tests/CMakeLists.txt | 1 + .../tests/test_combined_thermo_mechanics.cpp | 408 ++++++++++++++++++ .../thermal_system.hpp | 128 ++++-- 12 files changed, 1198 insertions(+), 183 deletions(-) create mode 100644 src/smith/differentiable_numerics/combined_system.cpp create mode 100644 src/smith/differentiable_numerics/combined_system.hpp create mode 100644 src/smith/differentiable_numerics/coupling_spec.hpp create mode 100644 src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 75d95a4a68..2cbe291b82 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -16,6 +16,7 @@ set(differentiable_numerics_sources evaluate_objective.cpp dirichlet_boundary_conditions.cpp multiphysics_time_integrator.cpp + combined_system.cpp ) set(differentiable_numerics_headers @@ -41,6 +42,8 @@ set(differentiable_numerics_headers solid_mechanics_with_internal_vars_system.hpp thermal_system.hpp thermo_mechanics_system.hpp + coupling_spec.hpp + combined_system.hpp system_base.hpp differentiable_test_utils.hpp ) diff --git a/src/smith/differentiable_numerics/combined_system.cpp b/src/smith/differentiable_numerics/combined_system.cpp new file mode 100644 index 0000000000..6ede296053 --- /dev/null +++ b/src/smith/differentiable_numerics/combined_system.cpp @@ -0,0 +1,62 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "smith/differentiable_numerics/combined_system.hpp" + +namespace smith { + +std::vector CombinedSystem::solve(const TimeInfo& time_info) const +{ + // Snapshot the current solve-state unknowns for convergence checking. + // One mfem::Vector copy per combined weak form (indexed same as weak_forms). + std::vector prev(weak_forms.size()); + for (size_t k = 0; k < weak_forms.size(); ++k) { + const std::string reaction_name = field_store->getWeakFormReaction(weak_forms[k]->name()); + size_t u_idx = field_store->getFieldIndex(reaction_name); + prev[k] = mfem::Vector(*field_store->getAllFields()[u_idx].get()); + } + + // Staggered iteration: each sub-system reads from the shared FieldStore and writes its + // updated unknowns back before the next sub-system reads. + for (int iter = 0; iter < max_stagger_iters; ++iter) { + for (const auto& sub : subsystems) { + auto sub_unknowns = sub->solve(time_info); + + for (size_t i = 0; i < sub->weak_forms.size(); ++i) { + const std::string reaction_name = + field_store->getWeakFormReaction(sub->weak_forms[i]->name()); + size_t u_idx = field_store->getFieldIndex(reaction_name); + field_store->setField(u_idx, sub_unknowns[i]); + } + } + + // Convergence check: relative change in each unknown must be below stagger_tolerance. + double max_change = 0.0; + for (size_t k = 0; k < weak_forms.size(); ++k) { + const std::string reaction_name = field_store->getWeakFormReaction(weak_forms[k]->name()); + size_t u_idx = field_store->getFieldIndex(reaction_name); + mfem::Vector curr(*field_store->getAllFields()[u_idx].get()); + mfem::Vector diff(curr); + diff -= prev[k]; + const double change = diff.Norml2() / (1.0 + curr.Norml2()); + if (change > max_change) max_change = change; + prev[k] = curr; + } + if (max_change < stagger_tolerance) break; + } + + // Return one FieldState per combined weak_form. + std::vector result; + result.reserve(weak_forms.size()); + for (const auto& wf : weak_forms) { + const std::string reaction_name = field_store->getWeakFormReaction(wf->name()); + size_t u_idx = field_store->getFieldIndex(reaction_name); + result.push_back(field_store->getAllFields()[u_idx]); + } + return result; +} + +} // namespace smith diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp new file mode 100644 index 0000000000..8ff4d125e9 --- /dev/null +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -0,0 +1,142 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file combined_system.hpp + * @brief CombinedSystem and combineSystems for composing independent physics into a coupled system. + * + * Individual physics sub-systems (SolidMechanicsSystem, ThermalSystem, ...) remain authoritative + * for their own configuration APIs. combineSystems wires them together via a shared FieldStore + * and provides a coupled setMaterial that registers integrands on both sub-system weak forms. + * + * Usage: + * @code + * auto field_store = std::make_shared(mesh, 100, "coupled"); + * + * auto solid_info = registerSolidMechanicsFields(*field_store, disp_rule, params...); + * auto thermal_info = registerThermalFields(*field_store, temp_rule); + * + * CouplingSpec solid_coupling{FieldType>("temperature"), + * FieldType>("temperature_old")}; + * CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), + * FieldType>("displacement")}; + * + * auto solid = buildSolidMechanicsSystemFromStore( + * solid_info, solid_solver, solid_opts, solid_coupling, params...); + * auto thermal = buildThermalSystemFromStore( + * thermal_info, thermal_solver, thermal_opts, thermal_coupling); + * + * auto coupled = combineSystems(solid, thermal); + * coupled->setMaterial(thermo_mech_material, domain); // tight coupling + * solid->addTraction(right, traction_fn); // loose via sub-system + * thermal->addHeatFlux(top, flux_fn); + * + * auto advancer = makeAdvancer(coupled); + * @endcode + */ + +#pragma once + +#include +#include +#include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/field_store.hpp" + +namespace smith { + +/** + * @brief A non-templated system wrapper that combines multiple sub-systems sharing one FieldStore. + * + * The combined solve does staggered iterations: in each sweep every sub-system is solved in + * order, writing its updated unknowns back to the shared FieldStore so subsequent sub-systems see + * the updated fields. The base class weak_forms member is the concatenation of sub-system weak + * forms, which allows makeAdvancer / MultiphysicsTimeIntegrator to integrate the combined system + * transparently. + * + * For tight coupling (setMaterial on the combined system), sub-classes or concrete callers can + * down-cast subsystems[i] to the typed sub-system and call its own addBodyIntegral. This is done + * via the template setMaterial below. + */ +struct CombinedSystem : public SystemBase { + std::vector> subsystems; + std::vector> cycle_zero_systems; ///< Cycle-zero systems from sub-systems, in order. + int max_stagger_iters = 10; + double stagger_tolerance = 1e-8; + + /// @brief Construct a CombinedSystem. weak_forms is populated by combineSystems. + using SystemBase::SystemBase; + + /** + * @brief Staggered solve: iterate over sub-systems, writing each result to the shared + * FieldStore before the next sub-system reads it. + * + * Returns one FieldState per combined weak_form (same contract as SystemBase::solve). + */ + std::vector solve(const TimeInfo& time_info) const override; + + /** + * @brief Set a coupled material on all sub-systems. + * + * Calls setMaterial on each sub-system if that sub-system is of the given typed pointer + * (down-cast via dynamic_pointer_cast). Intended for tight-coupling materials that register + * integrands on both sub-systems' weak forms. + * + * @tparam SubSystemType The concrete type that exposes setMaterial. + * @tparam MaterialType The material type. + */ + template + void setMaterialOn(std::shared_ptr sub, const MaterialType& material, + const std::string& domain_name) + { + sub->setMaterial(material, domain_name); + } +}; + +/** + * @brief Combine two or more independently-built sub-systems into a CombinedSystem. + * + * Preconditions: + * - All sub-systems share the same FieldStore (built via registerXxxFields + buildXxxFromStore). + * - Sub-system weak_forms are already populated (registerXxx was called before buildXxx). + * + * The returned CombinedSystem: + * - Holds shared_ptrs to each sub-system (accessible as combined->subsystems[i]). + * - Its weak_forms is the concatenation of sub-system weak forms in argument order. + * - Its field_store is the shared FieldStore from the first sub-system. + * - Its solver member is null (CombinedSystem::solve() drives sub-system solvers directly). + * - Its cycle_zero_systems is populated with each sub-system's cycle_zero_system (if non-null). + * + * @param subs Two or more sub-systems that share a FieldStore. + */ +template +std::shared_ptr combineSystems(std::shared_ptr... subs) +{ + static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); + + auto combined = std::make_shared(); + + // All sub-systems must share the same FieldStore — use the first one. + combined->field_store = std::get<0>(std::forward_as_tuple(subs...))->field_store; + + // Concatenate weak_forms, collect subsystems, and gather any cycle-zero systems. + ( + [&](auto& sub) { + combined->subsystems.push_back(sub); + for (auto& wf : sub->weak_forms) { + combined->weak_forms.push_back(wf); + } + if constexpr (requires { sub->cycle_zero_system; }) { + if (sub->cycle_zero_system) { + combined->cycle_zero_systems.push_back(sub->cycle_zero_system); + } + } + }(subs), + ...); + + return combined; +} + +} // namespace smith diff --git a/src/smith/differentiable_numerics/coupling_spec.hpp b/src/smith/differentiable_numerics/coupling_spec.hpp new file mode 100644 index 0000000000..24478ba0a8 --- /dev/null +++ b/src/smith/differentiable_numerics/coupling_spec.hpp @@ -0,0 +1,83 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file coupling_spec.hpp + * @brief CouplingSpec type and helpers for injecting coupled-physics fields into weak form parameter packs. + * + * Convention: coupling fields occupy the *leading* positions of the "tail" parameter pack in every + * weak form constructed with a non-empty CouplingSpec. Concretely, after the time-rule state fields + * (e.g. u, u_old, v_old, a_old for solid) come the coupling fields in the order declared in + * CouplingSpec::fields, and only then come the user-supplied parameter_space fields. + * + * This ordering must be respected in every setMaterial / addBodyForce / addTraction / addPressure + * closure: the `auto...` tail pack is partitioned as (coupling_fields..., user_params...). + */ + +#pragma once + +#include "smith/differentiable_numerics/field_store.hpp" +#include "smith/differentiable_numerics/system_base.hpp" + +namespace smith { + +/** + * @brief Declares the finite element spaces and field names of fields borrowed from another physics. + * + * @tparam Spaces FE space types of the coupling fields (e.g. H1, H1). + * + * Usage: + * @code + * CouplingSpec solid_coupling{FieldType>("temperature"), + * FieldType>("temperature_old")}; + * @endcode + * + * The default CouplingSpec<> (empty) leaves weak form parameter packs unchanged. + */ +template +struct CouplingSpec { + static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); + std::tuple...> fields; + CouplingSpec(FieldType... fs) : fields(std::move(fs)...) {} +}; + +/// Deduction guide: CouplingSpec{FieldType("a"), FieldType("b")} -> CouplingSpec +template +CouplingSpec(FieldType...) -> CouplingSpec; + +namespace detail { + +/** + * @brief Produce TimeRuleParams from a CouplingSpec. + * + * Inserts coupling spaces immediately after the num_states copies of Space (the time-rule + * state fields), and before the user-supplied Tail types (parameter_space...). + */ +template +struct TimeRuleParamsWithCoupling; + +template +struct TimeRuleParamsWithCoupling, Tail...> { + using type = TimeRuleParams; +}; + +/** + * @brief Append coupling spaces (CS...) and Tail... onto a base Parameters type. + * + * Produces Parameters. + * Used for weak form types whose leading fields are hardcoded (cycle-zero, stress output). + */ +template +struct AppendCouplingToParams; + +template +struct AppendCouplingToParams, Parameters, Tail...> { + using type = Parameters; +}; + +} // namespace detail + +} // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 2ee5dec775..b2b68d29a5 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -158,6 +158,14 @@ std::vector FieldStore::getBoundaryConditionMan return bcs; } +bool FieldStore::hasField(const std::string& field_name) const +{ + if (to_states_index_.count(field_name)) return true; + if (to_params_index_.count(field_name)) return true; + if (!shape_disp_.empty() && shape_disp_[0].get()->name() == field_name) return true; + return false; +} + size_t FieldStore::getFieldIndex(const std::string& field_name) const { if (to_states_index_.count(field_name)) { diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 4d6883b3ff..ebfe415744 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -353,6 +353,8 @@ struct FieldStore { */ void shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc); + bool hasField(const std::string& field_name) const; + /** * @brief Get the internal index of a field by name. * @param field_name Name of the field. diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index fee55fa82b..653b7dcce2 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -15,6 +15,7 @@ #include "smith/physics/mesh.hpp" #include "smith/differentiable_numerics/field_store.hpp" #include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" namespace smith { @@ -60,7 +61,23 @@ inline std::shared_ptr makeAdvancer(std::shared_ptr< template std::shared_ptr makeAdvancer(std::shared_ptr system) { - if constexpr (requires { system->cycle_zero_system; }) { + if constexpr (requires { system->cycle_zero_systems; }) { + // CombinedSystem: run each sub-system's cycle-zero solve in sequence (one pass, no stagger). + std::shared_ptr cz = nullptr; + if (!system->cycle_zero_systems.empty()) { + auto cz_combined = std::make_shared(); + cz_combined->field_store = system->field_store; + cz_combined->max_stagger_iters = 1; + for (auto& czs : system->cycle_zero_systems) { + cz_combined->subsystems.push_back(czs); + for (auto& wf : czs->weak_forms) { + cz_combined->weak_forms.push_back(wf); + } + } + cz = cz_combined; + } + return makeAdvancer(std::static_pointer_cast(system), cz); + } else if constexpr (requires { system->cycle_zero_system; }) { return makeAdvancer(std::static_pointer_cast(system), system->cycle_zero_system); } else { return makeAdvancer(std::static_pointer_cast(system), nullptr); diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index d34ac2ceb1..4ce565a2b7 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -21,6 +21,7 @@ #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/coupling_spec.hpp" namespace smith { @@ -34,31 +35,38 @@ namespace smith { * @tparam dim Spatial dimension. * @tparam order Polynomial order for displacement field. * @tparam DisplacementTimeRule Time integration rule type (must have num_states == 4). + * @tparam Coupling CouplingSpec listing fields borrowed from other physics (default: none). + * Coupling fields occupy leading positions in the tail after the 4 time-rule state fields, + * before user parameter_space fields. * @tparam parameter_space Parameter spaces for material properties. */ template + typename Coupling = CouplingSpec<>, typename... parameter_space> struct SolidMechanicsSystem : public SystemBase { using SystemBase::SystemBase; static_assert(DisplacementTimeRule::num_states == 4, "SolidMechanicsSystem requires a 4-state time integration rule"); - /// using - using SolidWeakFormType = - TimeDiscretizedWeakForm, - TimeRuleParams, parameter_space...>>; + /// Main weak form: (u, u_old, v_old, a_old, coupling_fields..., params...) + using SolidWeakFormType = TimeDiscretizedWeakForm< + dim, H1, + typename detail::TimeRuleParamsWithCoupling, Coupling, + parameter_space...>::type>; - /// using -- 3-state form: u, v, a (no u_old needed; at cycle 0 u and v are given, solve for a) - using CycleZeroSolidWeakFormType = - TimeDiscretizedWeakForm, - Parameters, H1, H1, parameter_space...>>; + /// Cycle-zero form: (u, v_old, a, coupling_fields..., params...) + /// 3-state form: u, v, a (no u_old needed; at cycle 0 u and v are given, solve for a) + using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< + dim, H1, + typename detail::AppendCouplingToParams, H1, H1>, + parameter_space...>::type>; /// L2 projection weak form for PK1 stress output (dim*dim components). - /// Args: (stress_unknown, u, u_old, v_old, a_old, params...). The stress_unknown is the - /// Jacobian variable so the L2 mass matrix diagonal can be built against it. + /// Args: (stress_unknown, u, u_old, v_old, a_old, coupling_fields..., params...). using StressOutputWeakFormType = TimeDiscretizedWeakForm< dim, L2<0, dim * dim>, - Parameters, H1, H1, H1, H1, parameter_space...>>; + typename detail::AppendCouplingToParams< + Coupling, Parameters, H1, H1, H1, H1>, + parameter_space...>::type>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr @@ -145,7 +153,7 @@ struct SolidMechanicsSystem : public SystemBase { [=](auto t_info, auto X, auto u, auto v_old, auto a, auto... params) { return force_function(t_info.time(), X, u, v_old, a, params...); }, - std::make_index_sequence<3 + sizeof...(parameter_space)>{}); + std::make_index_sequence<3 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -157,7 +165,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addBodyForce(const std::string& domain_name, BodyForceType force_function) { - addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<4 + sizeof...(parameter_space)>{}); + addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -185,7 +193,7 @@ struct SolidMechanicsSystem : public SystemBase { [=](auto t_info, auto X, auto n, auto u, auto v_old, auto a, auto... params) { return traction_function(t_info.time(), X, n, u, v_old, a, params...); }, - std::make_index_sequence<3 + sizeof...(parameter_space)>{}); + std::make_index_sequence<3 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -197,7 +205,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addTraction(const std::string& domain_name, TractionType traction_function) { - addTractionAllParams(domain_name, traction_function, std::make_index_sequence<4 + sizeof...(parameter_space)>{}); + addTractionAllParams(domain_name, traction_function, std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -239,7 +247,7 @@ struct SolidMechanicsSystem : public SystemBase { return pressure * n_deformed * (1.0 / n_shape_norm); }, - std::make_index_sequence<3 + sizeof...(parameter_space)>{}); + std::make_index_sequence<3 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -251,7 +259,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addPressure(const std::string& domain_name, PressureType pressure_function) { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<4 + sizeof...(parameter_space)>{}); + addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } private: @@ -302,61 +310,101 @@ struct SolidMechanicsSystem : public SystemBase { template struct SolidMechanicsOptions { std::string prepend_name{}; + std::shared_ptr shared_field_store{}; ///< Shared store for coupled systems; nullptr = allocate own. std::shared_ptr cycle_zero_solver{}; bool enable_stress_output = false; std::shared_ptr stress_output_solver{}; }; +/// @brief Returned by registerSolidMechanicsFields; holds the FieldType tokens needed by buildSolidMechanicsSystemFromStore. +template +struct SolidMechanicsFieldInfo { + std::shared_ptr field_store; + FieldType> disp_type; + FieldType> disp_old_type; + FieldType> velo_old_type; + FieldType> accel_old_type; + std::tuple...> parameter_types; + std::shared_ptr disp_bc; + std::shared_ptr disp_time_rule_ptr; +}; + /** - * @brief Factory function to build a solid dynamics system with configurable time integration. - * @tparam dim Spatial dimension. - * @tparam order Polynomial order for displacement field. - * @tparam DisplacementTimeRule Time integration rule type (must have num_states == 4, deduced from argument). - * @tparam parameter_space Parameter spaces for material properties. - * @param mesh The mesh. - * @param solver The coupled system solver. - * @param disp_time_rule The time integration rule. - * @param options Options for system creation. - * @param parameter_types Parameter field types. - * @return SolidMechanicsSystem with all components initialized. + * @brief Register all solid mechanics fields into a FieldStore. + * + * Phase 1 of the two-phase factory. Safe to call before other physics have registered their + * fields; weak form construction is deferred to buildSolidMechanicsSystemFromStore. + * When composing coupled systems, call all registerXxxFields functions before any buildXxxFromStore. */ template -std::shared_ptr> buildSolidMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_time_rule, - SolidMechanicsOptions options, +SolidMechanicsFieldInfo registerSolidMechanicsFields( + std::shared_ptr field_store, DisplacementTimeRule disp_time_rule, FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100, options.prepend_name); - - // Add shape displacement FieldType> shape_disp_type("shape_displacement"); - field_store->addShapeDisp(shape_disp_type); + if (!field_store->hasField(shape_disp_type.name)) { + field_store->addShapeDisp(shape_disp_type); + } - // Add displacement as independent (unknown) with time integration rule auto disp_time_rule_ptr = std::make_shared(disp_time_rule); FieldType> disp_type("displacement_solve_state"); auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); - // Add dependent fields for time integration history auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); - // Add parameters auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; field_store->addParameter(pt); }; (prefix_param(parameter_types), ...); - using SystemType = SolidMechanicsSystem; + return {field_store, disp_type, disp_old_type, velo_old_type, accel_old_type, + std::make_tuple(parameter_types...), disp_bc, disp_time_rule_ptr}; +} - // Create solid mechanics weak form (u, u_old, v_old, a_old) +/** + * @brief Build a SolidMechanicsSystem from an already-populated FieldStore. + * + * Phase 2 of the two-phase factory. Constructs all weak forms using fields already registered + * in the store (including any coupling fields from other physics). + * + * @tparam Coupling CouplingSpec listing fields borrowed from other physics (leading tail positions). + */ +template +std::shared_ptr> +buildSolidMechanicsSystemFromStore( + SolidMechanicsFieldInfo info, + std::shared_ptr solver, + const SolidMechanicsOptions& options, + const Coupling& coupling) +{ + auto& field_store = info.field_store; + auto& disp_type = info.disp_type; + auto& disp_old_type = info.disp_old_type; + auto& velo_old_type = info.velo_old_type; + auto& accel_old_type = info.accel_old_type; + auto parameter_types = info.parameter_types; + auto& disp_bc = info.disp_bc; + auto& disp_time_rule_ptr = info.disp_time_rule_ptr; + + using SystemType = SolidMechanicsSystem; + + // Create solid mechanics weak form: (u, u_old, v_old, a_old, coupling_fields..., params...) std::string force_name = field_store->prefix("reactions"); - auto solid_weak_form = std::make_shared( - force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, - parameter_types...)); + auto solid_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), + field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, + accel_old_type, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); sys->disp_bc = disp_bc; @@ -365,27 +413,29 @@ std::shared_ptrrequiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("solid_reaction"); - // At cycle 0, u and v are given; solve for a. Make acceleration (arg 2) the Jacobian - // variable by setting is_unknown=true on the copy. Displacement is a fixed input here - // even though disp_type.is_unknown=true from addIndependent, so re-wrap with is_unknown=false. auto accel_as_unknown = accel_old_type; accel_as_unknown.is_unknown = true; FieldType> disp_cz_input(disp_type.name); - sys->cycle_zero_solid_weak_form = std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, - parameter_types...)); + sys->cycle_zero_solid_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + cycle_zero_name, field_store->getMesh(), + field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); field_store->markWeakFormInternal(cycle_zero_name); - // Share displacement BCs with acceleration: constrained acceleration DOFs = constrained - // displacement DOFs (if u is pinned, all its time derivatives are also zero). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); std::shared_ptr cz_solver; if (options.cycle_zero_solver) { cz_solver = options.cycle_zero_solver; } else { - // The cycle-zero solve is a linear mass-matrix system — one Newton step suffices. - // Never inherit the main solid solver (often TrustRegion, tuned for nonlinear mechanics). NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, .absolute_tol = 1e-14, @@ -397,36 +447,37 @@ std::shared_ptr(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); + cz_solver = std::make_shared( + buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); } - sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); } if (options.enable_stress_output) { - // Register L2 stress field (dim*dim components, quasi-static first-order rule) auto stress_time_rule = std::make_shared(); FieldType> stress_type("stress_solve_state"); field_store->addIndependent(stress_type, stress_time_rule); field_store->addDependent(stress_type, FieldStore::TimeDerivative::VAL, "stress"); - // Create stress projection weak form. Arg list: (stress_unknown, u, u_old, v_old, a_old, params...). - // The stress field is the Jacobian unknown for this subsystem. disp_type is passed as a fixed - // INPUT (not a Jacobian unknown); since disp_type.is_unknown=true from addIndependent, re-wrap - // it as a plain FieldType (is_unknown=false) before passing to createSpaces. FieldType> disp_as_input(disp_type.name); std::string stress_name = field_store->prefix("stress_projection"); - sys->stress_weak_form = std::make_shared( - stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), - field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, - velo_old_type, accel_old_type, parameter_types...)); + sys->stress_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), + field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, + velo_old_type, accel_old_type, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); std::shared_ptr stress_solver; if (options.stress_output_solver) { stress_solver = options.stress_output_solver; } else { - // L2 projection is a linear system — one Newton step suffices. - // Never inherit the main solid solver (often TrustRegion, tuned for nonlinear mechanics). NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, .absolute_tol = 1e-14, @@ -438,13 +489,34 @@ std::shared_ptr(buildNonlinearBlockSolver(stress_nonlin, stress_lin, *mesh)); + stress_solver = std::make_shared( + buildNonlinearBlockSolver(stress_nonlin, stress_lin, *field_store->getMesh())); } - sys->stress_output_system = makeSubSystem(field_store, stress_solver, {sys->stress_weak_form}); } return sys; } +/** + * @brief Standalone factory — allocates its own FieldStore and builds the full system. + * + * Thin wrapper around registerSolidMechanicsFields + buildSolidMechanicsSystemFromStore. + * Existing call sites are unchanged. + */ +template +std::shared_ptr, parameter_space...>> +buildSolidMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, + DisplacementTimeRule disp_time_rule, + SolidMechanicsOptions options, + FieldType... parameter_types) +{ + auto field_store = options.shared_field_store + ? options.shared_field_store + : std::make_shared(mesh, 100, options.prepend_name); + auto info = registerSolidMechanicsFields(field_store, disp_time_rule, parameter_types...); + return buildSolidMechanicsSystemFromStore>(info, solver, options, + CouplingSpec<>{}); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 3bb5d60d95..f4233d00be 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -6,7 +6,18 @@ /** * @file solid_mechanics_with_internal_vars_system.hpp - * @brief Defines the SolidMechanicsWithInternalVarsSystem struct and its factory function + * @brief Defines SolidMechanicsWithInternalVarsSystem and its two-phase factory functions. + * + * Two-phase factory (for coupling via combineSystems): + * auto info = registerSolidMechanicsWithInternalVarsFields( + * field_store, disp_rule, state_rule, params...); + * CouplingSpec coupling{...}; + * auto sys = buildSolidMechanicsWithInternalVarsSystemFromStore<...>( + * info, solver, opts, coupling); + * + * Standalone factory (backwards-compatible, allocates its own FieldStore): + * auto sys = buildSolidMechanicsWithInternalVarsSystem( + * mesh, solver, disp_rule, state_rule, options); */ #pragma once @@ -23,6 +34,7 @@ #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/coupling_spec.hpp" namespace smith { @@ -33,16 +45,23 @@ namespace smith { * Internal variable uses a 2-state first-order layout (state_solve_state, state). * Total: 6 state fields. * + * With a non-empty Coupling, coupling fields appear immediately after the hardcoded state fields + * (after alpha_old for the solid form; after a_old for the state form) and before user parameter fields. + * setMaterial and addStateEvolution work correctly only when Coupling = CouplingSpec<> (default). + * For coupled systems, register integrands directly on solid_weak_form / state_weak_form. + * * @tparam dim Spatial dimension. * @tparam disp_order Polynomial order for displacement field. * @tparam StateSpace Finite element space for the internal variable (e.g., L2). * @tparam DisplacementTimeRule Time integration rule for displacement (must have num_states == 4). * @tparam InternalVarTimeRule Time integration rule for the internal variable (must have num_states == 2). + * @tparam Coupling CouplingSpec listing fields borrowed from other physics (default: no coupling). * @tparam parameter_space Parameter spaces for material properties. */ template + typename InternalVarTimeRule = BackwardEulerFirstOrderTimeIntegrationRule, + typename Coupling = CouplingSpec<>, typename... parameter_space> struct SolidMechanicsWithInternalVarsSystem : public SystemBase { using SystemBase::SystemBase; @@ -52,26 +71,32 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { "SolidMechanicsWithInternalVarsSystem requires a 2-state internal variable rule"); // Primary weak form: residual for displacement (u). - // Inputs: u, u_old, v_old, a_old, alpha, alpha_old, params... - /// using - using SolidWeakFormType = - TimeDiscretizedWeakForm, - Parameters, H1, H1, - H1, StateSpace, StateSpace, parameter_space...>>; + // Inputs: u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params... + using SolidWeakFormType = TimeDiscretizedWeakForm< + dim, H1, + typename detail::AppendCouplingToParams< + Coupling, + Parameters, H1, H1, H1, StateSpace, + StateSpace>, + parameter_space...>::type>; // State weak form: residual for internal variable (alpha). - // Inputs: alpha, alpha_old, u, u_old, v_old, a_old, params... - /// using - using StateWeakFormType = - TimeDiscretizedWeakForm, H1, - H1, H1, parameter_space...>>; - - // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, alpha, params... - /// using + // Inputs: alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params... + using StateWeakFormType = TimeDiscretizedWeakForm< + dim, StateSpace, + typename detail::AppendCouplingToParams< + Coupling, + Parameters, H1, H1, + H1>, + parameter_space...>::type>; + + // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, alpha, coupling_fields..., params... using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< dim, H1, - Parameters, H1, H1, StateSpace, parameter_space...>>; + typename detail::AppendCouplingToParams< + Coupling, + Parameters, H1, H1, StateSpace>, + parameter_space...>::type>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr state_weak_form; ///< Internal variable weak form. @@ -86,8 +111,8 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { /** * @brief Set the material model for the solid mechanics part. * - * The material is called as material(state, grad_u_current, alpha_current, params...) and - * must expose a `density` member for the cycle-zero acceleration solve. + * NOTE: works correctly only when Coupling = CouplingSpec<> (default). When coupling is active, + * register integrands directly on solid_weak_form. * * @tparam MaterialType The material model type. * @param material The material model instance. @@ -149,7 +174,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { auto alpha_dot = 0.0 * alpha; return force_function(t_info.time(), X, u, v, a, alpha, alpha_dot, params...); }, - std::make_index_sequence<4 + sizeof...(parameter_space)>{}); + std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -160,7 +185,8 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addBodyForce(const std::string& domain_name, BodyForceType force_function) { - addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); + addBodyForceAllParams(domain_name, force_function, + std::make_index_sequence<6 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -191,7 +217,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { auto alpha_dot = 0.0 * alpha; return traction_function(t_info.time(), X, n, u, v, a, alpha, alpha_dot, params...); }, - std::make_index_sequence<4 + sizeof...(parameter_space)>{}); + std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -202,7 +228,8 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addTraction(const std::string& domain_name, TractionType traction_function) { - addTractionAllParams(domain_name, traction_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); + addTractionAllParams(domain_name, traction_function, + std::make_index_sequence<6 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -249,7 +276,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { return pressure * n_deformed * (1.0 / n_shape_norm); }, - std::make_index_sequence<4 + sizeof...(parameter_space)>{}); + std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -260,11 +287,16 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addPressure(const std::string& domain_name, PressureType pressure_function) { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); + addPressureAllParams(domain_name, pressure_function, + std::make_index_sequence<6 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** * @brief Add the evolution law for the internal variable. + * + * NOTE: works correctly only when Coupling = CouplingSpec<> (default). When coupling is active, + * register integrands directly on state_weak_form. + * * @tparam EvolutionType The evolution law function type. * @param domain_name The name of the domain. * @param evolution_law Function (t_info, alpha, alpha_dot, grad_u, params...) returning the ODE residual. @@ -307,7 +339,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { addPressure(DependsOn(Is)...>{}, domain_name, pressure_function); } - // Cycle-zero helpers: use all-params DependsOn with the 5-state cycle-zero form (u, v, a, alpha, alpha_old) + // Cycle-zero helpers: use all-params DependsOn with the 4-state cycle-zero form (u, v, a, alpha) template void addCycleZeroBodySourceImpl(const std::string& name, IntegrandType f, std::index_sequence) { @@ -333,77 +365,160 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { } }; +// --------------------------------------------------------------------------- +// Options +// --------------------------------------------------------------------------- + template struct SolidMechanicsWithInternalVarsOptions { std::string prepend_name{}; + std::shared_ptr shared_field_store{}; ///< Shared store for coupled systems; nullptr = allocate own. std::shared_ptr cycle_zero_solver{}; }; +// --------------------------------------------------------------------------- +// FieldInfo — returned by register; consumed by buildFromStore +// --------------------------------------------------------------------------- + +/// @brief Returned by registerSolidMechanicsWithInternalVarsFields. +template +struct SolidMechanicsWithInternalVarsFieldInfo { + std::shared_ptr field_store; + FieldType> disp_type; + FieldType> disp_old_type; + FieldType> velo_old_type; + FieldType> accel_old_type; + FieldType state_type; + FieldType state_old_type; + std::tuple...> parameter_types; + std::shared_ptr disp_bc; + std::shared_ptr state_bc; + std::shared_ptr disp_time_rule_ptr; + std::shared_ptr state_time_rule_ptr; +}; + +// --------------------------------------------------------------------------- +// Phase 1: register fields +// --------------------------------------------------------------------------- + /** - * @brief Factory function to build a solid mechanics system with internal variable. - * @param mesh The mesh. - * @param solver The coupled system solver. - * @param disp_rule The displacement time integration rule. - * @param state_rule The internal-variable time integration rule. - * @param options System creation options. - * @param parameter_types Optional parameter field descriptors. + * @brief Register all solid-mechanics-with-internal-vars fields into a FieldStore. + * + * Phase 1 of the two-phase factory. Safe to call before other physics have registered their + * fields; weak form construction is deferred to buildSolidMechanicsWithInternalVarsSystemFromStore. + * When composing coupled systems, call all registerXxxFields functions before any buildXxxFromStore. */ -template -std::shared_ptr> -buildSolidMechanicsWithInternalVarsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, - InternalVarTimeRule state_rule, - SolidMechanicsWithInternalVarsOptions - options, - FieldType... parameter_types) +SolidMechanicsWithInternalVarsFieldInfo +registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_store, DisplacementTimeRule disp_rule, + InternalVarTimeRule state_rule, + FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100, options.prepend_name); - - // Add shape displacement FieldType> shape_disp_type("shape_displacement"); - field_store->addShapeDisp(shape_disp_type); + if (!field_store->hasField(shape_disp_type.name)) { + field_store->addShapeDisp(shape_disp_type); + } - // 1. Displacement fields (4-state second-order) + // Displacement fields (4-state second-order) auto disp_time_rule_ptr = std::make_shared(disp_rule); - FieldType> disp_type("displacement_solve_state"); + FieldType> disp_type("displacement_solve_state"); auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); - // 2. Internal variable fields (2-state first-order) + // Internal variable fields (2-state first-order) auto state_time_rule_ptr = std::make_shared(state_rule); FieldType state_type("state_solve_state"); auto state_bc = field_store->addIndependent(state_type, state_time_rule_ptr); auto state_old_type = field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); - // 3. Parameters + // Parameters auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; field_store->addParameter(pt); }; (prefix_param(parameter_types), ...); - using SystemType = SolidMechanicsWithInternalVarsSystem; + return {field_store, disp_type, disp_old_type, velo_old_type, accel_old_type, + state_type, state_old_type, std::make_tuple(parameter_types...), disp_bc, + state_bc, disp_time_rule_ptr, state_time_rule_ptr}; +} - // 4. Solid weak form: residual for u (inputs: u, u_old, v_old, a_old, alpha, alpha_old, params...) - std::string solid_res_name = field_store->prefix("solid_residual"); - auto solid_weak_form = std::make_shared( - solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, - state_type, state_old_type, parameter_types...)); +// --------------------------------------------------------------------------- +// Phase 2: build system from store +// --------------------------------------------------------------------------- - // 5. State weak form: residual for alpha (inputs: alpha, alpha_old, u, u_old, v_old, a_old, params...) +/** + * @brief Build a SolidMechanicsWithInternalVarsSystem from an already-populated FieldStore. + * + * Phase 2 of the two-phase factory. Constructs all weak forms from fields already registered + * in the store (including any coupling fields from other physics). + * + * @tparam Coupling CouplingSpec listing fields borrowed from other physics (leading tail positions). + */ +template +std::shared_ptr> +buildSolidMechanicsWithInternalVarsSystemFromStore( + SolidMechanicsWithInternalVarsFieldInfo + info, + std::shared_ptr solver, + const SolidMechanicsWithInternalVarsOptions& options, + const Coupling& coupling) +{ + auto& field_store = info.field_store; + auto& disp_type = info.disp_type; + auto& disp_old_type = info.disp_old_type; + auto& velo_old_type = info.velo_old_type; + auto& accel_old_type = info.accel_old_type; + auto& state_type = info.state_type; + auto& state_old_type = info.state_old_type; + auto parameter_types = info.parameter_types; + auto& disp_bc = info.disp_bc; + auto& state_bc = info.state_bc; + auto& disp_time_rule_ptr = info.disp_time_rule_ptr; + auto& state_time_rule_ptr = info.state_time_rule_ptr; + + using SystemType = SolidMechanicsWithInternalVarsSystem; + + // Solid weak form: (u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params...) + std::string solid_res_name = field_store->prefix("solid_residual"); + auto solid_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), + field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, + accel_old_type, state_type, state_old_type, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); + + // State weak form: (alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params...) std::string state_res_name = field_store->prefix("state_residual"); - auto state_weak_form = std::make_shared( - state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), - field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, disp_old_type, - velo_old_type, accel_old_type, parameter_types...)); + auto state_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), + field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, + disp_old_type, velo_old_type, accel_old_type, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form, state_weak_form}); @@ -415,18 +530,25 @@ buildSolidMechanicsWithInternalVarsSystem( sys->state_weak_form = state_weak_form; if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - // Cycle-zero: solve for acceleration (u, v, alpha given; a is the Jacobian unknown). std::string cycle_zero_name = field_store->prefix("solid_reaction"); auto accel_as_unknown = accel_old_type; accel_as_unknown.is_unknown = true; - FieldType> disp_cz_input(disp_type.name); + FieldType> disp_cz_input(disp_type.name); FieldType state_cz_input(state_type.name); - sys->cycle_zero_solid_weak_form = std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, - state_cz_input, parameter_types...)); + sys->cycle_zero_solid_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + cycle_zero_name, field_store->getMesh(), + field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, state_cz_input, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); field_store->markWeakFormInternal(cycle_zero_name); - // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); std::shared_ptr cz_solver; @@ -439,18 +561,49 @@ buildSolidMechanicsWithInternalVarsSystem( .max_iterations = 2, .print_level = 0}; LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + cz_solver = std::make_shared( + buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); } - sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); } return sys; } +// --------------------------------------------------------------------------- +// Standalone factory — thin wrapper, backwards-compatible +// --------------------------------------------------------------------------- + +/** + * @brief Standalone factory — allocates its own FieldStore and builds the full system. + * + * Thin wrapper around registerSolidMechanicsWithInternalVarsFields + + * buildSolidMechanicsWithInternalVarsSystemFromStore. Existing call sites are unchanged. + */ +template +std::shared_ptr, parameter_space...>> +buildSolidMechanicsWithInternalVarsSystem( + std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, + InternalVarTimeRule state_rule, + SolidMechanicsWithInternalVarsOptions + options, + FieldType... parameter_types) +{ + auto field_store = options.shared_field_store ? options.shared_field_store + : std::make_shared(mesh, 100, options.prepend_name); + auto info = registerSolidMechanicsWithInternalVarsFields( + field_store, disp_rule, state_rule, parameter_types...); + return buildSolidMechanicsWithInternalVarsSystemFromStore>( + info, solver, options, CouplingSpec<>{}); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index bbe95cbd55..78419ce523 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set(differentiable_numerics_test_source test_explicit_dynamics.cpp test_porous_heat_sink.cpp test_thermo_mechanics.cpp + test_combined_thermo_mechanics.cpp test_thermal_static.cpp test_solid_static_with_internal_vars.cpp test_multiphysics_time_integrator.cpp diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp new file mode 100644 index 0000000000..67eae7c69a --- /dev/null +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -0,0 +1,408 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file test_combined_thermo_mechanics.cpp + * @brief Demo of combineSystems: same coupled thermoelastic problem as test_thermo_mechanics + * but written with the new modular API. Results are diffed against the old + * buildThermoMechanicsSystem path to confirm correctness. + * + * Code-size comparison (staggered case): + * + * OLD (buildThermoMechanicsSystem): + * - Build system: 1 call (buildThermoMechanicsSystem) + * - Stagger config: manual addSubsystemSolver({0}, ...) + addSubsystemSolver({0,1}, ...) + * - BCs: system->disp_bc / system->temperature_bc + * - BCs: system->addSolidTraction, system->addSolidBodyForce, system->addHeatSource + * - Material: single system->setMaterial call + * + * NEW (combineSystems): + * - Register fields: registerSolidMechanicsFields + registerThermalFields + * - CouplingSpec: 2 declarations (solid borrows temp, thermal borrows disp) + * - Build systems: buildSolidMechanicsSystemFromStore + buildThermalSystemFromStore + * - Combine: combineSystems(solid, thermal) + * - Stagger: automatically handled by CombinedSystem::solve — no block-index wiring + * - BCs: solid->disp_bc / thermal->temperature_bc (cleaner naming) + * - BCs: solid->addTraction, solid->addBodyForce, thermal->addHeatSource + * - Material: setCoupledThermoMechanicsMaterial(solid, thermal, mat, domain) + * + * Win: no manual addSubsystemSolver block indices; each physics solver is independently + * configured and passed directly to its factory. + */ + +#include +#include "gtest/gtest.h" + +#include "smith/smith_config.hpp" +#include "smith/infrastructure/application_manager.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/physics/state/state_manager.hpp" +#include "smith/physics/mesh.hpp" + +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" // for reference solution +#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" +#include "smith/differentiable_numerics/differentiable_test_utils.hpp" + +namespace smith { + +static constexpr int dim = 3; +static constexpr int displacement_order = 1; +static constexpr int temperature_order = 1; + +// --------------------------------------------------------------------------- +// Coupled thermoelastic material — no user parameter fields; E is hardcoded. +// This avoids the shared-parameter registration issue (both physics needing the +// same L2 param in the same FieldStore) which requires a dedicated combineSystems +// API beyond this demo. All other coupling (solid<->thermal fields) is exercised. +// --------------------------------------------------------------------------- +template +auto greenStrainCombined(const tensor& grad_u) +{ + return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); +} + +struct ThermoelasticMaterialNoParam { + double density; + double E; + double nu; + double C_v; + double alpha; + double theta_ref; + double kappa; + using State = Empty; + template + auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, + const tensor& grad_theta) const + { + const auto K = E / (3.0 * (1.0 - 2.0 * nu)); + const auto G = 0.5 * E / (1.0 + nu); + const auto Eg = greenStrainCombined(grad_u); + const auto trEg = tr(Eg); + static constexpr auto I = Identity(); + const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; + auto F = grad_u + I; + const auto Piola = dot(F, S); + auto greenStrainRate = + 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); + const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate); + const auto q0 = -kappa * grad_theta; + return smith::tuple{Piola, C_v, s0, q0}; + } +}; + +// Also keep the parameterized version (with L2<0> E_param) for the OLD path comparison only. +template +auto greenStrainCombinedParam(const tensor& grad_u) +{ + return greenStrainCombined(grad_u); +} + +struct GreenSaintVenantThermoelasticMaterialCombined { + double density; + double E0; + double nu; + double C_v; + double alpha; + double theta_ref; + double kappa; + using State = Empty; + template + auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, + const tensor& grad_theta, const T5& E_param) const + { + auto E = E0 + get<0>(E_param); + const auto K = E / (3.0 * (1.0 - 2.0 * nu)); + const auto G = 0.5 * E / (1.0 + nu); + const auto Eg = greenStrainCombinedParam(grad_u); + const auto trEg = tr(Eg); + static constexpr auto I = Identity(); + const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; + auto F = grad_u + I; + const auto Piola = dot(F, S); + auto greenStrainRate = + 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); + const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; + const auto q0 = -kappa * grad_theta; + return smith::tuple{Piola, C_v, s0, q0}; + } +}; + +// --------------------------------------------------------------------------- +// Free function: register material integrands on separate solid + thermal systems. +// This is the new "coupled setMaterial" pattern when using combineSystems. +// +// Solid closure sees: (t_info, X, u, u_old, v_old, a_old, temp_solve_state, temperature, params...) +// Thermal closure sees: (t_info, X, T, T_old, disp, disp_old, velo, accel, params...) +// --------------------------------------------------------------------------- +template +void setCoupledThermoMechanicsMaterial( + std::shared_ptr, H1>, + P...>> solid, + std::shared_ptr, H1, + H1, H1>, + P...>> thermal, + const MaterialType& material, + const std::string& domain_name) +{ + auto captured_disp_rule = solid->disp_time_rule; + auto captured_temp_rule = thermal->temperature_time_rule; + + // Solid contribution: inertia + PK1 stress + solid->solid_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, + auto temperature, auto temperature_old, auto... params) { + auto [u_current, v_current, a_current] = + captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = + material(t_info.dt(), state, get(u_current), get(v_current), + get(T), get(T), params...); + return smith::tuple{get(a_current) * material.density, pk}; + }); + + // Thermal contribution: heat capacity * dT/dt - volumetric source, and heat flux + thermal->thermal_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto T, auto T_old, + auto disp, auto disp_old, auto v_old, auto a_old, auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = + material(t_info.dt(), state, get(u), get(v), + get(T_current), get(T_current), params...); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); +} + +// --------------------------------------------------------------------------- +// Fixture +// --------------------------------------------------------------------------- +struct CombinedThermoMechanicsMeshFixture : public testing::Test { + void SetUp() + { + datastore_ = std::make_unique(); + smith::StateManager::initialize(*datastore_, "solid"); + mesh_ = std::make_shared( + mfem::Mesh::MakeCartesian3D(24, 2, 2, mfem::Element::HEXAHEDRON, 1.2, 0.03, 0.03), "mesh", 0, 0); + mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); + } + std::unique_ptr datastore_; + std::shared_ptr mesh_; +}; + +// --------------------------------------------------------------------------- +// Helper: build and run the problem using the OLD buildThermoMechanicsSystem path +// --------------------------------------------------------------------------- +static auto runOldStaggered(std::shared_ptr mesh, + std::shared_ptr solid_block_solver, + std::shared_ptr thermal_block_solver) +{ + ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + + // OLD: manually wire block indices for stagger + auto staggered_solver = std::make_shared(10); + staggered_solver->addSubsystemSolver({0}, solid_block_solver); + staggered_solver->addSubsystemSolver({1}, thermal_block_solver); + + auto system = buildThermoMechanicsSystem( + mesh, staggered_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, + BackwardEulerFirstOrderTimeIntegrationRule{}, {}); + + system->setMaterial(material, mesh->entireBodyName()); + system->disp_bc->setFixedVectorBCs(mesh->domain("left")); + system->temperature_bc->setFixedScalarBCs(mesh->domain("left")); + system->temperature_bc->setFixedScalarBCs(mesh->domain("right")); + + constexpr double compressive_traction = 0.015; + constexpr double lateral_body_force = 2.5e-5; + constexpr double thermal_source = 1.0; + + system->addSolidTraction("right", [=](auto, auto X, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + system->addSolidBodyForce(mesh->entireBodyName(), [=](auto, auto X, auto... /*args*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + system->addHeatSource(mesh->entireBodyName(), + [=](auto, auto, auto, auto, auto, auto, auto) { return thermal_source; }); + + auto shape_disp = system->field_store->getShapeDisp(); + auto states = system->field_store->getStateFields(); + auto params = system->field_store->getParameterFields(); + std::vector reactions; + double time = 0.0; + std::tie(states, reactions) = + makeAdvancer(system)->advanceState(smith::TimeInfo(time, 1.0, 0), shape_disp, states, params); + + return std::make_pair( + mfem::Vector(*states[system->field_store->getFieldIndex("displacement_solve_state")].get()), + mfem::Vector(*states[system->field_store->getFieldIndex("temperature_solve_state")].get())); +} + +// --------------------------------------------------------------------------- +// Helper: build and run the problem using the NEW combineSystems path +// --------------------------------------------------------------------------- +static auto runNewCombined(std::shared_ptr mesh, + std::shared_ptr solid_block_solver, + std::shared_ptr thermal_block_solver) +{ + // Use no-param material — hardcoded E avoids the shared-parameter registration issue. + ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + + // ---- Phase 1: register all fields into a shared store ---- + auto field_store = std::make_shared(mesh, 100, ""); + auto solid_info = registerSolidMechanicsFields( + field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); + auto thermal_info = registerThermalFields( + field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); + + // ---- Declare coupling: each physics borrows fields from the other ---- + CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), + FieldType>("temperature")}; + CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), + FieldType>("displacement"), + FieldType>("velocity"), + FieldType>("acceleration")}; + + // ---- Phase 2: build each system with its own solver — no block-index wiring ---- + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + SolidMechanicsOptions solid_opts{}; + ThermalOptions thermal_opts{}; + + auto solid = buildSolidMechanicsSystemFromStore( + solid_info, std::make_shared(solid_block_solver), solid_opts, + solid_coupling); + auto thermal = buildThermalSystemFromStore( + thermal_info, std::make_shared(thermal_block_solver), thermal_opts, + thermal_coupling); + + // ---- Combine — stagger is automatic ---- + auto coupled = combineSystems(solid, thermal); + + // ---- Configure the problem ---- + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh->entireBodyName()); + solid->disp_bc->setFixedVectorBCs(mesh->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh->domain("right")); + + constexpr double compressive_traction = 0.015; + constexpr double lateral_body_force = 2.5e-5; + constexpr double thermal_source = 1.0; + + // Coupling fields appear as leading auto... after time-rule state args; absorb with /**/ + solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*coupling+params*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + solid->addBodyForce(mesh->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*coupling+params*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + thermal->addHeatSource(mesh->entireBodyName(), + [=](auto, auto, auto, auto... /*coupling+params*/) { return thermal_source; }); + + // ---- Solve ---- + auto shape_disp = field_store->getShapeDisp(); + auto states = field_store->getStateFields(); + auto params = field_store->getParameterFields(); + std::vector reactions; + double time = 0.0; + std::tie(states, reactions) = + makeAdvancer(coupled)->advanceState(smith::TimeInfo(time, 1.0, 0), shape_disp, states, params); + + return std::make_pair( + mfem::Vector(*states[field_store->getFieldIndex("displacement_solve_state")].get()), + mfem::Vector(*states[field_store->getFieldIndex("temperature_solve_state")].get())); +} + +// --------------------------------------------------------------------------- +// Test: new combineSystems result matches old buildThermoMechanicsSystem +// --------------------------------------------------------------------------- +TEST_F(CombinedThermoMechanicsMeshFixture, StaggeredResultsMatchOldPath) +{ + smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, + .preconditioner = smith::Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 120, + .print_level = 0}; + smith::NonlinearSolverOptions mech_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::TrustRegion, + .relative_tol = 1e-6, + .absolute_tol = 1e-7, + .max_iterations = 25, + .print_level = 0}; + smith::LinearSolverOptions therm_lin_opts{.linear_solver = smith::LinearSolver::GMRES, + .preconditioner = smith::Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80, + .print_level = 0}; + smith::NonlinearSolverOptions therm_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-7, + .absolute_tol = 1e-7, + .max_iterations = 12, + .max_line_search_iterations = 6, + .print_level = 0}; + + auto solid_solver_old = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); + auto thermal_solver_old = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); + auto old_result = runOldStaggered(mesh_, solid_solver_old, thermal_solver_old); + + // Reset state manager between runs (same pattern as test_thermo_mechanics) + mesh_.reset(); + smith::StateManager::reset(); + SetUp(); + + auto solid_solver_new = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); + auto thermal_solver_new = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); + auto new_result = runNewCombined(mesh_, solid_solver_new, thermal_solver_new); + + double disp_diff = + mfem::Vector(old_result.first).Add(-1.0, new_result.first).Normlinf(); + double temp_diff = + mfem::Vector(old_result.second).Add(-1.0, new_result.second).Normlinf(); + + SLIC_INFO_ROOT("Old vs new displacement diff: " << disp_diff); + SLIC_INFO_ROOT("Old vs new temperature diff: " << temp_diff); + + // Both approaches run the same number of stagger iterations so results should be + // numerically identical (same tolerances, same solvers, same mesh). + EXPECT_LT(disp_diff, 1e-4); + EXPECT_LT(temp_diff, 1e-4); +} + +} // namespace smith + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + smith::ApplicationManager applicationManager(argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 3ec49df88e..3b05a1eaf9 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -20,6 +20,7 @@ #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/coupling_spec.hpp" namespace smith { @@ -33,19 +34,23 @@ namespace smith { * @tparam dim Spatial dimension. * @tparam temp_order Order of the temperature basis. * @tparam TemperatureTimeRule Time integration rule type (must have num_states == 2). + * @tparam Coupling CouplingSpec listing fields borrowed from other physics (default: none). + * Coupling fields occupy leading positions in the tail after the 2 time-rule state fields, + * before user parameter_space fields. * @tparam parameter_space Finite element spaces for optional parameters. */ template + typename Coupling = CouplingSpec<>, typename... parameter_space> struct ThermalSystem : public SystemBase { using SystemBase::SystemBase; static_assert(TemperatureTimeRule::num_states == 2, "ThermalSystem requires a 2-state time integration rule"); - /// @brief using for ThermalWeakFormType - using ThermalWeakFormType = - TimeDiscretizedWeakForm, - TimeRuleParams, parameter_space...>>; + /// Thermal weak form: (temp, temp_old, coupling_fields..., params...) + using ThermalWeakFormType = TimeDiscretizedWeakForm< + dim, H1, + typename detail::TimeRuleParamsWithCoupling, Coupling, + parameter_space...>::type>; std::shared_ptr thermal_weak_form; ///< Thermal weak form. std::shared_ptr temperature_bc; ///< Temperature boundary conditions. @@ -104,7 +109,7 @@ struct ThermalSystem : public SystemBase { template void addHeatSource(const std::string& domain_name, HeatSourceType source_function) { - addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence<2 + sizeof...(parameter_space)>{}); + addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence<2 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } /** @@ -135,7 +140,7 @@ struct ThermalSystem : public SystemBase { template void addHeatFlux(const std::string& boundary_name, HeatFluxType flux_function) { - addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence<2 + sizeof...(parameter_space)>{}); + addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence<2 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); } private: @@ -155,35 +160,39 @@ struct ThermalSystem : public SystemBase { template struct ThermalOptions { std::string prepend_name{}; + std::shared_ptr shared_field_store{}; ///< Shared store for coupled systems; nullptr = allocate own. +}; + +/// @brief Returned by registerThermalFields; holds the FieldType tokens needed by buildThermalSystemFromStore. +template +struct ThermalFieldInfo { + std::shared_ptr field_store; + FieldType> temperature_type; + FieldType> temperature_old_type; + std::tuple...> parameter_types; + std::shared_ptr temperature_bc; + std::shared_ptr temperature_time_rule_ptr; }; /** - * @brief Factory function to build a thermal system. - * @tparam dim Spatial dimension. - * @tparam temp_order Order of the temperature basis. - * @tparam TemperatureTimeRule Time integration rule type (must have num_states == 2). - * @tparam parameter_space Finite element spaces for optional parameters. - * @param mesh The mesh. - * @param solver The coupled system solver. - * @param temp_rule The time integration rule for temperature. - * @param options System creation options. - * @param parameter_types Parameter field types. - * @return ThermalSystem with all components initialized. + * @brief Register all thermal fields into a FieldStore. + * + * Phase 1 of the two-phase factory. Safe to call before other physics have registered their + * fields; weak form construction is deferred to buildThermalSystemFromStore. */ template -std::shared_ptr> buildThermalSystem( - std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_rule, - ThermalOptions options, +ThermalFieldInfo registerThermalFields( + std::shared_ptr field_store, TemperatureTimeRule temp_rule, FieldType... parameter_types) { - auto field_store = std::make_shared(mesh, 100, options.prepend_name); - FieldType> shape_disp_type("shape_displacement"); - field_store->addShapeDisp(shape_disp_type); + if (!field_store->hasField(shape_disp_type.name)) { + field_store->addShapeDisp(shape_disp_type); + } - auto temperature_time_rule = std::make_shared(temp_rule); + auto temperature_time_rule_ptr = std::make_shared(temp_rule); FieldType> temperature_type("temperature_solve_state"); - auto temperature_bc = field_store->addIndependent(temperature_type, temperature_time_rule); + auto temperature_bc = field_store->addIndependent(temperature_type, temperature_time_rule_ptr); auto temperature_old_type = field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); @@ -193,21 +202,76 @@ std::shared_ptr; + return {field_store, temperature_type, temperature_old_type, + std::make_tuple(parameter_types...), temperature_bc, temperature_time_rule_ptr}; +} + +/** + * @brief Build a ThermalSystem from an already-populated FieldStore. + * + * Phase 2 of the two-phase factory. Constructs all weak forms using fields already registered + * in the store (including any coupling fields from other physics). + * + * @tparam Coupling CouplingSpec listing fields borrowed from other physics (leading tail positions). + */ +template +std::shared_ptr> +buildThermalSystemFromStore(ThermalFieldInfo info, + std::shared_ptr solver, + const ThermalOptions& /*options*/, + const Coupling& coupling) +{ + auto& field_store = info.field_store; + auto& temperature_type = info.temperature_type; + auto& temperature_old_type = info.temperature_old_type; + auto parameter_types = info.parameter_types; + auto& temperature_bc = info.temperature_bc; + auto& temperature_time_rule_ptr = info.temperature_time_rule_ptr; + + using SystemType = ThermalSystem; std::string thermal_flux_name = field_store->prefix("thermal_flux"); - auto thermal_weak_form = std::make_shared( - thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), - field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, - parameter_types...)); + auto thermal_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + thermal_flux_name, field_store->getMesh(), + field_store->getField(temperature_type.name).get()->space(), + field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, + temperature_old_type, cfs..., params...)); + }, + coupling.fields); + }, + parameter_types); auto sys = std::make_shared(field_store, solver, std::vector>{thermal_weak_form}); sys->temperature_bc = temperature_bc; - sys->temperature_time_rule = temperature_time_rule; + sys->temperature_time_rule = temperature_time_rule_ptr; sys->thermal_weak_form = thermal_weak_form; return sys; } +/** + * @brief Standalone factory — allocates its own FieldStore and builds the full system. + * + * Thin wrapper around registerThermalFields + buildThermalSystemFromStore. + * Existing call sites are unchanged. + */ +template +std::shared_ptr, parameter_space...>> +buildThermalSystem(std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_rule, + ThermalOptions options, + FieldType... parameter_types) +{ + auto field_store = options.shared_field_store + ? options.shared_field_store + : std::make_shared(mesh, 100, options.prepend_name); + auto info = registerThermalFields(field_store, temp_rule, parameter_types...); + return buildThermalSystemFromStore>(info, solver, options, + CouplingSpec<>{}); +} + } // namespace smith From e7456fca3a023a0054606f00a5c67a40fc98dc77 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 15 Apr 2026 16:42:42 -0600 Subject: [PATCH 14/67] Increment on the path to coupled systems. --- .../differentiable_numerics/CMakeLists.txt | 1 - .../combined_system.hpp | 61 ++ .../solid_mechanics_system.hpp | 8 +- ...id_mechanics_with_internal_vars_system.hpp | 8 +- .../tests/CMakeLists.txt | 1 - .../tests/test_combined_thermo_mechanics.cpp | 531 +++++++++++------- .../tests/test_thermo_mechanics.cpp | 313 ----------- .../thermo_mechanics_system.hpp | 453 --------------- 8 files changed, 398 insertions(+), 978 deletions(-) delete mode 100644 src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp delete mode 100644 src/smith/differentiable_numerics/thermo_mechanics_system.hpp diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 2cbe291b82..b325b58eeb 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -41,7 +41,6 @@ set(differentiable_numerics_headers solid_mechanics_system.hpp solid_mechanics_with_internal_vars_system.hpp thermal_system.hpp - thermo_mechanics_system.hpp coupling_spec.hpp combined_system.hpp system_base.hpp diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index 8ff4d125e9..b4a2398288 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -42,6 +42,7 @@ #include #include +#include #include "smith/differentiable_numerics/system_base.hpp" #include "smith/differentiable_numerics/field_store.hpp" @@ -139,4 +140,64 @@ std::shared_ptr combineSystems(std::shared_ptr... su return combined; } + +/** + * @brief A generic wrapper that combines multiple sub-systems into a single monolithic block system. + * + * Unlike CombinedSystem (which performs staggered solver iterations), MonolithicCombinedSystem + * concatenates all weak forms and solves them simultaneously using a single global SystemSolver. + */ +struct MonolithicCombinedSystem : public SystemBase { + std::shared_ptr cycle_zero_system; + + using SystemBase::SystemBase; +}; + +/** + * @brief Combine two or more independently-built sub-systems into a MonolithicCombinedSystem. + * + * Preconditions: + * - All sub-systems share the same FieldStore. + * - Sub-system weak_forms are already populated. + * + * @param solver The monolithic SystemSolver that will solve the combined block system, + * including the aggregated cycle-zero system if any sub-systems have one. + * @param subs Two or more sub-systems that share a FieldStore. + */ +template +std::shared_ptr combineSystemsMonolithic( + std::shared_ptr solver, + std::shared_ptr... subs) +{ + static_assert(sizeof...(subs) >= 2, "combineSystemsMonolithic requires at least two sub-systems"); + + auto field_store = std::get<0>(std::forward_as_tuple(subs...))->field_store; + + std::vector> wfs; + std::vector> cycle_zero_wfs; + + ( + [&](auto& sub) { + for (auto& wf : sub->weak_forms) { + wfs.push_back(wf); + } + if constexpr (requires { sub->cycle_zero_system; }) { + if (sub->cycle_zero_system) { + for (auto& cz_wf : sub->cycle_zero_system->weak_forms) { + cycle_zero_wfs.push_back(cz_wf); + } + } + } + }(subs), + ...); + + auto combined = std::make_shared(field_store, solver, wfs); + if (!cycle_zero_wfs.empty()) { + combined->cycle_zero_system = std::make_shared(field_store, solver, cycle_zero_wfs); + } + + return combined; +} + } // namespace smith + diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 4ce565a2b7..44a081f767 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -432,9 +432,9 @@ buildSolidMechanicsSystemFromStore( field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - std::shared_ptr cz_solver; + std::shared_ptr cycle_zero_solver; if (options.cycle_zero_solver) { - cz_solver = options.cycle_zero_solver; + cycle_zero_solver = options.cycle_zero_solver; } else { NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, @@ -447,10 +447,10 @@ buildSolidMechanicsSystemFromStore( .absolute_tol = 1e-14, .max_iterations = 1000, .print_level = 0}; - cz_solver = std::make_shared( + cycle_zero_solver = std::make_shared( buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); } - sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); + sys->cycle_zero_system = makeSubSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); } if (options.enable_stress_output) { diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index f4233d00be..8ace111433 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -551,9 +551,9 @@ buildSolidMechanicsWithInternalVarsSystemFromStore( field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - std::shared_ptr cz_solver; + std::shared_ptr cycle_zero_solver; if (options.cycle_zero_solver) { - cz_solver = options.cycle_zero_solver; + cycle_zero_solver = options.cycle_zero_solver; } else { NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, @@ -566,10 +566,10 @@ buildSolidMechanicsWithInternalVarsSystemFromStore( .absolute_tol = 1e-14, .max_iterations = 1000, .print_level = 0}; - cz_solver = std::make_shared( + cycle_zero_solver = std::make_shared( buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); } - sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_solid_weak_form}); + sys->cycle_zero_system = makeSubSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); } return sys; diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index 78419ce523..0c95a23a34 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -12,7 +12,6 @@ set(differentiable_numerics_test_source test_solid_dynamics.cpp test_explicit_dynamics.cpp test_porous_heat_sink.cpp - test_thermo_mechanics.cpp test_combined_thermo_mechanics.cpp test_thermal_static.cpp test_solid_static_with_internal_vars.cpp diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 67eae7c69a..7f0ba1a787 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -4,35 +4,6 @@ // // SPDX-License-Identifier: (BSD-3-Clause) -/** - * @file test_combined_thermo_mechanics.cpp - * @brief Demo of combineSystems: same coupled thermoelastic problem as test_thermo_mechanics - * but written with the new modular API. Results are diffed against the old - * buildThermoMechanicsSystem path to confirm correctness. - * - * Code-size comparison (staggered case): - * - * OLD (buildThermoMechanicsSystem): - * - Build system: 1 call (buildThermoMechanicsSystem) - * - Stagger config: manual addSubsystemSolver({0}, ...) + addSubsystemSolver({0,1}, ...) - * - BCs: system->disp_bc / system->temperature_bc - * - BCs: system->addSolidTraction, system->addSolidBodyForce, system->addHeatSource - * - Material: single system->setMaterial call - * - * NEW (combineSystems): - * - Register fields: registerSolidMechanicsFields + registerThermalFields - * - CouplingSpec: 2 declarations (solid borrows temp, thermal borrows disp) - * - Build systems: buildSolidMechanicsSystemFromStore + buildThermalSystemFromStore - * - Combine: combineSystems(solid, thermal) - * - Stagger: automatically handled by CombinedSystem::solve — no block-index wiring - * - BCs: solid->disp_bc / thermal->temperature_bc (cleaner naming) - * - BCs: solid->addTraction, solid->addBodyForce, thermal->addHeatSource - * - Material: setCoupledThermoMechanicsMaterial(solid, thermal, mat, domain) - * - * Win: no manual addSubsystemSolver block indices; each physics solver is independently - * configured and passed directly to its factory. - */ - #include #include "gtest/gtest.h" @@ -47,9 +18,11 @@ #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" -#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" // for reference solution #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" +#include "smith/differentiable_numerics/nonlinear_solve.hpp" +#include "smith/physics/functional_objective.hpp" +#include "gretl/wang_checkpoint_strategy.hpp" namespace smith { @@ -57,34 +30,30 @@ static constexpr int dim = 3; static constexpr int displacement_order = 1; static constexpr int temperature_order = 1; -// --------------------------------------------------------------------------- -// Coupled thermoelastic material — no user parameter fields; E is hardcoded. -// This avoids the shared-parameter registration issue (both physics needing the -// same L2 param in the same FieldStore) which requires a dedicated combineSystems -// API beyond this demo. All other coupling (solid<->thermal fields) is exercised. -// --------------------------------------------------------------------------- template -auto greenStrainCombined(const tensor& grad_u) +auto greenStrain(const tensor& grad_u) { return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); } -struct ThermoelasticMaterialNoParam { +// Material with E parameter +struct GreenSaintVenantThermoelasticMaterial { double density; - double E; + double E0; double nu; double C_v; double alpha; double theta_ref; double kappa; using State = Empty; - template + template auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta) const + const tensor& grad_theta, const T5& E_param) const { + auto E = E0 + get<0>(E_param); const auto K = E / (3.0 * (1.0 - 2.0 * nu)); const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrainCombined(grad_u); + const auto Eg = greenStrain(grad_u); const auto trEg = tr(Eg); static constexpr auto I = Identity(); const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; @@ -92,36 +61,29 @@ struct ThermoelasticMaterialNoParam { const auto Piola = dot(F, S); auto greenStrainRate = 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate); + const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; const auto q0 = -kappa * grad_theta; return smith::tuple{Piola, C_v, s0, q0}; } }; -// Also keep the parameterized version (with L2<0> E_param) for the OLD path comparison only. -template -auto greenStrainCombinedParam(const tensor& grad_u) -{ - return greenStrainCombined(grad_u); -} - -struct GreenSaintVenantThermoelasticMaterialCombined { +// Material without user parameters +struct ThermoelasticMaterialNoParam { double density; - double E0; + double E; double nu; double C_v; double alpha; double theta_ref; double kappa; using State = Empty; - template + template auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta, const T5& E_param) const + const tensor& grad_theta) const { - auto E = E0 + get<0>(E_param); const auto K = E / (3.0 * (1.0 - 2.0 * nu)); const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrainCombinedParam(grad_u); + const auto Eg = greenStrain(grad_u); const auto trEg = tr(Eg); static constexpr auto I = Identity(); const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; @@ -129,19 +91,12 @@ struct GreenSaintVenantThermoelasticMaterialCombined { const auto Piola = dot(F, S); auto greenStrainRate = 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; + const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate); const auto q0 = -kappa * grad_theta; return smith::tuple{Piola, C_v, s0, q0}; } }; -// --------------------------------------------------------------------------- -// Free function: register material integrands on separate solid + thermal systems. -// This is the new "coupled setMaterial" pattern when using combineSystems. -// -// Solid closure sees: (t_info, X, u, u_old, v_old, a_old, temp_solve_state, temperature, params...) -// Thermal closure sees: (t_info, X, T, T_old, disp, disp_old, velo, accel, params...) -// --------------------------------------------------------------------------- template void setCoupledThermoMechanicsMaterial( @@ -152,8 +107,7 @@ void setCoupledThermoMechanicsMaterial( std::shared_ptr, H1, - H1, H1>, - P...>> thermal, + H1, H1, P...>>> thermal, const MaterialType& material, const std::string& domain_name) { @@ -176,7 +130,22 @@ void setCoupledThermoMechanicsMaterial( return smith::tuple{get(a_current) * material.density, pk}; }); - // Thermal contribution: heat capacity * dT/dt - volumetric source, and heat flux + // Cycle-zero + if (solid->cycle_zero_solid_weak_form) { + solid->cycle_zero_solid_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto u, auto v, auto a, + auto temperature, auto temperature_old, auto... params) { + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = + material(t_info.dt(), state, get(u), get(v), + get(T), get(T), params...); + return smith::tuple{get(a) * material.density, pk}; + }); + } + + // Thermal contribution thermal->thermal_weak_form->addBodyIntegral( domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, @@ -192,10 +161,7 @@ void setCoupledThermoMechanicsMaterial( }); } -// --------------------------------------------------------------------------- -// Fixture -// --------------------------------------------------------------------------- -struct CombinedThermoMechanicsMeshFixture : public testing::Test { +struct ThermoMechanicsMeshFixture : public testing::Test { void SetUp() { datastore_ = std::make_unique(); @@ -209,144 +175,146 @@ struct CombinedThermoMechanicsMeshFixture : public testing::Test { std::shared_ptr mesh_; }; -// --------------------------------------------------------------------------- -// Helper: build and run the problem using the OLD buildThermoMechanicsSystem path -// --------------------------------------------------------------------------- -static auto runOldStaggered(std::shared_ptr mesh, - std::shared_ptr solid_block_solver, - std::shared_ptr thermal_block_solver) +// 1. CreateDifferentiablePhysicsAllocatesReactionInfo +TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionInfo) { - ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - - // OLD: manually wire block indices for stagger - auto staggered_solver = std::make_shared(10); - staggered_solver->addSubsystemSolver({0}, solid_block_solver); - staggered_solver->addSubsystemSolver({1}, thermal_block_solver); - - auto system = buildThermoMechanicsSystem( - mesh, staggered_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, {}); + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 4}; - system->setMaterial(material, mesh->entireBodyName()); - system->disp_bc->setFixedVectorBCs(mesh->domain("left")); - system->temperature_bc->setFixedScalarBCs(mesh->domain("left")); - system->temperature_bc->setFixedScalarBCs(mesh->domain("right")); + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; - - system->addSolidTraction("right", [=](auto, auto X, auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -compressive_traction; - return traction; - }); - system->addSolidBodyForce(mesh->entireBodyName(), [=](auto, auto X, auto... /*args*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - system->addHeatSource(mesh->entireBodyName(), - [=](auto, auto, auto, auto, auto, auto, auto) { return thermal_source; }); + auto field_store = std::make_shared(mesh_, 100, ""); + FieldType> youngs_modulus("youngs_modulus"); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}, youngs_modulus); + auto thermal_info = registerThermalFields(field_store, TempRule{}); - auto shape_disp = system->field_store->getShapeDisp(); - auto states = system->field_store->getStateFields(); - auto params = system->field_store->getParameterFields(); - std::vector reactions; - double time = 0.0; - std::tie(states, reactions) = - makeAdvancer(system)->advanceState(smith::TimeInfo(time, 1.0, 0), shape_disp, states, params); + CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), + FieldType>("temperature")}; + CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), + FieldType>("displacement"), + FieldType>("velocity"), + FieldType>("acceleration"), + FieldType>("param_youngs_modulus")}; + + SolidMechanicsOptions> solid_opts{.shared_field_store = field_store}; + ThermalOptions thermal_opts{.shared_field_store = field_store}; + + auto solid = buildSolidMechanicsSystemFromStore( + solid_info, std::make_shared(solid_block_solver), solid_opts, solid_coupling); + + auto thermal = buildThermalSystemFromStore( + thermal_info, std::make_shared(thermal_block_solver), thermal_opts, thermal_coupling); + + auto coupled = combineSystems(solid, thermal); - return std::make_pair( - mfem::Vector(*states[system->field_store->getFieldIndex("displacement_solve_state")].get()), - mfem::Vector(*states[system->field_store->getFieldIndex("temperature_solve_state")].get())); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); + const auto& solid_dual_space = physics->dual("reactions").space(); + const auto& solid_state_space = physics->state("displacement_solve_state").space(); + const auto& thermal_dual_space = physics->dual("thermal_flux").space(); + const auto& thermal_state_space = physics->state("temperature_solve_state").space(); + + EXPECT_EQ(physics->dualNames().size(), 2); + EXPECT_EQ(physics->dualNames()[0], "reactions"); + EXPECT_EQ(physics->dualNames()[1], "thermal_flux"); + EXPECT_EQ(solid_dual_space.GetMesh(), solid_state_space.GetMesh()); + EXPECT_STREQ(solid_dual_space.FEColl()->Name(), solid_state_space.FEColl()->Name()); + EXPECT_EQ(solid_dual_space.GetVDim(), solid_state_space.GetVDim()); + EXPECT_EQ(solid_dual_space.TrueVSize(), solid_state_space.TrueVSize()); + EXPECT_EQ(thermal_dual_space.GetMesh(), thermal_state_space.GetMesh()); + EXPECT_STREQ(thermal_dual_space.FEColl()->Name(), thermal_state_space.FEColl()->Name()); + EXPECT_EQ(thermal_dual_space.GetVDim(), thermal_state_space.GetVDim()); + EXPECT_EQ(thermal_dual_space.TrueVSize(), thermal_state_space.TrueVSize()); } -// --------------------------------------------------------------------------- -// Helper: build and run the problem using the NEW combineSystems path -// --------------------------------------------------------------------------- -static auto runNewCombined(std::shared_ptr mesh, - std::shared_ptr solid_block_solver, - std::shared_ptr thermal_block_solver) +// 2. BackpropagateThroughPhysics +TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) { - // Use no-param material — hardcoded E avoids the shared-parameter registration issue. - ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 4}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - // ---- Phase 1: register all fields into a shared store ---- - auto field_store = std::make_shared(mesh, 100, ""); - auto solid_info = registerSolidMechanicsFields( - field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); - auto thermal_info = registerThermalFields( - field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); + auto field_store = std::make_shared(mesh_, 100, ""); + FieldType> youngs_modulus("youngs_modulus"); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}, youngs_modulus); + auto thermal_info = registerThermalFields(field_store, TempRule{}); - // ---- Declare coupling: each physics borrows fields from the other ---- CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), FieldType>("temperature")}; CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), FieldType>("displacement"), FieldType>("velocity"), - FieldType>("acceleration")}; - - // ---- Phase 2: build each system with its own solver — no block-index wiring ---- - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - SolidMechanicsOptions solid_opts{}; - ThermalOptions thermal_opts{}; - - auto solid = buildSolidMechanicsSystemFromStore( - solid_info, std::make_shared(solid_block_solver), solid_opts, - solid_coupling); - auto thermal = buildThermalSystemFromStore( - thermal_info, std::make_shared(thermal_block_solver), thermal_opts, - thermal_coupling); - - // ---- Combine — stagger is automatic ---- + FieldType>("acceleration"), + FieldType>("param_youngs_modulus")}; // thermal borrows parameter + + SolidMechanicsOptions> solid_opts{.shared_field_store = field_store}; + ThermalOptions thermal_opts{.shared_field_store = field_store}; + + auto solid = buildSolidMechanicsSystemFromStore( + solid_info, std::make_shared(solid_block_solver), solid_opts, solid_coupling); + + auto thermal = buildThermalSystemFromStore( + thermal_info, std::make_shared(thermal_block_solver), thermal_opts, thermal_coupling); + auto coupled = combineSystems(solid, thermal); - // ---- Configure the problem ---- - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh->entireBodyName()); - solid->disp_bc->setFixedVectorBCs(mesh->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh->domain("right")); + GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; + coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [=](smith::tensor) { return 100.0; }); + + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - // Coupling fields appear as leading auto... after time-rule state args; absorb with /**/ - solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*coupling+params*/) { + solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { auto traction = 0.0 * X; - traction[0] = -compressive_traction; + traction[0] = -0.015; return traction; }); - solid->addBodyForce(mesh->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*coupling+params*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - thermal->addHeatSource(mesh->entireBodyName(), - [=](auto, auto, auto, auto... /*coupling+params*/) { return thermal_source; }); - // ---- Solve ---- - auto shape_disp = field_store->getShapeDisp(); - auto states = field_store->getStateFields(); - auto params = field_store->getParameterFields(); - std::vector reactions; - double time = 0.0; - std::tie(states, reactions) = - makeAdvancer(coupled)->advanceState(smith::TimeInfo(time, 1.0, 0), shape_disp, states, params); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); + + // Run forward + double dt = 1.0; + for (int step = 0; step < 2; ++step) { + physics->advanceTimestep(dt); + } + + auto reactions = physics->getReactionStates(); + auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); + + gretl::set_as_objective(obj); + obj.data_store().back_prop(); - return std::make_pair( - mfem::Vector(*states[field_store->getFieldIndex("displacement_solve_state")].get()), - mfem::Vector(*states[field_store->getFieldIndex("temperature_solve_state")].get())); + auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + EXPECT_TRUE(param_sens->Norml2() > 0.0); } -// --------------------------------------------------------------------------- -// Test: new combineSystems result matches old buildThermoMechanicsSystem -// --------------------------------------------------------------------------- -TEST_F(CombinedThermoMechanicsMeshFixture, StaggeredResultsMatchOldPath) +// 3. StaggeredBucklingChallenge +// Replaces MonolithicBucklingChallenge: verifies convergence and displacement of the staggered combined solver. +TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) { + constexpr double compressive_traction = 0.015; + constexpr double lateral_body_force = 2.5e-5; + constexpr double thermal_source = 1.0; + smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, .preconditioner = smith::Preconditioner::HypreAMG, .relative_tol = 1e-6, @@ -358,6 +326,7 @@ TEST_F(CombinedThermoMechanicsMeshFixture, StaggeredResultsMatchOldPath) .absolute_tol = 1e-7, .max_iterations = 25, .print_level = 0}; + smith::LinearSolverOptions therm_lin_opts{.linear_solver = smith::LinearSolver::GMRES, .preconditioner = smith::Preconditioner::HypreAMG, .relative_tol = 1e-6, @@ -371,31 +340,189 @@ TEST_F(CombinedThermoMechanicsMeshFixture, StaggeredResultsMatchOldPath) .max_line_search_iterations = 6, .print_level = 0}; - auto solid_solver_old = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); - auto thermal_solver_old = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); - auto old_result = runOldStaggered(mesh_, solid_solver_old, thermal_solver_old); + auto solid_block_solver = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}); + auto thermal_info = registerThermalFields(field_store, TempRule{}); + + CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), + FieldType>("temperature")}; + CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), + FieldType>("displacement"), + FieldType>("velocity"), + FieldType>("acceleration")}; + + SolidMechanicsOptions solid_opts{.shared_field_store = field_store}; + ThermalOptions thermal_opts{.shared_field_store = field_store}; - // Reset state manager between runs (same pattern as test_thermo_mechanics) - mesh_.reset(); - smith::StateManager::reset(); - SetUp(); + auto solid = buildSolidMechanicsSystemFromStore( + solid_info, std::make_shared(solid_block_solver), solid_opts, solid_coupling); + auto thermal = buildThermalSystemFromStore( + thermal_info, std::make_shared(thermal_block_solver), thermal_opts, thermal_coupling); - auto solid_solver_new = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); - auto thermal_solver_new = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); - auto new_result = runNewCombined(mesh_, solid_solver_new, thermal_solver_new); + auto coupled = combineSystems(solid, thermal); + + ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - double disp_diff = - mfem::Vector(old_result.first).Add(-1.0, new_result.first).Normlinf(); - double temp_diff = - mfem::Vector(old_result.second).Add(-1.0, new_result.second).Normlinf(); + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); + + solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + thermal->addHeatSource(mesh_->entireBodyName(), + [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + + SLIC_INFO_ROOT("Starting staggered thermo-mechanics solve"); + + double dt = 1.0; + double time = 0.0; + auto shape_disp = field_store->getShapeDisp(); + auto states = field_store->getStateFields(); + auto params = field_store->getParameterFields(); + std::vector reactions; + for (size_t step = 0; step < 1; ++step) { + std::tie(states, reactions) = + makeAdvancer(coupled)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + time += dt; + } + + mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); + + bool staggered_solid_converged = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); + int staggered_solid_iterations = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + bool staggered_thermal_converged = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); + int staggered_thermal_iterations = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + + double staggered_lateral_deflection = 0.0; + for (int i = 1; i < final_disp.Size(); i += dim) { + staggered_lateral_deflection = std::max(staggered_lateral_deflection, std::abs(final_disp(i))); + } + + SLIC_INFO_ROOT("Staggered solid converged: " << staggered_solid_converged + << ", iterations: " << staggered_solid_iterations); + SLIC_INFO_ROOT("Staggered thermal converged: " << staggered_thermal_converged + << ", iterations: " << staggered_thermal_iterations); + SLIC_INFO_ROOT("Staggered max lateral deflection: " << staggered_lateral_deflection); + + EXPECT_TRUE(staggered_solid_converged); + EXPECT_TRUE(staggered_thermal_converged); + EXPECT_GT(staggered_lateral_deflection, 1e-5); +} + + +// 4. MonolithicBucklingChallenge +// Verifies convergence and displacement of the monolithic combined solver. +TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) +{ + constexpr double compressive_traction = 0.015; + constexpr double lateral_body_force = 2.5e-5; + constexpr double thermal_source = 1.0; + + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80, + .print_level = 0}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-7, + .absolute_tol = 1e-7, + .max_iterations = 12, + .print_level = 0}; + + auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto solver_ptr = std::make_shared(block_solver); + + auto field_store = std::make_shared(mesh_, 100, ""); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + // Notice that the block_solver is the SAME solver for the whole system + auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}); + auto thermal_info = registerThermalFields(field_store, TempRule{}); + + CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), + FieldType>("temperature")}; + CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), + FieldType>("displacement"), + FieldType>("velocity"), + FieldType>("acceleration")}; + + SolidMechanicsOptions solid_opts{.shared_field_store = field_store}; + ThermalOptions thermal_opts{.shared_field_store = field_store}; + + auto solid = buildSolidMechanicsSystemFromStore( + solid_info, nullptr, solid_opts, solid_coupling); + auto thermal = buildThermalSystemFromStore( + thermal_info, nullptr, thermal_opts, thermal_coupling); + + auto coupled = combineSystemsMonolithic(solver_ptr, solid, thermal); + + ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); + + solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + thermal->addHeatSource(mesh_->entireBodyName(), + [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + + SLIC_INFO_ROOT("Starting monolithic thermo-mechanics solve"); + + double dt = 1.0; + double time = 0.0; + auto shape_disp = field_store->getShapeDisp(); + auto states = field_store->getStateFields(); + auto params = field_store->getParameterFields(); + std::vector reactions; + for (size_t step = 0; step < 1; ++step) { + std::tie(states, reactions) = + makeAdvancer(coupled)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + time += dt; + } + + mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); + + bool converged = block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); + int iterations = block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + + double lateral_deflection = 0.0; + for (int i = 1; i < final_disp.Size(); i += dim) { + lateral_deflection = std::max(lateral_deflection, std::abs(final_disp(i))); + } - SLIC_INFO_ROOT("Old vs new displacement diff: " << disp_diff); - SLIC_INFO_ROOT("Old vs new temperature diff: " << temp_diff); + SLIC_INFO_ROOT("Monolithic converged: " << converged << ", iterations: " << iterations); + SLIC_INFO_ROOT("Monolithic max lateral deflection: " << lateral_deflection); - // Both approaches run the same number of stagger iterations so results should be - // numerically identical (same tolerances, same solvers, same mesh). - EXPECT_LT(disp_diff, 1e-4); - EXPECT_LT(temp_diff, 1e-4); + EXPECT_TRUE(converged); + EXPECT_GT(lateral_deflection, 1e-5); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp deleted file mode 100644 index 3f8588fc2f..0000000000 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp +++ /dev/null @@ -1,313 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#include -#include "gtest/gtest.h" - -#include "smith/smith_config.hpp" -#include "smith/infrastructure/application_manager.hpp" -#include "smith/numerics/solver_config.hpp" -#include "smith/physics/state/state_manager.hpp" -#include "smith/physics/mesh.hpp" - -#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/system_solver.hpp" -#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" -#include "smith/differentiable_numerics/paraview_writer.hpp" -#include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" -#include "smith/differentiable_numerics/differentiable_test_utils.hpp" -#include "smith/differentiable_numerics/nonlinear_solve.hpp" -#include "smith/physics/functional_objective.hpp" -#include "gretl/wang_checkpoint_strategy.hpp" - -namespace smith { - -static constexpr int dim = 3; -static constexpr int displacement_order = 1; -static constexpr int temperature_order = 1; - -template -auto greenStrain(const tensor& grad_u) -{ - return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); -} - -struct GreenSaintVenantThermoelasticMaterial { - double density; - double E0; - double nu; - double C_v; - double alpha; - double theta_ref; - double kappa; - using State = Empty; - template - auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta, const T5& E_param) const - { - auto E = E0 + get<0>(E_param); - const auto K = E / (3.0 * (1.0 - 2.0 * nu)); - const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrain(grad_u); - const auto trEg = tr(Eg); - static constexpr auto I = Identity(); - const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; - auto F = grad_u + I; - const auto Piola = dot(F, S); - auto greenStrainRate = - 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; - const auto q0 = -kappa * grad_theta; - return smith::tuple{Piola, C_v, s0, q0}; - } - static constexpr int numParameters() { return 1; } -}; - -struct ThermoMechanicsMeshFixture : public testing::Test { - void SetUp() - { - datastore_ = std::make_unique(); - smith::StateManager::initialize(*datastore_, "solid"); - mesh_ = std::make_shared( - mfem::Mesh::MakeCartesian3D(24, 2, 2, mfem::Element::HEXAHEDRON, 1.2, 0.03, 0.03), "mesh", 0, 0); - mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); - mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); - } - std::unique_ptr datastore_; - std::shared_ptr mesh_; -}; - -TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionInfo) -{ - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - FieldType> youngs_modulus("youngs_modulus"); - auto system = buildThermoMechanicsSystem( - mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, {.prepend_name = "thermo"}, youngs_modulus); - - auto physics = makeDifferentiablePhysics(system, "thermo_physics"); - const auto& solid_dual_space = physics->dual("thermo_solid_force").space(); - const auto& solid_state_space = physics->state("thermo_displacement").space(); - const auto& thermal_dual_space = physics->dual("thermo_thermal_flux").space(); - const auto& thermal_state_space = physics->state("thermo_temperature").space(); - - EXPECT_EQ(physics->dualNames().size(), 2); - EXPECT_EQ(physics->dualNames()[0], "thermo_solid_force"); - EXPECT_EQ(physics->dualNames()[1], "thermo_thermal_flux"); - EXPECT_EQ(solid_dual_space.GetMesh(), solid_state_space.GetMesh()); - EXPECT_STREQ(solid_dual_space.FEColl()->Name(), solid_state_space.FEColl()->Name()); - EXPECT_EQ(solid_dual_space.GetVDim(), solid_state_space.GetVDim()); - EXPECT_EQ(solid_dual_space.TrueVSize(), solid_state_space.TrueVSize()); - EXPECT_EQ(thermal_dual_space.GetMesh(), thermal_state_space.GetMesh()); - EXPECT_STREQ(thermal_dual_space.FEColl()->Name(), thermal_state_space.FEColl()->Name()); - EXPECT_EQ(thermal_dual_space.GetVDim(), thermal_state_space.GetVDim()); - EXPECT_EQ(thermal_dual_space.TrueVSize(), thermal_state_space.TrueVSize()); -} - -TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) -{ - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - FieldType> youngs_modulus("youngs_modulus"); - auto system = buildThermoMechanicsSystem( - mesh_, std::make_shared(block_solver), QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, {.prepend_name = "thermo"}, youngs_modulus); - - GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - system->setMaterial(material, mesh_->entireBodyName()); - system->field_store->getParameterFields()[0].get()->setFromFieldFunction( - [=](smith::tensor) { return 100.0; }); - system->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - system->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - - system->addSolidTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { - auto traction = 0.0 * X; - traction[0] = -0.015; - return traction; - }); - - auto physics = makeDifferentiablePhysics(system, "thermo_physics"); - - // Run forward - double dt = 1.0; - for (int step = 0; step < 2; ++step) { - physics->advanceTimestep(dt); - } - - auto reactions = physics->getReactionStates(); - auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); - - gretl::set_as_objective(obj); - obj.data_store().back_prop(); - - auto param_sens = system->field_store->getParameterFields()[0].get_dual(); - EXPECT_TRUE(param_sens->Norml2() > 0.0); -} - -TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) -{ - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; - - auto run_problem = [&](const std::string& label, std::shared_ptr coupled_solver) { - GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - FieldType> youngs_modulus("youngs_modulus"); - auto system = buildThermoMechanicsSystem( - mesh_, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, - BackwardEulerFirstOrderTimeIntegrationRule{}, {}, youngs_modulus); - system->setMaterial(material, mesh_->entireBodyName()); - system->field_store->getParameterFields()[0].get()->setFromFieldFunction( - [=](smith::tensor) { return 100.0; }); - system->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - system->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - system->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); - system->addSolidTraction("right", [=](auto, auto X, auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -compressive_traction; - return traction; - }); - system->addSolidBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto... /*args*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - system->addHeatSource(mesh_->entireBodyName(), - [=](auto, auto, auto, auto, auto, auto, auto, auto) { return thermal_source; }); - - SLIC_INFO_ROOT("Starting " << label << " thermo-mechanics solve"); - - double dt = 1.0; - double time = 0.0; - auto shape_disp = system->field_store->getShapeDisp(); - auto states = system->field_store->getStateFields(); - auto params = system->field_store->getParameterFields(); - std::vector reactions; - for (size_t step = 0; step < 1; ++step) { - std::tie(states, reactions) = - makeAdvancer(system)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); - time += dt; - } - - return std::make_pair(mfem::Vector(*states[system->field_store->getFieldIndex("displacement_solve_state")].get()), - mfem::Vector(*states[system->field_store->getFieldIndex("temperature_solve_state")].get())); - }; - - smith::LinearSolverOptions monolithic_lin_opts{.linear_solver = smith::LinearSolver::GMRES, - .preconditioner = smith::Preconditioner::BlockDiagonal, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 100, - .print_level = 0}; - smith::LinearSolverOptions block_opt{.linear_solver = smith::LinearSolver::SuperLU}; - monolithic_lin_opts.sub_block_linear_solver_options.push_back(block_opt); - monolithic_lin_opts.sub_block_linear_solver_options.push_back(block_opt); - - smith::NonlinearSolverOptions monolithic_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 5, - .max_line_search_iterations = 6, - .print_level = 2}; - - auto monolithic_block_solver = buildNonlinearBlockSolver(monolithic_nonlin_opts, monolithic_lin_opts, *mesh_); - auto monolithic_result = run_problem("monolithic", std::make_shared(monolithic_block_solver)); - bool monolithic_converged = monolithic_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int monolithic_iterations = monolithic_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - - this->mesh_.reset(); - smith::StateManager::reset(); - this->SetUp(); - - auto staggered_coupled_solver = std::make_shared(10); - - smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 120, - .print_level = 0}; - smith::NonlinearSolverOptions mech_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::TrustRegion, - .relative_tol = 1e-6, - .absolute_tol = 1e-7, - .max_iterations = 25, - .print_level = 1}; - - smith::LinearSolverOptions therm_lin_opts{.linear_solver = smith::LinearSolver::GMRES, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 80, - .print_level = 0}; - smith::NonlinearSolverOptions therm_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, - .relative_tol = 1e-7, - .absolute_tol = 1e-7, - .max_iterations = 12, - .max_line_search_iterations = 6, - .print_level = 1}; - - auto solid_block_solver = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); - staggered_coupled_solver->addSubsystemSolver({0}, solid_block_solver); - staggered_coupled_solver->addSubsystemSolver({0, 1}, thermal_block_solver); - - auto staggered_result = run_problem("staggered", staggered_coupled_solver); - bool staggered_solid_converged = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int staggered_solid_iterations = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - bool staggered_thermal_converged = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int staggered_thermal_iterations = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - - double disp_diff = mfem::Vector(monolithic_result.first).Add(-1.0, staggered_result.first).Normlinf(); - double temp_diff = mfem::Vector(monolithic_result.second).Add(-1.0, staggered_result.second).Normlinf(); - double monolithic_lateral_deflection = 0.0; - for (int i = 1; i < monolithic_result.first.Size(); i += dim) { - monolithic_lateral_deflection = std::max(monolithic_lateral_deflection, std::abs(monolithic_result.first(i))); - } - double staggered_lateral_deflection = 0.0; - for (int i = 1; i < staggered_result.first.Size(); i += dim) { - staggered_lateral_deflection = std::max(staggered_lateral_deflection, std::abs(staggered_result.first(i))); - } - - SLIC_INFO_ROOT("Monolithic converged: " << monolithic_converged << ", iterations: " << monolithic_iterations); - SLIC_INFO_ROOT("Monolithic solver tolerances: rel = 1e-10, abs = 1e-10"); - SLIC_INFO_ROOT("Monolithic max lateral deflection: " << monolithic_lateral_deflection); - SLIC_INFO_ROOT("Staggered solid converged: " << staggered_solid_converged - << ", iterations: " << staggered_solid_iterations); - SLIC_INFO_ROOT("Staggered thermal converged: " << staggered_thermal_converged - << ", iterations: " << staggered_thermal_iterations); - SLIC_INFO_ROOT("Staggered solver tolerances: solid rel/abs = 1e-6/1e-10, thermal rel/abs = 1e-7/1e-7"); - SLIC_INFO_ROOT("Buckling displacement discrepancy: " << disp_diff); - SLIC_INFO_ROOT("Buckling temperature discrepancy: " << temp_diff); - SLIC_INFO_ROOT("Staggered max lateral deflection: " << staggered_lateral_deflection); - - EXPECT_TRUE(monolithic_converged); - EXPECT_TRUE(staggered_solid_converged); - EXPECT_TRUE(staggered_thermal_converged); - EXPECT_GE(monolithic_iterations, staggered_thermal_iterations); - EXPECT_GT(staggered_lateral_deflection, 1e-5); - EXPECT_GT(disp_diff, 1e-8); - EXPECT_GT(temp_diff, 1e-8); -} - -} // namespace smith - -int main(int argc, char* argv[]) -{ - ::testing::InitGoogleTest(&argc, argv); - smith::ApplicationManager applicationManager(argc, argv); - return RUN_ALL_TESTS(); -} diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp deleted file mode 100644 index 0d3003f025..0000000000 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ /dev/null @@ -1,453 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -/** - * @file thermo_mechanics_system.hpp - * @brief Defines the ThermoMechanicsSystem struct and its factory function - */ - -#pragma once - -#include "smith/differentiable_numerics/field_store.hpp" -#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" -#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" -#include "smith/differentiable_numerics/time_integration_rule.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" -#include "smith/differentiable_numerics/differentiable_physics.hpp" -#include "smith/physics/weak_form.hpp" -#include "smith/differentiable_numerics/system_base.hpp" - -namespace smith { - -/** - * @brief Container for a coupled thermo-mechanical system with configurable time integration. - * - * Displacement uses a 4-state second-order layout (displacement_solve_state, displacement, velocity, acceleration). - * Temperature uses a 2-state first-order layout (temperature_solve_state, temperature). - * Total: 6 state fields. - * - * @tparam dim Spatial dimension. - * @tparam disp_order Order of the displacement basis. - * @tparam temp_order Order of the temperature basis. - * @tparam DisplacementTimeRule Time integration rule type for displacement (must have num_states == 4). - * @tparam TemperatureTimeRule Time integration rule type for temperature (must have num_states == 2). - * @tparam parameter_space Finite element spaces for optional parameters. - */ -template -struct ThermoMechanicsSystem : public SystemBase { - using SystemBase::SystemBase; - - static_assert(DisplacementTimeRule::num_states == 4, - "ThermoMechanicsSystem requires a 4-state displacement time rule"); - static_assert(TemperatureTimeRule::num_states == 2, "ThermoMechanicsSystem requires a 2-state temperature time rule"); - - /// @brief using for SolidWeakFormType - using SolidWeakFormType = - TimeDiscretizedWeakForm, - Parameters, H1, H1, - H1, H1, H1, parameter_space...>>; - - /// @brief using for ThermalWeakFormType - using ThermalWeakFormType = - TimeDiscretizedWeakForm, - Parameters, H1, H1, H1, - H1, H1, parameter_space...>>; - - // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, temp, temp_old, params... - /// @brief using for CycleZeroWeakFormType - using CycleZeroWeakFormType = - TimeDiscretizedWeakForm, - Parameters, H1, H1, H1, - H1, parameter_space...>>; - - std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. - std::shared_ptr thermal_weak_form; ///< Thermal weak form. - std::shared_ptr cycle_zero_weak_form; ///< Typed cycle zero weak form. - std::shared_ptr cycle_zero_system; ///< Cycle-zero system. - std::shared_ptr disp_bc; ///< Displacement boundary conditions. - std::shared_ptr temperature_bc; ///< Temperature boundary conditions. - std::shared_ptr disp_time_rule; ///< Time integration for displacement. - std::shared_ptr temperature_time_rule; ///< Time integration for temperature. - - /** - * @brief Set the material model for a domain, defining integrals for solid and thermal weak forms. - * - * The material is called as material(dt, state, grad_u, grad_v, T, grad_T, params...) and - * must expose a `density` member for the cycle-zero acceleration solve. - * - * @tparam MaterialType The material model type. - * @param material The material model instance. - * @param domain_name The name of the domain to apply the material to. - */ - template - void setMaterial(const MaterialType& material, const std::string& domain_name) - { - auto captured_disp_rule = disp_time_rule; - auto captured_temp_rule = temperature_time_rule; - - solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, - auto temperature_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), - get(T), get(T), params...); - return smith::tuple{get(a_current) * material.density, pk}; - }); - - thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, - auto disp_old, auto v_old, auto a_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), - get(T_current), get(T_current), params...); - auto dT_dt = get(T_dot); - return smith::tuple{C_v * dT_dt - s0, -q0}; - }); - - // Cycle-zero: u and v are given, solve for a; temperature at initial condition - // Only register if the displacement time rule requires an initial acceleration solve. - if (cycle_zero_weak_form) { - cycle_zero_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto v, auto a, - auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), - get(T), params...); - return smith::tuple{get(a) * material.density, pk}; - }); - } - } - - /** - * @brief Add a body force to the solid mechanics part of the system (with DependsOn). - */ - template - void addSolidBodyForce(DependsOn depends_on, const std::string& domain_name, - BodyForceType force_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_temp_rule = temperature_time_rule; - - solid_weak_form->addBodySource( - depends_on, domain_name, - [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto temperature, auto temperature_old, - auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [current_T, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - return force_function(t_info.time(), X, u_current, v_current, a_current, current_T, T_dot, params...); - }); - } - - /** - * @brief Add a body force to the solid mechanics part of the system. - */ - template - void addSolidBodyForce(const std::string& domain_name, BodyForceType force_function) - { - addSolidBodyForceAllParams(domain_name, force_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); - } - - /** - * @brief Add a surface traction to the solid mechanics part (with DependsOn). - */ - template - void addSolidTraction(DependsOn depends_on, const std::string& domain_name, - SurfaceFluxType flux_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_temp_rule = temperature_time_rule; - - solid_weak_form->addBoundaryFlux( - depends_on, domain_name, - [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto temperature, - auto temperature_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [current_T, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - return flux_function(t_info.time(), X, n, u_current, v_current, a_current, current_T, T_dot, params...); - }); - } - - /** - * @brief Add a surface traction to the solid mechanics part. - */ - template - void addSolidTraction(const std::string& domain_name, SurfaceFluxType flux_function) - { - addSolidTractionAllParams(domain_name, flux_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}, - std::make_index_sequence<5 + sizeof...(parameter_space)>{}); - } - - /** - * @brief Add a body heat source to the thermal part (with DependsOn). - */ - template - void addHeatSource(DependsOn depends_on, const std::string& domain_name, - BodySourceType source_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_temp_rule = temperature_time_rule; - - thermal_weak_form->addBodySource( - depends_on, domain_name, - [=](auto t_info, auto X, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old, auto... params) { - auto [current_u, v_current, a_current] = - captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - return source_function(t_info.time(), X, current_u, v_current, a_current, T_current, T_dot, params...); - }); - } - - /** - * @brief Add a body heat source to the thermal part. - */ - template - void addHeatSource(const std::string& domain_name, BodySourceType source_function) - { - addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); - } - - /** - * @brief Add a boundary heat flux to the thermal part (with DependsOn). - */ - template - void addHeatFlux(DependsOn depends_on, const std::string& domain_name, - SurfaceFluxType flux_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_temp_rule = temperature_time_rule; - - thermal_weak_form->addBoundaryFlux(depends_on, domain_name, - [=](auto t_info, auto X, auto n, auto T, auto T_old, auto disp, auto disp_old, - auto v_old, auto a_old, auto... params) { - auto [current_u, v_current, a_current] = - captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - return -flux_function(t_info.time(), X, n, current_u, v_current, a_current, - T_current, T_dot, params...); - }); - } - - /** - * @brief Add a boundary heat flux to the thermal part. - */ - template - void addHeatFlux(const std::string& domain_name, SurfaceFluxType flux_function) - { - addHeatFluxAllParams(domain_name, flux_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); - } - - /** - * @brief Add a pressure boundary condition (follower force) to the solid part (with DependsOn). - */ - template - void addPressure(DependsOn depends_on, const std::string& domain_name, - PressureType pressure_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_temp_rule = temperature_time_rule; - - solid_weak_form->addBoundaryIntegral( - depends_on, domain_name, - [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto temperature, auto temperature_old, - auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - - auto x_current = X + u_current; - auto n_deformed = cross(get(x_current)); - auto n_shape_norm = norm(cross(get(X))); - - auto pressure = pressure_function(t_info.time(), get(X), u_current, v_current, a_current, T_current, - T_dot, get(params)...); - - return pressure * n_deformed * (1.0 / n_shape_norm); - }); - } - - /** - * @brief Add a pressure boundary condition (follower force) to the solid part. - */ - template - void addPressure(const std::string& domain_name, PressureType pressure_function) - { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<6 + sizeof...(parameter_space)>{}); - } - - private: - template - void addSolidBodyForceAllParams(const std::string& domain_name, BodyForceType force_function, - std::index_sequence) - { - addSolidBodyForce(DependsOn(Is)...>{}, domain_name, force_function); - } - - template - void addSolidTractionAllParams(const std::string& domain_name, SurfaceFluxType flux_function, - std::index_sequence, std::index_sequence) - { - addSolidTraction(DependsOn(MainIs)...>{}, domain_name, flux_function); - - if (cycle_zero_weak_form) { - auto captured_temp_rule = temperature_time_rule; - cycle_zero_weak_form->addBoundaryFlux( - DependsOn(CycleZeroIs)...>{}, domain_name, - [=](auto t_info, auto X, auto n, auto u, auto v, auto a, auto temperature, auto temperature_old, - auto... params) { - auto [current_T, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - return flux_function(t_info.time(), X, n, u, v, a, current_T, T_dot, params...); - }); - } - } - - template - void addPressureAllParams(const std::string& domain_name, PressureType pressure_function, std::index_sequence) - { - addPressure(DependsOn(Is)...>{}, domain_name, pressure_function); - } - - template - void addHeatSourceAllParams(const std::string& domain_name, BodySourceType source_function, - std::index_sequence) - { - addHeatSource(DependsOn(Is)...>{}, domain_name, source_function); - } - - template - void addHeatFluxAllParams(const std::string& domain_name, SurfaceFluxType flux_function, std::index_sequence) - { - addHeatFlux(DependsOn(Is)...>{}, domain_name, flux_function); - } -}; - -template -struct ThermoMechanicsOptions { - std::string prepend_name{}; - std::shared_ptr cycle_zero_solver{}; -}; - -/** - * @brief Factory function to build a thermo-mechanical system. - * @param mesh The mesh. - * @param solver The coupled system solver. - * @param disp_rule The displacement time integration rule. - * @param temp_rule The temperature time integration rule. - * @param options Options defining fields prepends or sub-solvers. - * @param parameter_types Optional parameter field descriptors. - */ -template -std::shared_ptr< - ThermoMechanicsSystem> -buildThermoMechanicsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, - TemperatureTimeRule temp_rule, - ThermoMechanicsOptions - options, - FieldType... parameter_types) -{ - auto field_store = std::make_shared(mesh, 100, options.prepend_name); - - FieldType> shape_disp_type("shape_displacement"); - field_store->addShapeDisp(shape_disp_type); - - // Displacement fields (4-state second-order) - auto disp_time_rule_ptr = std::make_shared(disp_rule); - FieldType> disp_type("displacement_solve_state"); - auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); - auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); - auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); - auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); - - // Temperature fields (2-state first-order) - auto temperature_time_rule_ptr = std::make_shared(temp_rule); - FieldType> temperature_type("temperature_solve_state"); - auto temperature_bc = field_store->addIndependent(temperature_type, temperature_time_rule_ptr); - auto temperature_old_type = - field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); - - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - - using SystemType = - ThermoMechanicsSystem; - - // Solid mechanics weak form (u, u_old, v_old, a_old, temp, temp_old, params...) - std::string solid_force_name = field_store->prefix("solid_force"); - auto solid_weak_form = std::make_shared( - solid_force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(solid_force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, temperature_type, temperature_old_type, parameter_types...)); - - // Thermal weak form (temp, temp_old, u, u_old, v_old, a_old, params...) - std::string thermal_flux_name = field_store->prefix("thermal_flux"); - auto thermal_weak_form = std::make_shared( - thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), - field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, - disp_type, disp_old_type, velo_old_type, accel_old_type, parameter_types...)); - - auto sys = std::make_shared(field_store, solver, - std::vector>{solid_weak_form, thermal_weak_form}); - sys->disp_bc = disp_bc; - sys->temperature_bc = temperature_bc; - sys->disp_time_rule = disp_time_rule_ptr; - sys->temperature_time_rule = temperature_time_rule_ptr; - sys->solid_weak_form = solid_weak_form; - sys->thermal_weak_form = thermal_weak_form; - - if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - std::string cycle_zero_name = field_store->prefix("solid_reaction"); - // At cycle 0, u and v are given; solve for a. Make acceleration (arg 2) the Jacobian - // variable. Displacement and temperature are fixed inputs here. - auto accel_as_unknown = accel_old_type; - accel_as_unknown.is_unknown = true; - FieldType> disp_cz_input(disp_type.name); - FieldType> temp_cz_input(temperature_type.name); - sys->cycle_zero_weak_form = std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, - temp_cz_input, temperature_old_type, parameter_types...)); - field_store->markWeakFormInternal(cycle_zero_name); - // Share displacement BCs with acceleration (constrained displacement DOFs = zero acceleration). - field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - - std::shared_ptr cz_solver; - if (options.cycle_zero_solver) { - cz_solver = options.cycle_zero_solver; - } else { - NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; - LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - cz_solver = std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *mesh)); - } - - sys->cycle_zero_system = makeSubSystem(field_store, cz_solver, {sys->cycle_zero_weak_form}); - } - - return sys; -} - -} // namespace smith From 5a53f4b2fe0353e20c3008525021002a5395dc0e Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 15 Apr 2026 22:53:03 -0600 Subject: [PATCH 15/67] Refactor system interface to make it easy to combine systems. --- .../differentiable_numerics/CMakeLists.txt | 2 +- .../combined_system.hpp | 89 ++++--- ...{coupling_spec.hpp => coupling_params.hpp} | 36 ++- .../differentiable_physics.cpp | 5 + .../differentiable_physics.hpp | 9 +- .../differentiable_numerics/field_store.cpp | 23 +- .../differentiable_numerics/field_store.hpp | 5 + .../multiphysics_time_integrator.cpp | 5 +- .../multiphysics_time_integrator.hpp | 38 +-- .../solid_mechanics_system.hpp | 205 ++++++++------- ...id_mechanics_with_internal_vars_system.hpp | 234 ++++++++---------- .../tests/CMakeLists.txt | 2 +- ....cpp => test_coupled_thermo_mechanics.cpp} | 231 ++++++++--------- .../tests/test_solid_dynamics.cpp | 28 ++- .../test_solid_static_with_internal_vars.cpp | 41 ++- .../tests/test_thermal_static.cpp | 22 +- .../thermal_system.hpp | 114 ++++----- 17 files changed, 546 insertions(+), 543 deletions(-) rename src/smith/differentiable_numerics/{coupling_spec.hpp => coupling_params.hpp} (63%) rename src/smith/differentiable_numerics/tests/{test_combined_thermo_mechanics.cpp => test_coupled_thermo_mechanics.cpp} (68%) diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index b325b58eeb..ac2e547129 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -41,7 +41,7 @@ set(differentiable_numerics_headers solid_mechanics_system.hpp solid_mechanics_with_internal_vars_system.hpp thermal_system.hpp - coupling_spec.hpp + coupling_params.hpp combined_system.hpp system_base.hpp differentiable_test_utils.hpp diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index b4a2398288..ae4437ae45 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -16,18 +16,18 @@ * @code * auto field_store = std::make_shared(mesh, 100, "coupled"); * - * auto solid_info = registerSolidMechanicsFields(*field_store, disp_rule, params...); - * auto thermal_info = registerThermalFields(*field_store, temp_rule); - * - * CouplingSpec solid_coupling{FieldType>("temperature"), + * CouplingParams solid_coupling{FieldType>("temperature"), * FieldType>("temperature_old")}; - * CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), + * CouplingParams thermal_coupling{FieldType>("displacement_solve_state"), * FieldType>("displacement")}; * - * auto solid = buildSolidMechanicsSystemFromStore( - * solid_info, solid_solver, solid_opts, solid_coupling, params...); - * auto thermal = buildThermalSystemFromStore( - * thermal_info, thermal_solver, thermal_opts, thermal_coupling); + * auto solid_res = buildSolidMechanicsSystem( + * field_store, disp_rule, solid_coupling, solid_solver, solid_opts, params...); + * auto solid = solid_res.system; + * + * auto thermal_res = buildThermalSystem( + * field_store, temp_rule, thermal_coupling, thermal_solver, thermal_opts); + * auto thermal = thermal_res.system; * * auto coupled = combineSystems(solid, thermal); * coupled->setMaterial(thermo_mech_material, domain); // tight coupling @@ -63,7 +63,6 @@ namespace smith { */ struct CombinedSystem : public SystemBase { std::vector> subsystems; - std::vector> cycle_zero_systems; ///< Cycle-zero systems from sub-systems, in order. int max_stagger_iters = 10; double stagger_tolerance = 1e-8; @@ -103,26 +102,26 @@ struct CombinedSystem : public SystemBase { * - All sub-systems share the same FieldStore (built via registerXxxFields + buildXxxFromStore). * - Sub-system weak_forms are already populated (registerXxx was called before buildXxx). * - * The returned CombinedSystem: - * - Holds shared_ptrs to each sub-system (accessible as combined->subsystems[i]). - * - Its weak_forms is the concatenation of sub-system weak forms in argument order. - * - Its field_store is the shared FieldStore from the first sub-system. - * - Its solver member is null (CombinedSystem::solve() drives sub-system solvers directly). - * - Its cycle_zero_systems is populated with each sub-system's cycle_zero_system (if non-null). + * The returned pair contains: + * - first: The combined main system (staggered). + * - second: The combined cycle-zero system (staggered, max_stagger_iters=1), or nullptr if none. * * @param subs Two or more sub-systems that share a FieldStore. */ template -std::shared_ptr combineSystems(std::shared_ptr... subs) +std::pair, std::shared_ptr> combineSystems( + std::shared_ptr... subs) { static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); + auto first_sub = std::get<0>(std::forward_as_tuple(subs...)); + auto field_store = first_sub->field_store; + auto combined = std::make_shared(); + combined->field_store = field_store; - // All sub-systems must share the same FieldStore — use the first one. - combined->field_store = std::get<0>(std::forward_as_tuple(subs...))->field_store; + std::vector> cycle_zero_subs; - // Concatenate weak_forms, collect subsystems, and gather any cycle-zero systems. ( [&](auto& sub) { combined->subsystems.push_back(sub); @@ -131,15 +130,28 @@ std::shared_ptr combineSystems(std::shared_ptr... su } if constexpr (requires { sub->cycle_zero_system; }) { if (sub->cycle_zero_system) { - combined->cycle_zero_systems.push_back(sub->cycle_zero_system); + cycle_zero_subs.push_back(sub->cycle_zero_system); } } }(subs), ...); - return combined; -} + std::shared_ptr cycle_zero_combined = nullptr; + if (!cycle_zero_subs.empty()) { + auto cz = std::make_shared(); + cz->field_store = field_store; + cz->max_stagger_iters = 1; // Cycle-zero solves are one-shot + for (auto& sub : cycle_zero_subs) { + cz->subsystems.push_back(sub); + for (auto& wf : sub->weak_forms) { + cz->weak_forms.push_back(wf); + } + } + cycle_zero_combined = cz; + } + return {combined, cycle_zero_combined}; +} /** * @brief A generic wrapper that combines multiple sub-systems into a single monolithic block system. @@ -148,8 +160,6 @@ std::shared_ptr combineSystems(std::shared_ptr... su * concatenates all weak forms and solves them simultaneously using a single global SystemSolver. */ struct MonolithicCombinedSystem : public SystemBase { - std::shared_ptr cycle_zero_system; - using SystemBase::SystemBase; }; @@ -160,16 +170,19 @@ struct MonolithicCombinedSystem : public SystemBase { * - All sub-systems share the same FieldStore. * - Sub-system weak_forms are already populated. * + * The returned pair contains: + * - first: The combined main system (monolithic). + * - second: The combined cycle-zero system (monolithic), or nullptr if none. + * * @param solver The monolithic SystemSolver that will solve the combined block system, * including the aggregated cycle-zero system if any sub-systems have one. * @param subs Two or more sub-systems that share a FieldStore. */ template -std::shared_ptr combineSystemsMonolithic( - std::shared_ptr solver, - std::shared_ptr... subs) +std::pair, std::shared_ptr> combineSystems( + std::shared_ptr solver, std::shared_ptr... subs) { - static_assert(sizeof...(subs) >= 2, "combineSystemsMonolithic requires at least two sub-systems"); + static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); auto field_store = std::get<0>(std::forward_as_tuple(subs...))->field_store; @@ -192,11 +205,25 @@ std::shared_ptr combineSystemsMonolithic( ...); auto combined = std::make_shared(field_store, solver, wfs); + std::shared_ptr cycle_zero_combined = nullptr; if (!cycle_zero_wfs.empty()) { - combined->cycle_zero_system = std::make_shared(field_store, solver, cycle_zero_wfs); + cycle_zero_combined = std::make_shared(field_store, solver, cycle_zero_wfs); } - return combined; + return {combined, cycle_zero_combined}; +} + +/** + * @brief Concatenate multiple vectors of systems into a single vector. + * + * Primarily used to gather end-step systems (like stress output) from multiple physics systems. + */ +template +std::vector> mergeSystems(Vectors&&... vectors) +{ + std::vector> result; + (result.insert(result.end(), vectors.begin(), vectors.end()), ...); + return result; } } // namespace smith diff --git a/src/smith/differentiable_numerics/coupling_spec.hpp b/src/smith/differentiable_numerics/coupling_params.hpp similarity index 63% rename from src/smith/differentiable_numerics/coupling_spec.hpp rename to src/smith/differentiable_numerics/coupling_params.hpp index 24478ba0a8..75c841baf5 100644 --- a/src/smith/differentiable_numerics/coupling_spec.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -5,13 +5,13 @@ // SPDX-License-Identifier: (BSD-3-Clause) /** - * @file coupling_spec.hpp - * @brief CouplingSpec type and helpers for injecting coupled-physics fields into weak form parameter packs. + * @file coupling_params.hpp + * @brief CouplingParams type and helpers for injecting coupled-physics fields into weak form parameter packs. * * Convention: coupling fields occupy the *leading* positions of the "tail" parameter pack in every - * weak form constructed with a non-empty CouplingSpec. Concretely, after the time-rule state fields + * weak form constructed with a non-empty CouplingParams. Concretely, after the time-rule state fields * (e.g. u, u_old, v_old, a_old for solid) come the coupling fields in the order declared in - * CouplingSpec::fields, and only then come the user-supplied parameter_space fields. + * CouplingParams::fields, and only then come the user-supplied parameter_space fields. * * This ordering must be respected in every setMaterial / addBodyForce / addTraction / addPressure * closure: the `auto...` tail pack is partitioned as (coupling_fields..., user_params...). @@ -31,27 +31,37 @@ namespace smith { * * Usage: * @code - * CouplingSpec solid_coupling{FieldType>("temperature"), + * CouplingParams solid_coupling{FieldType>("temperature"), * FieldType>("temperature_old")}; * @endcode * - * The default CouplingSpec<> (empty) leaves weak form parameter packs unchanged. + * The default CouplingParams<> (empty) leaves weak form parameter packs unchanged. */ template -struct CouplingSpec { +struct CouplingParams { static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); std::tuple...> fields; - CouplingSpec(FieldType... fs) : fields(std::move(fs)...) {} + CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} }; -/// Deduction guide: CouplingSpec{FieldType("a"), FieldType("b")} -> CouplingSpec +/// Deduction guide: CouplingParams{FieldType("a"), FieldType("b")} -> CouplingParams template -CouplingSpec(FieldType...) -> CouplingSpec; +CouplingParams(FieldType...) -> CouplingParams; namespace detail { +/// Type trait: true if T is a CouplingParams<...> specialization. +template +struct is_coupling_params_impl : std::false_type {}; + +template +struct is_coupling_params_impl> : std::true_type {}; + +template +inline constexpr bool is_coupling_params_v = is_coupling_params_impl::value; + /** - * @brief Produce TimeRuleParams from a CouplingSpec. + * @brief Produce TimeRuleParams from a CouplingParams. * * Inserts coupling spaces immediately after the num_states copies of Space (the time-rule * state fields), and before the user-supplied Tail types (parameter_space...). @@ -60,7 +70,7 @@ template struct TimeRuleParamsWithCoupling; template -struct TimeRuleParamsWithCoupling, Tail...> { +struct TimeRuleParamsWithCoupling, Tail...> { using type = TimeRuleParams; }; @@ -74,7 +84,7 @@ template struct AppendCouplingToParams; template -struct AppendCouplingToParams, Parameters, Tail...> { +struct AppendCouplingToParams, Parameters, Tail...> { using type = Parameters; }; diff --git a/src/smith/differentiable_numerics/differentiable_physics.cpp b/src/smith/differentiable_numerics/differentiable_physics.cpp index 85326fff9f..092c14af52 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.cpp +++ b/src/smith/differentiable_numerics/differentiable_physics.cpp @@ -119,6 +119,11 @@ const FiniteElementState& DifferentiablePhysics::state([[maybe_unused]] const st const FiniteElementDual& DifferentiablePhysics::dual(const std::string& reaction_name) const { + if (reaction_name_to_reaction_index_.find(reaction_name) == reaction_name_to_reaction_index_.end()) { + std::cout << "\n[DEBUG] Available reactions (size " << reaction_names_.size() << "): "; + for (auto& n : reaction_names_) std::cout << n << " "; + std::cout << std::endl; + } SLIC_ERROR_IF(reaction_name_to_reaction_index_.find(reaction_name) == reaction_name_to_reaction_index_.end(), axom::fmt::format("Could not find reaction named {0} in mesh with tag \"{1}\" to get", reaction_name, mesh_->tag())); diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index e079ee9f2d..20689800bb 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -210,10 +210,13 @@ std::unique_ptr makeDifferentiablePhysics(std::shared_ptr } template -std::unique_ptr makeDifferentiablePhysics(std::shared_ptr system, - const std::string& physics_name) +std::unique_ptr makeDifferentiablePhysics( + std::shared_ptr system, const std::string& physics_name, + std::shared_ptr cycle_zero_system = nullptr, + std::vector> post_solve_systems = {}) { - return makeDifferentiablePhysics(system, makeAdvancer(system), physics_name); + return makeDifferentiablePhysics( + system, makeAdvancer(system, std::move(cycle_zero_system), std::move(post_solve_systems)), physics_name); } } // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index b2b68d29a5..45ba4a4332 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -224,10 +224,9 @@ const std::vector& FieldStore::getAllFields() const { return states_ std::vector FieldStore::getStates(const std::string& weak_form_name) const { // Validate that weak form is registered - SLIC_ERROR_ROOT_IF( - weak_form_name_to_field_names_.count(weak_form_name) == 0, - axom::fmt::format("Weak form '{}' not found in FieldStore. Did you forget to call addResireaction()?", - weak_form_name)); + SLIC_ERROR_ROOT_IF(weak_form_name_to_field_names_.count(weak_form_name) == 0, + axom::fmt::format("Weak form '{}' not found in FieldStore. Did you forget to call addReaction()?", + weak_form_name)); auto field_names = weak_form_name_to_field_names_.at(weak_form_name); std::vector fields_for_residual; @@ -251,10 +250,9 @@ std::vector FieldStore::getStatesFromVectors(const std::string& weak const std::vector& param_fields) const { // Validate that weak form is registered - SLIC_ERROR_ROOT_IF( - weak_form_name_to_field_names_.count(weak_form_name) == 0, - axom::fmt::format("Weak form '{}' not found in FieldStore. Did you forget to call addResireaction()?", - weak_form_name)); + SLIC_ERROR_ROOT_IF(weak_form_name_to_field_names_.count(weak_form_name) == 0, + axom::fmt::format("Weak form '{}' not found in FieldStore. Did you forget to call addReaction()?", + weak_form_name)); auto field_names = weak_form_name_to_field_names_.at(weak_form_name); std::vector fields_for_residual; @@ -284,6 +282,15 @@ std::vector FieldStore::getStatesFromVectors(const std::string& weak const std::shared_ptr& FieldStore::getMesh() const { return mesh_; } +std::shared_ptr FieldStore::getBoundaryConditions(const std::string& field_name) const +{ + auto it = boundary_conditions_.find(field_name); + if (it != boundary_conditions_.end()) { + return it->second; + } + return nullptr; +} + const std::shared_ptr& FieldStore::graph() const { return graph_; } const std::vector, FieldStore::TimeIntegrationMapping>>& diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index ebfe415744..7092f21710 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -447,6 +447,11 @@ struct FieldStore { const std::shared_ptr& getMesh() const; + /** + * @brief Get the boundary conditions for a given field name. + */ + std::shared_ptr getBoundaryConditions(const std::string& field_name) const; + /** * @brief Get the associated data store graph. * @return const std::shared_ptr& The graph. diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index 8471ac5213..f5908422a5 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -17,8 +17,9 @@ namespace smith { MultiphysicsTimeIntegrator::MultiphysicsTimeIntegrator(std::shared_ptr system, - std::shared_ptr cycle_zero_system) - : system_(system), cycle_zero_system_(cycle_zero_system) + std::shared_ptr cycle_zero_system, + std::vector> post_solve_systems) + : system_(system), cycle_zero_system_(cycle_zero_system), post_solve_systems_(std::move(post_solve_systems)) { } diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 653b7dcce2..4350312d99 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -29,7 +29,8 @@ class BoundaryConditionManager; class MultiphysicsTimeIntegrator : public StateAdvancer { public: MultiphysicsTimeIntegrator(std::shared_ptr system, - std::shared_ptr cycle_zero_system = nullptr); + std::shared_ptr cycle_zero_system = nullptr, + std::vector> post_solve_systems = {}); /// @brief Register a system to be solved after the main solve and reaction computation. void addPostSolveSystem(std::shared_ptr system); @@ -52,36 +53,13 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { std::vector> post_solve_systems_; }; -inline std::shared_ptr makeAdvancer(std::shared_ptr system, - std::shared_ptr cycle_zero_system = nullptr) +inline std::shared_ptr makeAdvancer( + std::shared_ptr system, + std::shared_ptr cycle_zero_system = nullptr, + std::vector> post_solve_systems = {}) { - return std::make_shared(std::move(system), std::move(cycle_zero_system)); -} - -template -std::shared_ptr makeAdvancer(std::shared_ptr system) -{ - if constexpr (requires { system->cycle_zero_systems; }) { - // CombinedSystem: run each sub-system's cycle-zero solve in sequence (one pass, no stagger). - std::shared_ptr cz = nullptr; - if (!system->cycle_zero_systems.empty()) { - auto cz_combined = std::make_shared(); - cz_combined->field_store = system->field_store; - cz_combined->max_stagger_iters = 1; - for (auto& czs : system->cycle_zero_systems) { - cz_combined->subsystems.push_back(czs); - for (auto& wf : czs->weak_forms) { - cz_combined->weak_forms.push_back(wf); - } - } - cz = cz_combined; - } - return makeAdvancer(std::static_pointer_cast(system), cz); - } else if constexpr (requires { system->cycle_zero_system; }) { - return makeAdvancer(std::static_pointer_cast(system), system->cycle_zero_system); - } else { - return makeAdvancer(std::static_pointer_cast(system), nullptr); - } + return std::make_shared(std::move(system), std::move(cycle_zero_system), + std::move(post_solve_systems)); } } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 44a081f767..46cc4e64c6 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -21,7 +21,7 @@ #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" -#include "smith/differentiable_numerics/coupling_spec.hpp" +#include "smith/differentiable_numerics/coupling_params.hpp" namespace smith { @@ -35,13 +35,13 @@ namespace smith { * @tparam dim Spatial dimension. * @tparam order Polynomial order for displacement field. * @tparam DisplacementTimeRule Time integration rule type (must have num_states == 4). - * @tparam Coupling CouplingSpec listing fields borrowed from other physics (default: none). + * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). * Coupling fields occupy leading positions in the tail after the 4 time-rule state fields, * before user parameter_space fields. * @tparam parameter_space Parameter spaces for material properties. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>, typename... parameter_space> struct SolidMechanicsSystem : public SystemBase { using SystemBase::SystemBase; @@ -307,38 +307,23 @@ struct SolidMechanicsSystem : public SystemBase { } }; -template struct SolidMechanicsOptions { - std::string prepend_name{}; - std::shared_ptr shared_field_store{}; ///< Shared store for coupled systems; nullptr = allocate own. - std::shared_ptr cycle_zero_solver{}; + bool cycle_zero_solve = false; bool enable_stress_output = false; - std::shared_ptr stress_output_solver{}; -}; - -/// @brief Returned by registerSolidMechanicsFields; holds the FieldType tokens needed by buildSolidMechanicsSystemFromStore. -template -struct SolidMechanicsFieldInfo { - std::shared_ptr field_store; - FieldType> disp_type; - FieldType> disp_old_type; - FieldType> velo_old_type; - FieldType> accel_old_type; - std::tuple...> parameter_types; - std::shared_ptr disp_bc; - std::shared_ptr disp_time_rule_ptr; }; /** * @brief Register all solid mechanics fields into a FieldStore. * - * Phase 1 of the two-phase factory. Safe to call before other physics have registered their - * fields; weak form construction is deferred to buildSolidMechanicsSystemFromStore. - * When composing coupled systems, call all registerXxxFields functions before any buildXxxFromStore. + * Phase 1 of the two-phase initialization. Pass an instance of the desired time integration rule + * so its type is deduced; only `` need be specified explicitly. + * + * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). */ template -SolidMechanicsFieldInfo registerSolidMechanicsFields( - std::shared_ptr field_store, DisplacementTimeRule disp_time_rule, +auto registerSolidMechanicsFields( + std::shared_ptr field_store, + DisplacementTimeRule /*rule*/, FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); @@ -346,13 +331,13 @@ SolidMechanicsFieldInfo re field_store->addShapeDisp(shape_disp_type); } - auto disp_time_rule_ptr = std::make_shared(disp_time_rule); + auto disp_time_rule_ptr = std::make_shared(); FieldType> disp_type("displacement_solve_state"); - auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); + field_store->addIndependent(disp_type, disp_time_rule_ptr); - auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); - auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); - auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; @@ -360,38 +345,53 @@ SolidMechanicsFieldInfo re }; (prefix_param(parameter_types), ...); - return {field_store, disp_type, disp_old_type, velo_old_type, accel_old_type, - std::make_tuple(parameter_types...), disp_bc, disp_time_rule_ptr}; + return CouplingParams{ + FieldType>(field_store->prefix("displacement_solve_state")), + FieldType>(field_store->prefix("displacement")), + FieldType>(field_store->prefix("velocity")), + FieldType>(field_store->prefix("acceleration")), + parameter_types... + }; } /** - * @brief Build a SolidMechanicsSystem from an already-populated FieldStore. + * @brief Build a SolidMechanicsSystem with coupling, assuming fields are already registered. * - * Phase 2 of the two-phase factory. Constructs all weak forms using fields already registered - * in the store (including any coupling fields from other physics). + * Phase 2 of the two-phase initialization. Pass the same rule instance used in + * registerSolidMechanicsFields so the type is deduced; only `` need be specified. * - * @tparam Coupling CouplingSpec listing fields borrowed from other physics (leading tail positions). + * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. + * `cycle_zero_system` is nullptr unless the rule requires an initial acceleration solve. + * `end_step_systems` contains the stress output system when `enable_stress_output` is set. */ template -std::shared_ptr> -buildSolidMechanicsSystemFromStore( - SolidMechanicsFieldInfo info, + requires detail::is_coupling_params_v +auto buildSolidMechanicsSystem( + std::shared_ptr field_store, + DisplacementTimeRule /*rule*/, + const Coupling& coupling, std::shared_ptr solver, - const SolidMechanicsOptions& options, - const Coupling& coupling) + const SolidMechanicsOptions& options, + FieldType... parameter_types) { - auto& field_store = info.field_store; - auto& disp_type = info.disp_type; - auto& disp_old_type = info.disp_old_type; - auto& velo_old_type = info.velo_old_type; - auto& accel_old_type = info.accel_old_type; - auto parameter_types = info.parameter_types; - auto& disp_bc = info.disp_bc; - auto& disp_time_rule_ptr = info.disp_time_rule_ptr; + auto disp_time_rule_ptr = std::make_shared(); + + FieldType> shape_disp_type(field_store->prefix("shape_displacement")); + FieldType> disp_type(field_store->prefix("displacement_solve_state"), true); + FieldType> disp_old_type(field_store->prefix("displacement")); + FieldType> velo_old_type(field_store->prefix("velocity")); + FieldType> accel_old_type(field_store->prefix("acceleration")); + + auto disp_bc = field_store->getBoundaryConditions(disp_type.name); + + auto prefix_param = [&](auto& pt) { + pt.name = field_store->prefix("param_" + pt.name); + }; + (prefix_param(parameter_types), ...); + auto parameter_types_tuple = std::make_tuple(parameter_types...); using SystemType = SolidMechanicsSystem; - // Create solid mechanics weak form: (u, u_old, v_old, a_old, coupling_fields..., params...) std::string force_name = field_store->prefix("reactions"); auto solid_weak_form = std::apply( [&](auto&... params) { @@ -404,13 +404,16 @@ buildSolidMechanicsSystemFromStore( }, coupling.fields); }, - parameter_types); + parameter_types_tuple); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); sys->disp_bc = disp_bc; sys->disp_time_rule = disp_time_rule_ptr; sys->solid_weak_form = solid_weak_form; + std::shared_ptr cycle_zero_system; + std::vector> end_step_systems; + if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("solid_reaction"); auto accel_as_unknown = accel_old_type; @@ -428,29 +431,26 @@ buildSolidMechanicsSystemFromStore( }, coupling.fields); }, - parameter_types); + parameter_types_tuple); field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - std::shared_ptr cycle_zero_solver; - if (options.cycle_zero_solver) { - cycle_zero_solver = options.cycle_zero_solver; - } else { - NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; - LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - cycle_zero_solver = std::make_shared( - buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); - } + NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + auto cycle_zero_solver = std::make_shared( + buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); + sys->cycle_zero_system = makeSubSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); + cycle_zero_system = sys->cycle_zero_system; } if (options.enable_stress_output) { @@ -472,51 +472,44 @@ buildSolidMechanicsSystemFromStore( }, coupling.fields); }, - parameter_types); - - std::shared_ptr stress_solver; - if (options.stress_output_solver) { - stress_solver = options.stress_output_solver; - } else { - NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; - LinearSolverOptions stress_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - stress_solver = std::make_shared( - buildNonlinearBlockSolver(stress_nonlin, stress_lin, *field_store->getMesh())); - } + parameter_types_tuple); + + NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions stress_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + auto stress_solver = std::make_shared( + buildNonlinearBlockSolver(stress_nonlin, stress_lin, *field_store->getMesh())); + sys->stress_output_system = makeSubSystem(field_store, stress_solver, {sys->stress_weak_form}); + end_step_systems.push_back(sys->stress_output_system); } - return sys; + return std::make_tuple(sys, cycle_zero_system, end_step_systems); } /** - * @brief Standalone factory — allocates its own FieldStore and builds the full system. + * @brief Build a SolidMechanicsSystem without coupling, assuming fields are already registered. * - * Thin wrapper around registerSolidMechanicsFields + buildSolidMechanicsSystemFromStore. - * Existing call sites are unchanged. + * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). */ template -std::shared_ptr, parameter_space...>> -buildSolidMechanicsSystem(std::shared_ptr mesh, std::shared_ptr solver, - DisplacementTimeRule disp_time_rule, - SolidMechanicsOptions options, - FieldType... parameter_types) +auto buildSolidMechanicsSystem( + std::shared_ptr field_store, + DisplacementTimeRule rule, + std::shared_ptr solver, + const SolidMechanicsOptions& options, + FieldType... parameter_types) { - auto field_store = options.shared_field_store - ? options.shared_field_store - : std::make_shared(mesh, 100, options.prepend_name); - auto info = registerSolidMechanicsFields(field_store, disp_time_rule, parameter_types...); - return buildSolidMechanicsSystemFromStore>(info, solver, options, - CouplingSpec<>{}); + return buildSolidMechanicsSystem( + field_store, rule, CouplingParams<>{}, solver, options, parameter_types...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 8ace111433..21ad646ae0 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -11,7 +11,7 @@ * Two-phase factory (for coupling via combineSystems): * auto info = registerSolidMechanicsWithInternalVarsFields( * field_store, disp_rule, state_rule, params...); - * CouplingSpec coupling{...}; + * CouplingParams coupling{...}; * auto sys = buildSolidMechanicsWithInternalVarsSystemFromStore<...>( * info, solver, opts, coupling); * @@ -34,7 +34,7 @@ #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" -#include "smith/differentiable_numerics/coupling_spec.hpp" +#include "smith/differentiable_numerics/coupling_params.hpp" namespace smith { @@ -47,7 +47,7 @@ namespace smith { * * With a non-empty Coupling, coupling fields appear immediately after the hardcoded state fields * (after alpha_old for the solid form; after a_old for the state form) and before user parameter fields. - * setMaterial and addStateEvolution work correctly only when Coupling = CouplingSpec<> (default). + * setMaterial and addStateEvolution work correctly only when Coupling = CouplingParams<> (default). * For coupled systems, register integrands directly on solid_weak_form / state_weak_form. * * @tparam dim Spatial dimension. @@ -55,13 +55,13 @@ namespace smith { * @tparam StateSpace Finite element space for the internal variable (e.g., L2). * @tparam DisplacementTimeRule Time integration rule for displacement (must have num_states == 4). * @tparam InternalVarTimeRule Time integration rule for the internal variable (must have num_states == 2). - * @tparam Coupling CouplingSpec listing fields borrowed from other physics (default: no coupling). + * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: no coupling). * @tparam parameter_space Parameter spaces for material properties. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>, typename... parameter_space> struct SolidMechanicsWithInternalVarsSystem : public SystemBase { using SystemBase::SystemBase; @@ -111,7 +111,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { /** * @brief Set the material model for the solid mechanics part. * - * NOTE: works correctly only when Coupling = CouplingSpec<> (default). When coupling is active, + * NOTE: works correctly only when Coupling = CouplingParams<> (default). When coupling is active, * register integrands directly on solid_weak_form. * * @tparam MaterialType The material model type. @@ -294,7 +294,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { /** * @brief Add the evolution law for the internal variable. * - * NOTE: works correctly only when Coupling = CouplingSpec<> (default). When coupling is active, + * NOTE: works correctly only when Coupling = CouplingParams<> (default). When coupling is active, * register integrands directly on state_weak_form. * * @tparam EvolutionType The evolution law function type. @@ -369,54 +369,24 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { // Options // --------------------------------------------------------------------------- -template struct SolidMechanicsWithInternalVarsOptions { - std::string prepend_name{}; - std::shared_ptr shared_field_store{}; ///< Shared store for coupled systems; nullptr = allocate own. - std::shared_ptr cycle_zero_solver{}; + bool cycle_zero_solve = false; }; -// --------------------------------------------------------------------------- -// FieldInfo — returned by register; consumed by buildFromStore -// --------------------------------------------------------------------------- - -/// @brief Returned by registerSolidMechanicsWithInternalVarsFields. -template -struct SolidMechanicsWithInternalVarsFieldInfo { - std::shared_ptr field_store; - FieldType> disp_type; - FieldType> disp_old_type; - FieldType> velo_old_type; - FieldType> accel_old_type; - FieldType state_type; - FieldType state_old_type; - std::tuple...> parameter_types; - std::shared_ptr disp_bc; - std::shared_ptr state_bc; - std::shared_ptr disp_time_rule_ptr; - std::shared_ptr state_time_rule_ptr; -}; - -// --------------------------------------------------------------------------- -// Phase 1: register fields -// --------------------------------------------------------------------------- - /** - * @brief Register all solid-mechanics-with-internal-vars fields into a FieldStore. + * @brief Register all solid mechanics with internal vars fields into a FieldStore. + * + * Phase 1 of the two-phase initialization. Pass instances of the desired time integration rules + * so their types are deduced; only `` need be specified explicitly. * - * Phase 1 of the two-phase factory. Safe to call before other physics have registered their - * fields; weak form construction is deferred to buildSolidMechanicsWithInternalVarsSystemFromStore. - * When composing coupled systems, call all registerXxxFields functions before any buildXxxFromStore. + * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). */ -template -SolidMechanicsWithInternalVarsFieldInfo -registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_store, DisplacementTimeRule disp_rule, - InternalVarTimeRule state_rule, - FieldType... parameter_types) +template +auto registerSolidMechanicsWithInternalVarsFields( + std::shared_ptr field_store, + DisplacementTimeRule /*disp_rule*/, + InternalVarTimeRule /*state_rule*/, + FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(shape_disp_type.name)) { @@ -424,18 +394,18 @@ registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_s } // Displacement fields (4-state second-order) - auto disp_time_rule_ptr = std::make_shared(disp_rule); + auto disp_time_rule_ptr = std::make_shared(); FieldType> disp_type("displacement_solve_state"); - auto disp_bc = field_store->addIndependent(disp_type, disp_time_rule_ptr); - auto disp_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); - auto velo_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); - auto accel_old_type = field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); + field_store->addIndependent(disp_type, disp_time_rule_ptr); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); // Internal variable fields (2-state first-order) - auto state_time_rule_ptr = std::make_shared(state_rule); + auto state_time_rule_ptr = std::make_shared(); FieldType state_type("state_solve_state"); - auto state_bc = field_store->addIndependent(state_type, state_time_rule_ptr); - auto state_old_type = field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); + field_store->addIndependent(state_type, state_time_rule_ptr); + field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); // Parameters auto prefix_param = [&](auto& pt) { @@ -444,48 +414,58 @@ registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_s }; (prefix_param(parameter_types), ...); - return {field_store, disp_type, disp_old_type, velo_old_type, accel_old_type, - state_type, state_old_type, std::make_tuple(parameter_types...), disp_bc, - state_bc, disp_time_rule_ptr, state_time_rule_ptr}; + return CouplingParams{ + FieldType>(field_store->prefix("displacement_solve_state")), + FieldType>(field_store->prefix("displacement")), + FieldType>(field_store->prefix("velocity")), + FieldType>(field_store->prefix("acceleration")), + FieldType(field_store->prefix("state_solve_state")), + FieldType(field_store->prefix("state")), + parameter_types... + }; } -// --------------------------------------------------------------------------- -// Phase 2: build system from store -// --------------------------------------------------------------------------- - /** - * @brief Build a SolidMechanicsWithInternalVarsSystem from an already-populated FieldStore. + * @brief Build a SolidMechanicsWithInternalVarsSystem with coupling, assuming fields are already registered. * - * Phase 2 of the two-phase factory. Constructs all weak forms from fields already registered - * in the store (including any coupling fields from other physics). + * Phase 2 of the two-phase initialization. Pass the same rule instances used in + * registerSolidMechanicsWithInternalVarsFields so their types are deduced; + * only `` need be specified. * - * @tparam Coupling CouplingSpec listing fields borrowed from other physics (leading tail positions). + * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. */ template -std::shared_ptr> -buildSolidMechanicsWithInternalVarsSystemFromStore( - SolidMechanicsWithInternalVarsFieldInfo - info, + requires detail::is_coupling_params_v +auto buildSolidMechanicsWithInternalVarsSystem( + std::shared_ptr field_store, + DisplacementTimeRule /*disp_rule*/, + InternalVarTimeRule /*state_rule*/, + const Coupling& coupling, std::shared_ptr solver, - const SolidMechanicsWithInternalVarsOptions& options, - const Coupling& coupling) + const SolidMechanicsWithInternalVarsOptions& /*options*/, + FieldType... parameter_types) { - auto& field_store = info.field_store; - auto& disp_type = info.disp_type; - auto& disp_old_type = info.disp_old_type; - auto& velo_old_type = info.velo_old_type; - auto& accel_old_type = info.accel_old_type; - auto& state_type = info.state_type; - auto& state_old_type = info.state_old_type; - auto parameter_types = info.parameter_types; - auto& disp_bc = info.disp_bc; - auto& state_bc = info.state_bc; - auto& disp_time_rule_ptr = info.disp_time_rule_ptr; - auto& state_time_rule_ptr = info.state_time_rule_ptr; + auto prefix_param = [&](auto& pt) { + pt.name = field_store->prefix("param_" + pt.name); + }; + (prefix_param(parameter_types), ...); + auto parameter_types_tuple = std::make_tuple(parameter_types...); + + auto disp_time_rule_ptr = std::make_shared(); + auto state_time_rule_ptr = std::make_shared(); + + FieldType> shape_disp_type(field_store->prefix("shape_displacement")); + FieldType> disp_type(field_store->prefix("displacement_solve_state"), true); + FieldType> disp_old_type(field_store->prefix("displacement")); + FieldType> velo_old_type(field_store->prefix("velocity")); + FieldType> accel_old_type(field_store->prefix("acceleration")); + + FieldType state_type(field_store->prefix("state_solve_state"), true); + FieldType state_old_type(field_store->prefix("state")); + + auto disp_bc = field_store->getBoundaryConditions(disp_type.name); + auto state_bc = field_store->getBoundaryConditions(state_type.name); using SystemType = SolidMechanicsWithInternalVarsSystem; @@ -503,7 +483,7 @@ buildSolidMechanicsWithInternalVarsSystemFromStore( }, coupling.fields); }, - parameter_types); + parameter_types_tuple); // State weak form: (alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params...) std::string state_res_name = field_store->prefix("state_residual"); @@ -518,7 +498,7 @@ buildSolidMechanicsWithInternalVarsSystemFromStore( }, coupling.fields); }, - parameter_types); + parameter_types_tuple); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form, state_weak_form}); @@ -529,6 +509,9 @@ buildSolidMechanicsWithInternalVarsSystemFromStore( sys->solid_weak_form = solid_weak_form; sys->state_weak_form = state_weak_form; + std::shared_ptr cycle_zero_system; + std::vector> end_step_systems; + if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("solid_reaction"); auto accel_as_unknown = accel_old_type; @@ -547,63 +530,48 @@ buildSolidMechanicsWithInternalVarsSystemFromStore( }, coupling.fields); }, - parameter_types); + parameter_types_tuple); field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - std::shared_ptr cycle_zero_solver; - if (options.cycle_zero_solver) { - cycle_zero_solver = options.cycle_zero_solver; - } else { - NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; - LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - cycle_zero_solver = std::make_shared( - buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); - } + NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + auto cycle_zero_solver = std::make_shared( + buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); + sys->cycle_zero_system = makeSubSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); + cycle_zero_system = sys->cycle_zero_system; } - return sys; + return std::make_tuple(sys, cycle_zero_system, end_step_systems); } -// --------------------------------------------------------------------------- -// Standalone factory — thin wrapper, backwards-compatible -// --------------------------------------------------------------------------- - /** - * @brief Standalone factory — allocates its own FieldStore and builds the full system. + * @brief Build a SolidMechanicsWithInternalVarsSystem without coupling, assuming fields are already registered. * - * Thin wrapper around registerSolidMechanicsWithInternalVarsFields + - * buildSolidMechanicsWithInternalVarsSystemFromStore. Existing call sites are unchanged. + * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). */ -template -std::shared_ptr, parameter_space...>> -buildSolidMechanicsWithInternalVarsSystem( - std::shared_ptr mesh, std::shared_ptr solver, DisplacementTimeRule disp_rule, +auto buildSolidMechanicsWithInternalVarsSystem( + std::shared_ptr field_store, + DisplacementTimeRule disp_rule, InternalVarTimeRule state_rule, - SolidMechanicsWithInternalVarsOptions - options, + std::shared_ptr solver, + const SolidMechanicsWithInternalVarsOptions& options, FieldType... parameter_types) { - auto field_store = options.shared_field_store ? options.shared_field_store - : std::make_shared(mesh, 100, options.prepend_name); - auto info = registerSolidMechanicsWithInternalVarsFields( - field_store, disp_rule, state_rule, parameter_types...); - return buildSolidMechanicsWithInternalVarsSystemFromStore>( - info, solver, options, CouplingSpec<>{}); + return buildSolidMechanicsWithInternalVarsSystem( + field_store, disp_rule, state_rule, CouplingParams<>{}, solver, options, parameter_types...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index 0c95a23a34..e99a7e1a24 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -12,7 +12,7 @@ set(differentiable_numerics_test_source test_solid_dynamics.cpp test_explicit_dynamics.cpp test_porous_heat_sink.cpp - test_combined_thermo_mechanics.cpp + test_coupled_thermo_mechanics.cpp test_thermal_static.cpp test_solid_static_with_internal_vars.cpp test_multiphysics_time_integrator.cpp diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp similarity index 68% rename from src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp rename to src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp index 7f0ba1a787..3c487d7514 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp @@ -97,36 +97,30 @@ struct ThermoelasticMaterialNoParam { } }; -template +template void setCoupledThermoMechanicsMaterial( - std::shared_ptr, H1>, - P...>> solid, - std::shared_ptr, H1, - H1, H1, P...>>> thermal, - const MaterialType& material, - const std::string& domain_name) + std::shared_ptr< + SolidMechanicsSystem, H1>, P...>> + solid, + std::shared_ptr, H1, H1, H1, P...>>> + thermal, + const MaterialType& material, const std::string& domain_name) { auto captured_disp_rule = solid->disp_time_rule; auto captured_temp_rule = thermal->temperature_time_rule; // Solid contribution: inertia + PK1 stress solid->solid_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, - auto temperature, auto temperature_old, auto... params) { - auto [u_current, v_current, a_current] = - captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, + auto temperature_old, auto... params) { + auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); auto T = captured_temp_rule->value(t_info, temperature, temperature_old); typename MaterialType::State state; - auto [pk, C_v, s0, q0] = - material(t_info.dt(), state, get(u_current), get(v_current), - get(T), get(T), params...); + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), + get(T), get(T), params...); return smith::tuple{get(a_current) * material.density, pk}; }); @@ -134,31 +128,26 @@ void setCoupledThermoMechanicsMaterial( if (solid->cycle_zero_solid_weak_form) { solid->cycle_zero_solid_weak_form->addBodyIntegral( domain_name, - [=](auto t_info, auto /*X*/, auto u, auto v, auto a, - auto temperature, auto temperature_old, auto... params) { + [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { auto T = captured_temp_rule->value(t_info, temperature, temperature_old); typename MaterialType::State state; - auto [pk, C_v, s0, q0] = - material(t_info.dt(), state, get(u), get(v), - get(T), get(T), params...); + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), + get(T), params...); return smith::tuple{get(a) * material.density, pk}; }); } // Thermal contribution - thermal->thermal_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto T, auto T_old, - auto disp, auto disp_old, auto v_old, auto a_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = - material(t_info.dt(), state, get(u), get(v), - get(T_current), get(T_current), params...); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; - }); + thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, + auto disp_old, auto v_old, auto a_old, auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T_current), + get(T_current), params...); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); } struct ThermoMechanicsMeshFixture : public testing::Test { @@ -189,33 +178,28 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); - + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}, youngs_modulus); - auto thermal_info = registerThermalFields(field_store, TempRule{}); - - CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), - FieldType>("temperature")}; - CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), - FieldType>("displacement"), - FieldType>("velocity"), - FieldType>("acceleration"), - FieldType>("param_youngs_modulus")}; - - SolidMechanicsOptions> solid_opts{.shared_field_store = field_store}; - ThermalOptions thermal_opts{.shared_field_store = field_store}; - - auto solid = buildSolidMechanicsSystemFromStore( - solid_info, std::make_shared(solid_block_solver), solid_opts, solid_coupling); - - auto thermal = buildThermalSystemFromStore( - thermal_info, std::make_shared(thermal_block_solver), thermal_opts, thermal_coupling); - - auto coupled = combineSystems(solid, thermal); - - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + DispRule disp_rule; + TempRule temp_rule; + auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); + + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts, + youngs_modulus); + + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + field_store, temp_rule, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + + auto [coupled, coupled_cz] = combineSystems(solid, thermal); + + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); const auto& solid_dual_space = physics->dual("reactions").space(); const auto& solid_state_space = physics->state("displacement_solve_state").space(); const auto& thermal_dual_space = physics->dual("thermal_flux").space(); @@ -248,38 +232,33 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); - + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}, youngs_modulus); - auto thermal_info = registerThermalFields(field_store, TempRule{}); - - CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), - FieldType>("temperature")}; - CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), - FieldType>("displacement"), - FieldType>("velocity"), - FieldType>("acceleration"), - FieldType>("param_youngs_modulus")}; // thermal borrows parameter - - SolidMechanicsOptions> solid_opts{.shared_field_store = field_store}; - ThermalOptions thermal_opts{.shared_field_store = field_store}; - - auto solid = buildSolidMechanicsSystemFromStore( - solid_info, std::make_shared(solid_block_solver), solid_opts, solid_coupling); - - auto thermal = buildThermalSystemFromStore( - thermal_info, std::make_shared(thermal_block_solver), thermal_opts, thermal_coupling); - - auto coupled = combineSystems(solid, thermal); + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + DispRule disp_rule; + TempRule temp_rule; + auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); + + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts, + youngs_modulus); + + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + field_store, temp_rule, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + + auto [coupled, coupled_cz] = combineSystems(solid, thermal); GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( [=](smith::tensor) { return 100.0; }); - + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); @@ -289,7 +268,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) return traction; }); - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); // Run forward double dt = 1.0; @@ -344,29 +323,25 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto thermal_block_solver = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); auto field_store = std::make_shared(mesh_, 100, ""); - + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}); - auto thermal_info = registerThermalFields(field_store, TempRule{}); - CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), - FieldType>("temperature")}; - CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), - FieldType>("displacement"), - FieldType>("velocity"), - FieldType>("acceleration")}; + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + DispRule disp_rule; + TempRule temp_rule; + auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule); + auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); - SolidMechanicsOptions solid_opts{.shared_field_store = field_store}; - ThermalOptions thermal_opts{.shared_field_store = field_store}; + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto solid = buildSolidMechanicsSystemFromStore( - solid_info, std::make_shared(solid_block_solver), solid_opts, solid_coupling); - auto thermal = buildThermalSystemFromStore( - thermal_info, std::make_shared(thermal_block_solver), thermal_opts, thermal_coupling); + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + field_store, temp_rule, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); - auto coupled = combineSystems(solid, thermal); + auto [coupled, coupled_cz] = combineSystems(solid, thermal); ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -385,8 +360,7 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) force[1] = lateral_body_force; return force; }); - thermal->addHeatSource(mesh_->entireBodyName(), - [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); SLIC_INFO_ROOT("Starting staggered thermo-mechanics solve"); @@ -398,12 +372,12 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) std::vector reactions; for (size_t step = 0; step < 1; ++step) { std::tie(states, reactions) = - makeAdvancer(coupled)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); - + bool staggered_solid_converged = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); int staggered_solid_iterations = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); bool staggered_thermal_converged = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); @@ -425,7 +399,6 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) EXPECT_GT(staggered_lateral_deflection, 1e-5); } - // 4. MonolithicBucklingChallenge // Verifies convergence and displacement of the monolithic combined solver. TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) @@ -449,30 +422,27 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solver_ptr = std::make_shared(block_solver); auto field_store = std::make_shared(mesh_, 100, ""); - + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - + // Notice that the block_solver is the SAME solver for the whole system - auto solid_info = registerSolidMechanicsFields(field_store, DispRule{}); - auto thermal_info = registerThermalFields(field_store, TempRule{}); - CouplingSpec solid_coupling{FieldType>("temperature_solve_state"), - FieldType>("temperature")}; - CouplingSpec thermal_coupling{FieldType>("displacement_solve_state"), - FieldType>("displacement"), - FieldType>("velocity"), - FieldType>("acceleration")}; + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + DispRule disp_rule; + TempRule temp_rule; + auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule); + auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); - SolidMechanicsOptions solid_opts{.shared_field_store = field_store}; - ThermalOptions thermal_opts{.shared_field_store = field_store}; + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + field_store, disp_rule, thermal_coupling_fields, nullptr, solid_opts); - auto solid = buildSolidMechanicsSystemFromStore( - solid_info, nullptr, solid_opts, solid_coupling); - auto thermal = buildThermalSystemFromStore( - thermal_info, nullptr, thermal_opts, thermal_coupling); + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + field_store, temp_rule, solid_coupling_fields, nullptr, thermal_opts); - auto coupled = combineSystemsMonolithic(solver_ptr, solid, thermal); + auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -491,8 +461,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) force[1] = lateral_body_force; return force; }); - thermal->addHeatSource(mesh_->entireBodyName(), - [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); SLIC_INFO_ROOT("Starting monolithic thermo-mechanics solve"); @@ -504,12 +473,12 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) std::vector reactions; for (size_t step = 0; step < 1; ++step) { std::tie(states, reactions) = - makeAdvancer(coupled)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); - + bool converged = block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); int iterations = block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index c1f4eed43f..fbb8dc9cd6 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -103,8 +103,15 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto solid_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(solid_block_solver); - auto system = buildSolidMechanicsSystem( - mesh, coupled_solver, ImplicitNewmarkSecondOrderTimeIntegrationRule{}, {.enable_stress_output = true}, + auto field_store = std::make_shared(mesh, 100, ""); + + ImplicitNewmarkSecondOrderTimeIntegrationRule time_rule; + registerSolidMechanicsFields( + field_store, time_rule, FieldType("bulk"), FieldType("shear") + ); + + auto [system, cz_sys, end_steps] = buildSolidMechanicsSystem( + field_store, time_rule, coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, FieldType("bulk"), FieldType("shear")); static constexpr double gravity = -9.0; @@ -147,8 +154,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) size_t cycle = 0; std::vector reactions; - auto advancer = makeAdvancer(system); - advancer->addPostSolveSystem(system->stress_output_system); + auto advancer = makeAdvancer(system, cz_sys, end_steps); for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); @@ -207,11 +213,17 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(solid_block_solver); - auto system = buildSolidMechanicsSystem(mesh, coupled_solver, time_rule, {.prepend_name = physics_name}, - FieldType("bulk"), - FieldType("shear")); + auto field_store = std::make_shared(mesh, 100, physics_name); + + registerSolidMechanicsFields( + field_store, time_rule, FieldType("bulk"), FieldType("shear") + ); + + auto [system, cz_sys, end_steps] = buildSolidMechanicsSystem( + field_store, time_rule, coupled_solver, SolidMechanicsOptions{}, + FieldType("bulk"), FieldType("shear")); - auto physics = makeDifferentiablePhysics(system, physics_name); + auto physics = makeDifferentiablePhysics(system, physics_name, cz_sys, end_steps); auto bcs = system->disp_bc; bcs->setFixedVectorBCs(mesh->domain("right")); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 866be1fa25..24a5941ef5 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -102,11 +102,16 @@ struct StrainNormEvolution { TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) { auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); - auto system = buildSolidMechanicsWithInternalVarsSystem( - mesh, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, - {.prepend_name = "solid_static_with_internal_vars"}); + + auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); + + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule state_rule; + registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); + + auto [system, cz_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( + field_store, disp_rule, state_rule, coupled_solver, SolidMechanicsWithInternalVarsOptions{}); // Material and Evolution system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); @@ -125,7 +130,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) return u; }); - auto physics = makeDifferentiablePhysics(system, "physics"); + auto physics = makeDifferentiablePhysics(system, "physics", cz_sys, end_steps); // Create ParaView writer auto writer = createParaviewWriter(*mesh, system->field_store->getOutputFieldStates(), "solid_state_output"); @@ -149,9 +154,14 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) staggered_solver->addSubsystemSolver({0}, disp_solver, 0.5); staggered_solver->addSubsystemSolver({1}, state_solver, 1.0); - auto system = buildSolidMechanicsWithInternalVarsSystem( - mesh, staggered_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, - {.prepend_name = "solid_staggered_relaxation"}); + auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); + + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule state_rule; + registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); + + auto [system, cz_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( + field_store, disp_rule, state_rule, staggered_solver, SolidMechanicsWithInternalVarsOptions{}); system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); @@ -164,7 +174,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) return u; }); - auto physics = makeDifferentiablePhysics(system, "physics_relaxed"); + auto physics = makeDifferentiablePhysics(system, "physics_relaxed", cz_sys, end_steps); for (int step = 1; step <= 3; ++step) { physics->advanceTimestep(1.0); SLIC_INFO("Staggered relaxation step " << step << " completed"); @@ -175,9 +185,14 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) { auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); - auto system = buildSolidMechanicsWithInternalVarsSystem( - mesh, coupled_solver, QuasiStaticSecondOrderTimeIntegrationRule{}, BackwardEulerFirstOrderTimeIntegrationRule{}, - {.prepend_name = "body_force_test"}); + auto field_store = std::make_shared(mesh, 100, "body_force_test_"); + + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule state_rule; + registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); + + auto [system, cz_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( + field_store, disp_rule, state_rule, coupled_solver, SolidMechanicsWithInternalVarsOptions{}); system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); @@ -201,7 +216,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) return t; }); - auto physics = makeDifferentiablePhysics(system, "physics_bf"); + auto physics = makeDifferentiablePhysics(system, "physics_bf", cz_sys, end_steps); physics->advanceTimestep(1.0); // Check that the displacement field is non-zero (the body force + traction produced deformation) diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 29891cf573..2af73c490a 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -51,8 +51,13 @@ struct ThermalStaticFixture : public testing::Test { auto nonlinear_block_solver = buildNonlinearBlockSolver(solver_options, linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); - auto thermal_system = - buildThermalSystem<2, temp_order>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}, {}); + auto field_store = std::make_shared(mesh, 100, ""); + + QuasiStaticFirstOrderTimeIntegrationRule temp_rule; + registerThermalFields<2, temp_order>(field_store, temp_rule); + + auto [thermal_system, cz_sys, end_steps] = buildThermalSystem<2, temp_order>( + field_store, temp_rule, coupled_solver, ThermalOptions{}); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -72,7 +77,7 @@ struct ThermalStaticFixture : public testing::Test { [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system) + auto [new_states, reactions] = makeAdvancer(thermal_system, cz_sys, end_steps) ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); @@ -142,8 +147,13 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto coupled_solver = std::make_shared(nonlinear_block_solver); FieldType> conductivity_param("conductivity"); - auto thermal_system = buildThermalSystem<2, 1>(mesh, coupled_solver, QuasiStaticFirstOrderTimeIntegrationRule{}, {}, - conductivity_param); + auto field_store = std::make_shared(mesh, 100, ""); + + QuasiStaticFirstOrderTimeIntegrationRule temp_rule; + registerThermalFields<2, 1>(field_store, temp_rule, conductivity_param); + + auto [thermal_system, cz_sys, end_steps] = buildThermalSystem<2, 1>( + field_store, temp_rule, coupled_solver, ThermalOptions{}, conductivity_param); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -169,7 +179,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system) + auto [new_states, reactions] = makeAdvancer(thermal_system, cz_sys, end_steps) ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 3b05a1eaf9..95709f562a 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -20,7 +20,7 @@ #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" -#include "smith/differentiable_numerics/coupling_spec.hpp" +#include "smith/differentiable_numerics/coupling_params.hpp" namespace smith { @@ -34,13 +34,13 @@ namespace smith { * @tparam dim Spatial dimension. * @tparam temp_order Order of the temperature basis. * @tparam TemperatureTimeRule Time integration rule type (must have num_states == 2). - * @tparam Coupling CouplingSpec listing fields borrowed from other physics (default: none). + * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). * Coupling fields occupy leading positions in the tail after the 2 time-rule state fields, * before user parameter_space fields. * @tparam parameter_space Finite element spaces for optional parameters. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>, typename... parameter_space> struct ThermalSystem : public SystemBase { using SystemBase::SystemBase; @@ -157,32 +157,21 @@ struct ThermalSystem : public SystemBase { } }; -template struct ThermalOptions { - std::string prepend_name{}; - std::shared_ptr shared_field_store{}; ///< Shared store for coupled systems; nullptr = allocate own. -}; - -/// @brief Returned by registerThermalFields; holds the FieldType tokens needed by buildThermalSystemFromStore. -template -struct ThermalFieldInfo { - std::shared_ptr field_store; - FieldType> temperature_type; - FieldType> temperature_old_type; - std::tuple...> parameter_types; - std::shared_ptr temperature_bc; - std::shared_ptr temperature_time_rule_ptr; }; /** * @brief Register all thermal fields into a FieldStore. * - * Phase 1 of the two-phase factory. Safe to call before other physics have registered their - * fields; weak form construction is deferred to buildThermalSystemFromStore. + * Phase 1 of the two-phase initialization. Pass an instance of the desired time integration rule + * so its type is deduced; only `` need be specified explicitly. + * + * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). */ template -ThermalFieldInfo registerThermalFields( - std::shared_ptr field_store, TemperatureTimeRule temp_rule, +auto registerThermalFields( + std::shared_ptr field_store, + TemperatureTimeRule /*rule*/, FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); @@ -190,11 +179,10 @@ ThermalFieldInfo regis field_store->addShapeDisp(shape_disp_type); } - auto temperature_time_rule_ptr = std::make_shared(temp_rule); + auto temperature_time_rule_ptr = std::make_shared(); FieldType> temperature_type("temperature_solve_state"); - auto temperature_bc = field_store->addIndependent(temperature_type, temperature_time_rule_ptr); - auto temperature_old_type = - field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); + field_store->addIndependent(temperature_type, temperature_time_rule_ptr); + field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; @@ -202,31 +190,46 @@ ThermalFieldInfo regis }; (prefix_param(parameter_types), ...); - return {field_store, temperature_type, temperature_old_type, - std::make_tuple(parameter_types...), temperature_bc, temperature_time_rule_ptr}; + return CouplingParams{ + FieldType>(field_store->prefix("temperature_solve_state")), + FieldType>(field_store->prefix("temperature")), + parameter_types... + }; } /** - * @brief Build a ThermalSystem from an already-populated FieldStore. + * @brief Build a ThermalSystem with coupling, assuming fields are already registered. * - * Phase 2 of the two-phase factory. Constructs all weak forms using fields already registered - * in the store (including any coupling fields from other physics). + * Phase 2 of the two-phase initialization. Pass the same rule instance used in + * registerThermalFields so the type is deduced; only `` need be specified. * - * @tparam Coupling CouplingSpec listing fields borrowed from other physics (leading tail positions). + * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. + * `cycle_zero_system` is always nullptr (thermal has no cycle-zero solve). + * `end_step_systems` is always empty. */ template -std::shared_ptr> -buildThermalSystemFromStore(ThermalFieldInfo info, - std::shared_ptr solver, - const ThermalOptions& /*options*/, - const Coupling& coupling) + requires detail::is_coupling_params_v +auto buildThermalSystem( + std::shared_ptr field_store, + TemperatureTimeRule /*rule*/, + const Coupling& coupling, + std::shared_ptr solver, + const ThermalOptions& /*options*/, + FieldType... parameter_types) { - auto& field_store = info.field_store; - auto& temperature_type = info.temperature_type; - auto& temperature_old_type = info.temperature_old_type; - auto parameter_types = info.parameter_types; - auto& temperature_bc = info.temperature_bc; - auto& temperature_time_rule_ptr = info.temperature_time_rule_ptr; + auto temperature_time_rule_ptr = std::make_shared(); + + FieldType> shape_disp_type(field_store->prefix("shape_displacement")); + FieldType> temperature_type(field_store->prefix("temperature_solve_state"), true); + FieldType> temperature_old_type(field_store->prefix("temperature")); + + auto temperature_bc = field_store->getBoundaryConditions(temperature_type.name); + + auto prefix_param = [&](auto& pt) { + pt.name = field_store->prefix("param_" + pt.name); + }; + (prefix_param(parameter_types), ...); + auto parameter_types_tuple = std::make_tuple(parameter_types...); using SystemType = ThermalSystem; @@ -243,7 +246,7 @@ buildThermalSystemFromStore(ThermalFieldInfo(field_store, solver, std::vector>{thermal_weak_form}); @@ -251,27 +254,24 @@ buildThermalSystemFromStore(ThermalFieldInfotemperature_time_rule = temperature_time_rule_ptr; sys->thermal_weak_form = thermal_weak_form; - return sys; + return std::make_tuple(sys, std::shared_ptr{}, std::vector>{}); } /** - * @brief Standalone factory — allocates its own FieldStore and builds the full system. + * @brief Build a ThermalSystem without coupling, assuming fields are already registered. * - * Thin wrapper around registerThermalFields + buildThermalSystemFromStore. - * Existing call sites are unchanged. + * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). */ template -std::shared_ptr, parameter_space...>> -buildThermalSystem(std::shared_ptr mesh, std::shared_ptr solver, TemperatureTimeRule temp_rule, - ThermalOptions options, - FieldType... parameter_types) +auto buildThermalSystem( + std::shared_ptr field_store, + TemperatureTimeRule rule, + std::shared_ptr solver, + const ThermalOptions& options, + FieldType... parameter_types) { - auto field_store = options.shared_field_store - ? options.shared_field_store - : std::make_shared(mesh, 100, options.prepend_name); - auto info = registerThermalFields(field_store, temp_rule, parameter_types...); - return buildThermalSystemFromStore>(info, solver, options, - CouplingSpec<>{}); + return buildThermalSystem( + field_store, rule, CouplingParams<>{}, solver, options, parameter_types...); } } // namespace smith From 1b46d4cdd83b4d7db5a636c73836eeef8ce17e4f Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 16 Apr 2026 08:51:52 -0600 Subject: [PATCH 16/67] Implement state variable system. --- .../differentiable_numerics/CMakeLists.txt | 1 + .../state_variable_system.hpp | 227 ++++++++++++++++++ .../test_solid_static_with_internal_vars.cpp | 3 + 3 files changed, 231 insertions(+) create mode 100644 src/smith/differentiable_numerics/state_variable_system.hpp diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index ac2e547129..3e869690b4 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -40,6 +40,7 @@ set(differentiable_numerics_headers multiphysics_time_integrator.hpp solid_mechanics_system.hpp solid_mechanics_with_internal_vars_system.hpp + state_variable_system.hpp thermal_system.hpp coupling_params.hpp combined_system.hpp diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp new file mode 100644 index 0000000000..29725dd68b --- /dev/null +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -0,0 +1,227 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file state_variable_system.hpp + * @brief Standalone composable system for a first-order internal variable (damage, plasticity, etc.). + * + * Two-phase factory: + * auto state_coupling_fields = registerStateVariableFields( + * field_store, state_rule, params...); + * + * auto [state_sys, cz, ends] = buildStateVariableSystem( + * field_store, state_rule, [solid_coupling,] solver, opts, params...); + * + * The returned CouplingParams from registerStateVariableFields carries field tokens + * (state_solve_state, state) that can be injected into another physics system + * (e.g. SolidMechanicsSystem) as coupling input. + * + * The system's addStateEvolution registers an ODE residual of the form: + * evolution_law(t_info, alpha_val, alpha_dot, coupling_fields..., params...) == 0 + * + * With Coupling = CouplingParams, H1, H1, H1> + * (the solid displacement coupling fields), the user accesses the displacement gradient via + * get(u_solve_state_arg) as the first coupling field argument. + */ + +#pragma once + +#include "smith/differentiable_numerics/field_store.hpp" +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" +#include "smith/differentiable_numerics/state_advancer.hpp" +#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" +#include "smith/differentiable_numerics/time_integration_rule.hpp" +#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/differentiable_numerics/differentiable_physics.hpp" +#include "smith/physics/weak_form.hpp" +#include "smith/differentiable_numerics/system_base.hpp" +#include "smith/differentiable_numerics/coupling_params.hpp" + +namespace smith { + +/** + * @brief System for a single internal variable using a two-state first-order rule. + * + * Field layout: (state_solve_state, state) — 2 fields. + * With a non-empty Coupling, coupling fields appear after the two state fields, + * before user parameter fields. + * + * @tparam dim Spatial dimension (needed for the weak form and zero-flux tensor). + * @tparam StateSpace FE space for the internal variable (e.g., L2<0>). + * @tparam InternalVarTimeRule Time integration rule (must have num_states == 2). + * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). + * @tparam parameter_space Parameter spaces for additional material properties. + */ +template , typename... parameter_space> +struct StateVariableSystem : public SystemBase { + using SystemBase::SystemBase; + + static_assert(InternalVarTimeRule::num_states == 2, + "StateVariableSystem requires a 2-state time integration rule"); + + /// State weak form: (alpha, alpha_old, coupling_fields..., params...) + using StateWeakFormType = TimeDiscretizedWeakForm< + dim, StateSpace, + typename detail::TimeRuleParamsWithCoupling::type>; + + std::shared_ptr state_weak_form; ///< Internal variable weak form. + std::shared_ptr state_bc; ///< Internal variable boundary conditions. + std::shared_ptr state_time_rule; ///< Time integration rule. + + /** + * @brief Register an ODE evolution law for the internal variable. + * + * The evolution_law is called as: + * evolution_law(t_info, alpha_val, alpha_dot, coupling_fields..., params...) + * and must return a scalar residual (zero when the ODE is satisfied). + * + * When Coupling carries solid displacement fields (u_ss, u, v, a), access the + * displacement gradient via get(u_ss_arg) on the first coupling field. + * + * @param domain_name Domain to apply the evolution on. + * @param evolution_law Callable returning the ODE residual. + */ + template + void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) + { + auto captured_state_rule = state_time_rule; + state_weak_form->addBodyIntegral( + domain_name, [=](auto t_info, auto /*X*/, auto alpha, auto alpha_old, auto... coupling_and_params) { + auto [alpha_current, alpha_dot] = captured_state_rule->interpolate(t_info, alpha, alpha_old); + auto residual_val = + evolution_law(t_info, get(alpha_current), get(alpha_dot), coupling_and_params...); + tensor flux{}; + return smith::tuple{residual_val, flux}; + }); + } +}; + +// --------------------------------------------------------------------------- +// Options +// --------------------------------------------------------------------------- + +struct StateVariableOptions {}; + +// --------------------------------------------------------------------------- +// Phase 1: registerStateVariableFields +// --------------------------------------------------------------------------- + +/** + * @brief Register state variable fields into a FieldStore. + * + * Adds a 2-field layout: (state_solve_state, state). + * Pass an instance of the desired time integration rule so its type is deduced; + * only `` needs to be specified explicitly. + * + * @return CouplingParams carrying (state_solve_state, state[, params...]) field tokens + * suitable for injection into another physics system. + */ +template +auto registerStateVariableFields( + std::shared_ptr field_store, + InternalVarTimeRule /*rule*/, + FieldType... parameter_types) +{ + auto state_time_rule_ptr = std::make_shared(); + FieldType state_type("state_solve_state"); + field_store->addIndependent(state_type, state_time_rule_ptr); + field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); + + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); + + return CouplingParams{ + FieldType(field_store->prefix("state_solve_state")), + FieldType(field_store->prefix("state")), + parameter_types... + }; +} + +// --------------------------------------------------------------------------- +// Phase 2: buildStateVariableSystem +// --------------------------------------------------------------------------- + +/** + * @brief Build a StateVariableSystem with coupling, assuming fields are already registered. + * + * Pass the same rule instance used in registerStateVariableFields so its type is deduced; + * only `` need be specified. + * + * Returns `{system, nullptr, {}}` as a tuple (no cycle-zero or end-step systems). + */ +template + requires detail::is_coupling_params_v +auto buildStateVariableSystem( + std::shared_ptr field_store, + InternalVarTimeRule /*rule*/, + const Coupling& coupling, + std::shared_ptr solver, + const StateVariableOptions& /*options*/, + FieldType... parameter_types) +{ + auto prefix_param = [&](auto& pt) { pt.name = field_store->prefix("param_" + pt.name); }; + (prefix_param(parameter_types), ...); + auto parameter_types_tuple = std::make_tuple(parameter_types...); + + auto state_time_rule_ptr = std::make_shared(); + + FieldType state_type(field_store->prefix("state_solve_state"), true); + FieldType state_old_type(field_store->prefix("state")); + + auto state_bc = field_store->getBoundaryConditions(state_type.name); + + using SystemType = StateVariableSystem; + + std::string state_res_name = field_store->prefix("state_residual"); + auto state_weak_form = std::apply( + [&](auto&... params) { + return std::apply( + [&](auto&... cfs) { + return std::make_shared( + state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), + field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, cfs..., + params...)); + }, + coupling.fields); + }, + parameter_types_tuple); + + auto sys = std::make_shared(field_store, solver, std::vector>{state_weak_form}); + sys->state_bc = state_bc; + sys->state_time_rule = state_time_rule_ptr; + sys->state_weak_form = state_weak_form; + + std::shared_ptr cycle_zero_system; + std::vector> end_step_systems; + return std::make_tuple(sys, cycle_zero_system, end_step_systems); +} + +/** + * @brief Build a StateVariableSystem without coupling. + * + * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). + */ +template +auto buildStateVariableSystem( + std::shared_ptr field_store, + InternalVarTimeRule rule, + std::shared_ptr solver, + const StateVariableOptions& options, + FieldType... parameter_types) +{ + return buildStateVariableSystem( + field_store, rule, CouplingParams<>{}, solver, options, parameter_types...); +} + +} // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 24a5941ef5..7b4bfe7e50 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -9,6 +9,9 @@ #include "smith/infrastructure/application_manager.hpp" #include "smith/numerics/solver_config.hpp" #include "smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/state_variable_system.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" #include "smith/differentiable_numerics/paraview_writer.hpp" From 4eecd967dc5eeb00d88af55bac01fd755d904f0d Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 16 Apr 2026 10:07:57 -0600 Subject: [PATCH 17/67] Support a None preconditioner. --- src/smith/numerics/equation_solver.cpp | 34 ++++++ src/smith/numerics/solver_config.hpp | 6 +- src/smith/numerics/tests/CMakeLists.txt | 1 + .../tests/test_linear_solver_none.cpp | 111 ++++++++++++++++++ 4 files changed, 151 insertions(+), 1 deletion(-) create mode 100644 src/smith/numerics/tests/test_linear_solver_none.cpp diff --git a/src/smith/numerics/equation_solver.cpp b/src/smith/numerics/equation_solver.cpp index b7bd513194..742ef9e7cd 100644 --- a/src/smith/numerics/equation_solver.cpp +++ b/src/smith/numerics/equation_solver.cpp @@ -25,6 +25,36 @@ namespace smith { namespace { +/** + * @brief Simple solver wrapper that only applies a preconditioner. + */ +class PreconditionerOnlySolver : public mfem::IterativeSolver { + public: + PreconditionerOnlySolver(MPI_Comm comm) : mfem::IterativeSolver(comm) {} + + /// @overload + void Mult(const mfem::Vector& x, mfem::Vector& y) const override + { + if (prec) { + prec->Mult(x, y); + } else { + y = x; + } + } + + /// @overload + void SetOperator(const mfem::Operator& op) override + { + if (prec) { + prec->SetOperator(op); + } + width = op.Width(); + height = op.Height(); + } + + private: + // Note: mfem::IterativeSolver already has a 'prec' member (mfem::Solver*) +}; bool preconditionerSupportsBlockOperator(Preconditioner preconditioner) { @@ -57,6 +87,7 @@ bool linearSolverSupportsBlockOperator(LinearSolver linear_solver) case LinearSolver::PetscCG: case LinearSolver::PetscGMRES: #endif + case LinearSolver::None: return true; default: return false; @@ -1338,6 +1369,9 @@ std::pair, std::unique_ptr> buildLin exit(1); break; #endif + case LinearSolver::None: + iter_lin_solver = std::make_unique(comm); + break; default: SLIC_ERROR_ROOT("Linear solver type not recognized."); exit(1); diff --git a/src/smith/numerics/solver_config.hpp b/src/smith/numerics/solver_config.hpp index 0a9c9b7236..5a203739cc 100644 --- a/src/smith/numerics/solver_config.hpp +++ b/src/smith/numerics/solver_config.hpp @@ -107,7 +107,8 @@ enum class LinearSolver SuperLU, /**< SuperLU MPI-enabled direct nodal solver */ Strumpack, /**< Strumpack MPI-enabled direct frontal solver*/ PetscCG, /**< PETSc MPI-enabled conjugate gradient solver */ - PetscGMRES /**< PETSc MPI-enabled generalize minimal residual solver */ + PetscGMRES, /**< PETSc MPI-enabled generalize minimal residual solver */ + None /**< Preconditioner application only, No linear solver Krylov iterations */ }; // _linear_solvers_end @@ -127,6 +128,8 @@ inline std::string linearName(const LinearSolver& s) return "PetscCG"; case LinearSolver::PetscGMRES: return "PetscGMRES"; + case LinearSolver::None: + return "None"; } // This cannot happen, but GCC doesn't know that return "UNKNOWN"; @@ -140,6 +143,7 @@ inline std::map linearSolverMap = { {"CG", LinearSolver::CG}, {"GMRES", LinearSolver::GMRES}, {"SuperLU", LinearSolver::SuperLU}, {"Strumpack", LinearSolver::Strumpack}, {"PetscCG", LinearSolver::PetscCG}, {"PetscGMRES", LinearSolver::PetscGMRES}, + {"None", LinearSolver::None}, }; // Add a custom list of strings? conduit node? diff --git a/src/smith/numerics/tests/CMakeLists.txt b/src/smith/numerics/tests/CMakeLists.txt index afce056899..8c12c8a35e 100644 --- a/src/smith/numerics/tests/CMakeLists.txt +++ b/src/smith/numerics/tests/CMakeLists.txt @@ -12,6 +12,7 @@ set(numerics_serial_test_sources odes.cpp test_block_preconditioner.cpp test_block_preconditioner_backend.cpp + test_linear_solver_none.cpp ) smith_add_tests( SOURCES ${numerics_serial_test_sources} diff --git a/src/smith/numerics/tests/test_linear_solver_none.cpp b/src/smith/numerics/tests/test_linear_solver_none.cpp new file mode 100644 index 0000000000..9d0cd6246d --- /dev/null +++ b/src/smith/numerics/tests/test_linear_solver_none.cpp @@ -0,0 +1,111 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" +#include "mfem.hpp" +#include "smith/numerics/equation_solver.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/infrastructure/application_manager.hpp" + +namespace smith { + +/** + * @brief Simple identity-like operator: A*x = x + */ +class IdentityOperator : public mfem::Operator { + public: + IdentityOperator(int size) : mfem::Operator(size) {} + void Mult(const mfem::Vector& x, mfem::Vector& y) const override { y = x; } + mfem::Operator& GetGradient(const mfem::Vector& /*x*/) const override { return const_cast(*this); } +}; + +/** + * @brief Simple diagonal operator: A*x = d * x + */ +class DiagonalOperator : public mfem::Operator { + public: + DiagonalOperator(const mfem::Vector& d) : mfem::Operator(d.Size()), d_(d) {} + void Mult(const mfem::Vector& x, mfem::Vector& y) const override + { + for (int i = 0; i < height; i++) { + y(i) = d_(i) * x(i); + } + } + mfem::Operator& GetGradient(const mfem::Vector& /*x*/) const override { return const_cast(*this); } + + private: + const mfem::Vector& d_; +}; + +TEST(LinearSolverNone, Identity) +{ + int size = 10; + IdentityOperator op(size); + + LinearSolverOptions linear_opts; + linear_opts.linear_solver = LinearSolver::None; + linear_opts.preconditioner = Preconditioner::None; + + NonlinearSolverOptions nonlinear_opts; + nonlinear_opts.nonlin_solver = NonlinearSolver::Newton; + nonlinear_opts.max_iterations = 1; + + EquationSolver solver(nonlinear_opts, linear_opts, MPI_COMM_WORLD); + solver.setOperator(op); + + mfem::Vector x(size); + x = 1.0; // Initial guess + // Residual will be f(x) = x. + // x_new = x - [df/dx]^-1 * f(x) = x - I^-1 * x = 0. + + solver.solve(x); + + for (int i = 0; i < size; i++) { + EXPECT_NEAR(x(i), 0.0, 1e-12); + } +} + +TEST(LinearSolverNone, Jacobi) +{ + int size = 10; + mfem::Vector d(size); + d = 2.0; + DiagonalOperator op(d); + + LinearSolverOptions linear_opts; + linear_opts.linear_solver = LinearSolver::None; + linear_opts.preconditioner = Preconditioner::None; // We'll set this manually if needed, but wait. + // Actually, Preconditioner::None with LinearSolver::None should be Identity. + // Let's test that first. + + NonlinearSolverOptions nonlinear_opts; + nonlinear_opts.nonlin_solver = NonlinearSolver::Newton; + nonlinear_opts.max_iterations = 1; + + EquationSolver solver(nonlinear_opts, linear_opts, MPI_COMM_WORLD); + solver.setOperator(op); + + mfem::Vector x(size); + x = 1.0; + // f(x) = 2x. df/dx = 2I. + // If LinearSolver::None and Preconditioner::None, it uses identity: + // x_new = x - I * (2x) = x - 2x = -x. + + solver.solve(x); + + for (int i = 0; i < size; i++) { + EXPECT_NEAR(x(i), -1.0, 1e-12); + } +} + +} // namespace smith + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + smith::ApplicationManager applicationManager(argc, argv); + return RUN_ALL_TESTS(); +} From b91c70a5f548ab50873cc7d666a6cf815b1221e0 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 16 Apr 2026 22:53:41 -0600 Subject: [PATCH 18/67] Working toward our dream interface. --- .../coupling_params.hpp | 156 +++++- .../differentiable_numerics/field_store.hpp | 10 + .../solid_mechanics_system.hpp | 222 ++++---- ...id_mechanics_with_internal_vars_system.hpp | 100 ++-- .../state_variable_system.hpp | 43 +- .../differentiable_numerics/system_base.hpp | 6 +- .../tests/test_combined_thermo_mechanics.cpp | 504 ++++++++++++++++++ .../tests/test_coupled_thermo_mechanics.cpp | 147 +++-- .../thermal_system.hpp | 120 ++++- 9 files changed, 1054 insertions(+), 254 deletions(-) create mode 100644 src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 75c841baf5..445dc3177f 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -48,17 +48,159 @@ struct CouplingParams { template CouplingParams(FieldType...) -> CouplingParams; +/// Sentinel: no time integration rule (used for parameter-only packs). +struct NoTimeRule { + static constexpr int num_states = 0; +}; + +/** + * @brief Fields returned by a physics register function, carrying time rule type information. + * + * Unlike CouplingParams, PhysicsFields knows which time integration rule governs its fields. + * This lets variadic build functions deduce which pack is "self" vs coupling, and enables + * compile-time interpolation of coupling fields in traction/body force wrappers. + * + * @tparam TimeRule The time integration rule type (e.g. QuasiStaticSecondOrderTimeIntegrationRule). + * @tparam Spaces FE space types of the fields (e.g. H1 repeated num_states times). + */ +template +struct PhysicsFields { + using time_rule_type = TimeRule; + static constexpr std::size_t num_rule_states = TimeRule::num_states; + static constexpr std::size_t num_fields = sizeof...(Spaces); + static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); + std::shared_ptr field_store; + std::tuple...> fields; + + PhysicsFields(std::shared_ptr fs, FieldType... f) + : field_store(std::move(fs)), fields(std::move(f)...) + { + } +}; + +/** + * @brief Register parameter fields as type-level tokens. + * + * Actual FieldStore registration is deferred to the build function. + * Returns a CouplingParams carrying the parameter field types. + */ +template +auto registerParameterFields(FieldType... param_types) +{ + return CouplingParams{std::move(param_types)...}; +} + namespace detail { -/// Type trait: true if T is a CouplingParams<...> specialization. +/// Type trait: true if T is a CouplingParams<...> or PhysicsFields<...> specialization. +/// Both carry a `fields` tuple and can be used as coupling input to build functions. template struct is_coupling_params_impl : std::false_type {}; template struct is_coupling_params_impl> : std::true_type {}; +template +struct is_coupling_params_impl> : std::true_type {}; + +template +inline constexpr bool is_coupling_params_v = is_coupling_params_impl>::value; + +/// Type trait: true if T is a PhysicsFields<...> specialization. +template +struct is_physics_fields_impl : std::false_type {}; + +template +struct is_physics_fields_impl> : std::true_type {}; + template -inline constexpr bool is_coupling_params_v = is_coupling_params_impl::value; +inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; + +/// True if T is a PhysicsFields with a real time rule (not NoTimeRule). +template +inline constexpr bool has_time_rule_v = false; + +template +inline constexpr bool has_time_rule_v>> = + !std::is_same_v::time_rule_type, NoTimeRule>; + +// ------------------------------------------------------------------------- +// Helpers for variadic build functions +// ------------------------------------------------------------------------- + +/// Extract field_store from the first PhysicsFields pack. +template +std::shared_ptr findFieldStore(const First& first, const Rest&... rest) +{ + if constexpr (is_physics_fields_v) { + return first.field_store; + } else { + static_assert(sizeof...(Rest) > 0, "No PhysicsFields pack found — at least one is required"); + return findFieldStore(rest...); + } +} + +/// Extract physics coupling fields (non-self PhysicsFields packs). +template +auto collectPhysicsFromPack(const Pack& pack) +{ + if constexpr (is_physics_fields_v) { + if constexpr (std::is_same_v::time_rule_type, TargetRule>) { + return std::tuple{}; // skip self + } else { + return pack.fields; // include coupling fields + } + } else { + return std::tuple{}; // skip non-physics packs + } +} + +/// Extract parameter fields (CouplingParams packs that are NOT PhysicsFields), with "param_" prefix. +template +auto collectParamsFromPack(const Pack& pack) +{ + if constexpr (is_coupling_params_v> && !is_physics_fields_v) { + return std::apply( + [](auto... pts) { + auto prefix = [](auto pt) { + pt.name = "param_" + pt.name; + return pt; + }; + return std::make_tuple(prefix(pts)...); + }, + pack.fields); + } else { + return std::tuple{}; + } +} + +/// Collect non-self fields from all packs into a single CouplingParams. +/// Order: physics coupling fields first (in pack order), then parameter fields. +template +auto collectCouplingFields(const Packs&... packs) +{ + auto physics = std::tuple_cat(collectPhysicsFromPack(packs)...); + auto params = std::tuple_cat(collectParamsFromPack(packs)...); + auto combined = std::tuple_cat(physics, params); + return std::apply([](auto&... all) { return CouplingParams{all...}; }, combined); +} + +/// Register parameter fields from a CouplingParams pack (not PhysicsFields) into a FieldStore. +template +void registerParamsIfNeeded(std::shared_ptr fs, const Pack& pack) +{ + if constexpr (is_coupling_params_v> && !is_physics_fields_v) { + std::apply( + [&](auto... pts) { + auto prefix_and_add = [&](auto pt) { + pt.name = "param_" + pt.name; + fs->addParameter(pt); + }; + (prefix_and_add(pts), ...); + }, + pack.fields); + } +} /** * @brief Produce TimeRuleParams from a CouplingParams. @@ -74,6 +216,11 @@ struct TimeRuleParamsWithCoupling, Tail...> { using type = TimeRuleParams; }; +template +struct TimeRuleParamsWithCoupling, Tail...> { + using type = TimeRuleParams; +}; + /** * @brief Append coupling spaces (CS...) and Tail... onto a base Parameters type. * @@ -88,6 +235,11 @@ struct AppendCouplingToParams, Parameters, Tail. using type = Parameters; }; +template +struct AppendCouplingToParams, Parameters, Tail...> { + using type = Parameters; +}; + } // namespace detail } // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 7092f21710..6bf8f42411 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -108,6 +108,16 @@ struct FieldStore { void addParameter(FieldType& type) { type.name = prefix(type.name); + if (to_params_index_.count(type.name)) { + // Already registered — expected when multiple systems share the same parameter. + // Verify the space matches by checking vdim (== Space::components). + auto& existing = params_[to_params_index_.at(type.name)]; + SLIC_ERROR_ROOT_IF(existing.get()->space().GetVDim() != Space::components, + axom::fmt::format("Parameter '{}' re-registered with a different space " + "(existing vdim={}, new vdim={})", + type.name, existing.get()->space().GetVDim(), Space::components)); + return; + } to_params_index_[type.name] = params_.size(); params_.push_back(smith::createFieldState(*graph_, Space{}, type.name, mesh_->tag())); } diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 46cc4e64c6..58e6da2225 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -41,7 +41,7 @@ namespace smith { * @tparam parameter_space Parameter spaces for material properties. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>> struct SolidMechanicsSystem : public SystemBase { using SystemBase::SystemBase; @@ -50,23 +50,21 @@ struct SolidMechanicsSystem : public SystemBase { /// Main weak form: (u, u_old, v_old, a_old, coupling_fields..., params...) using SolidWeakFormType = TimeDiscretizedWeakForm< dim, H1, - typename detail::TimeRuleParamsWithCoupling, Coupling, - parameter_space...>::type>; + typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; /// Cycle-zero form: (u, v_old, a, coupling_fields..., params...) /// 3-state form: u, v, a (no u_old needed; at cycle 0 u and v are given, solve for a) - using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< - dim, H1, - typename detail::AppendCouplingToParams, H1, H1>, - parameter_space...>::type>; + using CycleZeroSolidWeakFormType = + TimeDiscretizedWeakForm, + typename detail::AppendCouplingToParams< + Coupling, Parameters, H1, H1>>::type>; /// L2 projection weak form for PK1 stress output (dim*dim components). /// Args: (stress_unknown, u, u_old, v_old, a_old, coupling_fields..., params...). using StressOutputWeakFormType = TimeDiscretizedWeakForm< dim, L2<0, dim * dim>, - typename detail::AppendCouplingToParams< - Coupling, Parameters, H1, H1, H1, H1>, - parameter_space...>::type>; + typename detail::AppendCouplingToParams, H1, H1, + H1, H1>>::type>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr @@ -153,7 +151,7 @@ struct SolidMechanicsSystem : public SystemBase { [=](auto t_info, auto X, auto u, auto v_old, auto a, auto... params) { return force_function(t_info.time(), X, u, v_old, a, params...); }, - std::make_index_sequence<3 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); } /** @@ -165,7 +163,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addBodyForce(const std::string& domain_name, BodyForceType force_function) { - addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); } /** @@ -193,7 +191,7 @@ struct SolidMechanicsSystem : public SystemBase { [=](auto t_info, auto X, auto n, auto u, auto v_old, auto a, auto... params) { return traction_function(t_info.time(), X, n, u, v_old, a, params...); }, - std::make_index_sequence<3 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); } /** @@ -205,7 +203,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addTraction(const std::string& domain_name, TractionType traction_function) { - addTractionAllParams(domain_name, traction_function, std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + addTractionAllParams(domain_name, traction_function, std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); } /** @@ -247,7 +245,7 @@ struct SolidMechanicsSystem : public SystemBase { return pressure * n_deformed * (1.0 / n_shape_norm); }, - std::make_index_sequence<3 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); } /** @@ -259,7 +257,23 @@ struct SolidMechanicsSystem : public SystemBase { template void addPressure(const std::string& domain_name, PressureType pressure_function) { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); + } + + /// Set zero-displacement Dirichlet BC on all components. + void setDisplacementBC(const Domain& domain) { disp_bc->template setFixedVectorBCs(domain); } + + /// Set zero-displacement BC on specific components. + void setDisplacementBC(const Domain& domain, std::vector components) + { + disp_bc->template setFixedVectorBCs(domain, components); + } + + /// Set displacement BC with a prescribed function. + template + void setDisplacementBC(const Domain& domain, AppliedDisplacementFunction f) + { + disp_bc->template setVectorBCs(domain, f); } private: @@ -308,7 +322,6 @@ struct SolidMechanicsSystem : public SystemBase { }; struct SolidMechanicsOptions { - bool cycle_zero_solve = false; bool enable_stress_output = false; }; @@ -318,13 +331,43 @@ struct SolidMechanicsOptions { * Phase 1 of the two-phase initialization. Pass an instance of the desired time integration rule * so its type is deduced; only `` need be specified explicitly. * + * @return PhysicsFields carrying the exported field tokens and time rule type. + */ +template +auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule /*rule*/) +{ + FieldType> shape_disp_type("shape_displacement"); + if (!field_store->hasField(shape_disp_type.name)) { + field_store->addShapeDisp(shape_disp_type); + } + + auto disp_time_rule_ptr = std::make_shared(); + FieldType> disp_type("displacement_solve_state"); + field_store->addIndependent(disp_type, disp_time_rule_ptr); + + field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); + field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); + + return PhysicsFields, H1, H1, H1>{ + field_store, FieldType>(field_store->prefix("displacement_solve_state")), + FieldType>(field_store->prefix("displacement")), + FieldType>(field_store->prefix("velocity")), + FieldType>(field_store->prefix("acceleration"))}; +} + +/** + * @brief Register all solid mechanics fields into a FieldStore (with parameters). + * + * Legacy overload that also registers parameter fields directly. + * Prefer the no-params overload + registerParameterFields for new code. + * * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). */ template -auto registerSolidMechanicsFields( - std::shared_ptr field_store, - DisplacementTimeRule /*rule*/, - FieldType... parameter_types) + requires(sizeof...(parameter_space) > 0) +auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule /*rule*/, + FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(shape_disp_type.name)) { @@ -345,13 +388,10 @@ auto registerSolidMechanicsFields( }; (prefix_param(parameter_types), ...); - return CouplingParams{ - FieldType>(field_store->prefix("displacement_solve_state")), - FieldType>(field_store->prefix("displacement")), - FieldType>(field_store->prefix("velocity")), - FieldType>(field_store->prefix("acceleration")), - parameter_types... - }; + return CouplingParams{FieldType>(field_store->prefix("displacement_solve_state")), + FieldType>(field_store->prefix("displacement")), + FieldType>(field_store->prefix("velocity")), + FieldType>(field_store->prefix("acceleration")), parameter_types...}; } /** @@ -364,15 +404,11 @@ auto registerSolidMechanicsFields( * `cycle_zero_system` is nullptr unless the rule requires an initial acceleration solve. * `end_step_systems` contains the stress output system when `enable_stress_output` is set. */ -template +template requires detail::is_coupling_params_v -auto buildSolidMechanicsSystem( - std::shared_ptr field_store, - DisplacementTimeRule /*rule*/, - const Coupling& coupling, - std::shared_ptr solver, - const SolidMechanicsOptions& options, - FieldType... parameter_types) +auto buildSolidMechanicsSystem(std::shared_ptr field_store, DisplacementTimeRule /*rule*/, + const Coupling& coupling, std::shared_ptr solver, + const SolidMechanicsOptions& options) { auto disp_time_rule_ptr = std::make_shared(); @@ -384,27 +420,17 @@ auto buildSolidMechanicsSystem( auto disp_bc = field_store->getBoundaryConditions(disp_type.name); - auto prefix_param = [&](auto& pt) { - pt.name = field_store->prefix("param_" + pt.name); - }; - (prefix_param(parameter_types), ...); - auto parameter_types_tuple = std::make_tuple(parameter_types...); - - using SystemType = SolidMechanicsSystem; + using SystemType = SolidMechanicsSystem; std::string force_name = field_store->prefix("reactions"); auto solid_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), + field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, + accel_old_type, cfs...)); }, - parameter_types_tuple); + coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); sys->disp_bc = disp_bc; @@ -420,18 +446,13 @@ auto buildSolidMechanicsSystem( accel_as_unknown.is_unknown = true; FieldType> disp_cz_input(disp_type.name); sys->cycle_zero_solid_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - cycle_zero_name, field_store->getMesh(), - field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, cfs...)); }, - parameter_types_tuple); + coupling.fields); field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); @@ -446,10 +467,10 @@ auto buildSolidMechanicsSystem( .absolute_tol = 1e-14, .max_iterations = 1000, .print_level = 0}; - auto cycle_zero_solver = std::make_shared( - buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); + auto cycle_zero_solver = + std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); - sys->cycle_zero_system = makeSubSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); + sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; } @@ -462,17 +483,13 @@ auto buildSolidMechanicsSystem( FieldType> disp_as_input(disp_type.name); std::string stress_name = field_store->prefix("stress_projection"); sys->stress_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), - field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, - velo_old_type, accel_old_type, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), + field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, + velo_old_type, accel_old_type, cfs...)); }, - parameter_types_tuple); + coupling.fields); NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, @@ -485,10 +502,10 @@ auto buildSolidMechanicsSystem( .absolute_tol = 1e-14, .max_iterations = 1000, .print_level = 0}; - auto stress_solver = std::make_shared( - buildNonlinearBlockSolver(stress_nonlin, stress_lin, *field_store->getMesh())); + auto stress_solver = + std::make_shared(buildNonlinearBlockSolver(stress_nonlin, stress_lin, *field_store->getMesh())); - sys->stress_output_system = makeSubSystem(field_store, stress_solver, {sys->stress_weak_form}); + sys->stress_output_system = makeSystem(field_store, stress_solver, {sys->stress_weak_form}); end_step_systems.push_back(sys->stress_output_system); } @@ -498,18 +515,45 @@ auto buildSolidMechanicsSystem( /** * @brief Build a SolidMechanicsSystem without coupling, assuming fields are already registered. * - * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). + * Overload for the common case of no inter-physics coupling. + * Parameters (if any) are wrapped into a CouplingParams so the system sees them. */ template -auto buildSolidMechanicsSystem( - std::shared_ptr field_store, - DisplacementTimeRule rule, - std::shared_ptr solver, - const SolidMechanicsOptions& options, - FieldType... parameter_types) +auto buildSolidMechanicsSystem(std::shared_ptr field_store, DisplacementTimeRule rule, + std::shared_ptr solver, const SolidMechanicsOptions& options, + FieldType... parameter_types) +{ + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); + return buildSolidMechanicsSystem(field_store, rule, CouplingParams{parameter_types...}, solver, options); +} + +/** + * @brief Build a SolidMechanicsSystem from variadic field packs. + * + * New API: accepts any combination of PhysicsFields and CouplingParams packs. + * The FieldStore is extracted from the PhysicsFields pack matching DisplacementTimeRule. + * Non-self packs become coupling fields; CouplingParams packs are registered as parameters. + * + * Usage: + * @code + * auto [solid, cz, end] = buildSolidMechanicsSystem( + * solver, opts, param_fields, solid_fields, thermal_fields); + * @endcode + */ +template + requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && + !(std::is_same_v, SolidMechanicsOptions> || ...)) +auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, + const FieldPacks&... field_packs) { - return buildSolidMechanicsSystem( - field_store, rule, CouplingParams<>{}, solver, options, parameter_types...); + auto field_store = detail::findFieldStore(field_packs...); + (detail::registerParamsIfNeeded(field_store, field_packs), ...); + auto coupling = detail::collectCouplingFields(field_packs...); + return buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, coupling, solver, options); } } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 21ad646ae0..20306cd205 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -56,12 +56,11 @@ namespace smith { * @tparam DisplacementTimeRule Time integration rule for displacement (must have num_states == 4). * @tparam InternalVarTimeRule Time integration rule for the internal variable (must have num_states == 2). * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: no coupling). - * @tparam parameter_space Parameter spaces for material properties. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>> struct SolidMechanicsWithInternalVarsSystem : public SystemBase { using SystemBase::SystemBase; @@ -77,8 +76,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { typename detail::AppendCouplingToParams< Coupling, Parameters, H1, H1, H1, StateSpace, - StateSpace>, - parameter_space...>::type>; + StateSpace>>::type>; // State weak form: residual for internal variable (alpha). // Inputs: alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params... @@ -87,16 +85,14 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { typename detail::AppendCouplingToParams< Coupling, Parameters, H1, H1, - H1>, - parameter_space...>::type>; + H1>>::type>; // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, alpha, coupling_fields..., params... using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< dim, H1, typename detail::AppendCouplingToParams< Coupling, - Parameters, H1, H1, StateSpace>, - parameter_space...>::type>; + Parameters, H1, H1, StateSpace>>::type>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr state_weak_form; ///< Internal variable weak form. @@ -174,7 +170,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { auto alpha_dot = 0.0 * alpha; return force_function(t_info.time(), X, u, v, a, alpha, alpha_dot, params...); }, - std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); } /** @@ -186,7 +182,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { void addBodyForce(const std::string& domain_name, BodyForceType force_function) { addBodyForceAllParams(domain_name, force_function, - std::make_index_sequence<6 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); } /** @@ -217,7 +213,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { auto alpha_dot = 0.0 * alpha; return traction_function(t_info.time(), X, n, u, v, a, alpha, alpha_dot, params...); }, - std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); } /** @@ -229,7 +225,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { void addTraction(const std::string& domain_name, TractionType traction_function) { addTractionAllParams(domain_name, traction_function, - std::make_index_sequence<6 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); } /** @@ -276,7 +272,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { return pressure * n_deformed * (1.0 / n_shape_norm); }, - std::make_index_sequence<4 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); } /** @@ -288,7 +284,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { void addPressure(const std::string& domain_name, PressureType pressure_function) { addPressureAllParams(domain_name, pressure_function, - std::make_index_sequence<6 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); } /** @@ -370,7 +366,6 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { // --------------------------------------------------------------------------- struct SolidMechanicsWithInternalVarsOptions { - bool cycle_zero_solve = false; }; /** @@ -435,7 +430,7 @@ auto registerSolidMechanicsWithInternalVarsFields( * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. */ template + typename Coupling> requires detail::is_coupling_params_v auto buildSolidMechanicsWithInternalVarsSystem( std::shared_ptr field_store, @@ -443,15 +438,8 @@ auto buildSolidMechanicsWithInternalVarsSystem( InternalVarTimeRule /*state_rule*/, const Coupling& coupling, std::shared_ptr solver, - const SolidMechanicsWithInternalVarsOptions& /*options*/, - FieldType... parameter_types) + const SolidMechanicsWithInternalVarsOptions& /*options*/) { - auto prefix_param = [&](auto& pt) { - pt.name = field_store->prefix("param_" + pt.name); - }; - (prefix_param(parameter_types), ...); - auto parameter_types_tuple = std::make_tuple(parameter_types...); - auto disp_time_rule_ptr = std::make_shared(); auto state_time_rule_ptr = std::make_shared(); @@ -468,37 +456,29 @@ auto buildSolidMechanicsWithInternalVarsSystem( auto state_bc = field_store->getBoundaryConditions(state_type.name); using SystemType = SolidMechanicsWithInternalVarsSystem; + InternalVarTimeRule, Coupling>; // Solid weak form: (u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params...) std::string solid_res_name = field_store->prefix("solid_residual"); auto solid_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, state_type, state_old_type, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), + field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, + accel_old_type, state_type, state_old_type, cfs...)); }, - parameter_types_tuple); + coupling.fields); // State weak form: (alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params...) std::string state_res_name = field_store->prefix("state_residual"); auto state_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), - field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, - disp_old_type, velo_old_type, accel_old_type, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), + field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, + disp_old_type, velo_old_type, accel_old_type, cfs...)); }, - parameter_types_tuple); + coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form, state_weak_form}); @@ -519,18 +499,14 @@ auto buildSolidMechanicsWithInternalVarsSystem( FieldType> disp_cz_input(disp_type.name); FieldType state_cz_input(state_type.name); sys->cycle_zero_solid_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - cycle_zero_name, field_store->getMesh(), - field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, state_cz_input, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + cycle_zero_name, field_store->getMesh(), + field_store->getField(accel_old_type.name).get()->space(), + field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, + accel_as_unknown, state_cz_input, cfs...)); }, - parameter_types_tuple); + coupling.fields); field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); @@ -548,7 +524,7 @@ auto buildSolidMechanicsWithInternalVarsSystem( auto cycle_zero_solver = std::make_shared( buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); - sys->cycle_zero_system = makeSubSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); + sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; } @@ -558,7 +534,8 @@ auto buildSolidMechanicsWithInternalVarsSystem( /** * @brief Build a SolidMechanicsWithInternalVarsSystem without coupling, assuming fields are already registered. * - * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). + * Overload for the common case of no inter-physics coupling. + * Parameters (if any) are wrapped into a CouplingParams so the system sees them. */ template @@ -570,8 +547,13 @@ auto buildSolidMechanicsWithInternalVarsSystem( const SolidMechanicsWithInternalVarsOptions& options, FieldType... parameter_types) { + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); return buildSolidMechanicsWithInternalVarsSystem( - field_store, disp_rule, state_rule, CouplingParams<>{}, solver, options, parameter_types...); + field_store, disp_rule, state_rule, CouplingParams{parameter_types...}, solver, options); } } // namespace smith diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 29725dd68b..40d4436e42 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -54,11 +54,10 @@ namespace smith { * @tparam StateSpace FE space for the internal variable (e.g., L2<0>). * @tparam InternalVarTimeRule Time integration rule (must have num_states == 2). * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). - * @tparam parameter_space Parameter spaces for additional material properties. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>> struct StateVariableSystem : public SystemBase { using SystemBase::SystemBase; @@ -68,8 +67,7 @@ struct StateVariableSystem : public SystemBase { /// State weak form: (alpha, alpha_old, coupling_fields..., params...) using StateWeakFormType = TimeDiscretizedWeakForm< dim, StateSpace, - typename detail::TimeRuleParamsWithCoupling::type>; + typename detail::TimeRuleParamsWithCoupling::type>; std::shared_ptr state_weak_form; ///< Internal variable weak form. std::shared_ptr state_bc; ///< Internal variable boundary conditions. @@ -159,21 +157,15 @@ auto registerStateVariableFields( * * Returns `{system, nullptr, {}}` as a tuple (no cycle-zero or end-step systems). */ -template +template requires detail::is_coupling_params_v auto buildStateVariableSystem( std::shared_ptr field_store, InternalVarTimeRule /*rule*/, const Coupling& coupling, std::shared_ptr solver, - const StateVariableOptions& /*options*/, - FieldType... parameter_types) + const StateVariableOptions& /*options*/) { - auto prefix_param = [&](auto& pt) { pt.name = field_store->prefix("param_" + pt.name); }; - (prefix_param(parameter_types), ...); - auto parameter_types_tuple = std::make_tuple(parameter_types...); - auto state_time_rule_ptr = std::make_shared(); FieldType state_type(field_store->prefix("state_solve_state"), true); @@ -181,21 +173,16 @@ auto buildStateVariableSystem( auto state_bc = field_store->getBoundaryConditions(state_type.name); - using SystemType = StateVariableSystem; + using SystemType = StateVariableSystem; std::string state_res_name = field_store->prefix("state_residual"); auto state_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), - field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, cfs..., - params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), + field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, cfs...)); }, - parameter_types_tuple); + coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{state_weak_form}); sys->state_bc = state_bc; @@ -210,7 +197,8 @@ auto buildStateVariableSystem( /** * @brief Build a StateVariableSystem without coupling. * - * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). + * Overload for the common case of no inter-physics coupling. + * Parameters (if any) are wrapped into a CouplingParams so the system sees them. */ template auto buildStateVariableSystem( @@ -220,8 +208,13 @@ auto buildStateVariableSystem( const StateVariableOptions& options, FieldType... parameter_types) { + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); return buildStateVariableSystem( - field_store, rule, CouplingParams<>{}, solver, options, parameter_types...); + field_store, rule, CouplingParams{parameter_types...}, solver, options); } } // namespace smith diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index 950a5a6c77..b08b348fde 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -82,9 +82,9 @@ struct SystemBase { const std::vector& states_for_reactions) const; }; -inline std::shared_ptr makeSubSystem(std::shared_ptr field_store, - std::shared_ptr solver, - std::vector> weak_forms) +inline std::shared_ptr makeSystem(std::shared_ptr field_store, + std::shared_ptr solver, + std::vector> weak_forms) { return std::make_shared(std::move(field_store), std::move(solver), std::move(weak_forms)); } diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp new file mode 100644 index 0000000000..24ea7d85ac --- /dev/null +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -0,0 +1,504 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include +#include "gtest/gtest.h" + +#include "smith/smith_config.hpp" +#include "smith/infrastructure/application_manager.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/physics/state/state_manager.hpp" +#include "smith/physics/mesh.hpp" + +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" +#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" +#include "smith/differentiable_numerics/differentiable_test_utils.hpp" +#include "smith/differentiable_numerics/nonlinear_solve.hpp" +#include "smith/physics/functional_objective.hpp" +#include "gretl/wang_checkpoint_strategy.hpp" + +namespace smith { + +static constexpr int dim = 3; +static constexpr int displacement_order = 1; +static constexpr int temperature_order = 1; + +template +auto greenStrain(const tensor& grad_u) +{ + return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); +} + +// Material with E parameter +struct ParameterizedGreenSaintVenantThermoelasticMaterial { + double density; + double E0; + double nu; + double C_v; + double alpha; + double theta_ref; + double kappa; + using State = Empty; + template + auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, + const tensor& grad_theta, const T5& E_param) const + { + auto E = E0 + get<0>(E_param); + const auto K = E / (3.0 * (1.0 - 2.0 * nu)); + const auto G = 0.5 * E / (1.0 + nu); + const auto Eg = greenStrain(grad_u); + const auto trEg = tr(Eg); + static constexpr auto I = Identity(); + const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; + auto F = grad_u + I; + const auto Piola = dot(F, S); + auto greenStrainRate = + 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); + const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; + const auto q0 = -kappa * grad_theta; + return smith::tuple{Piola, C_v, s0, q0}; + } +}; + +// Material without user parameters +struct ThermoelasticMaterialNoParam { + double density; + double E; + double nu; + double C_v; + double alpha; + double theta_ref; + double kappa; + using State = Empty; + template + auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, + const tensor& grad_theta) const + { + const auto K = E / (3.0 * (1.0 - 2.0 * nu)); + const auto G = 0.5 * E / (1.0 + nu); + const auto Eg = greenStrain(grad_u); + const auto trEg = tr(Eg); + static constexpr auto I = Identity(); + const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; + auto F = grad_u + I; + const auto Piola = dot(F, S); + auto greenStrainRate = + 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); + const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate); + const auto q0 = -kappa * grad_theta; + return smith::tuple{Piola, C_v, s0, q0}; + } +}; + +template +void setCoupledThermoMechanicsMaterial( + std::shared_ptr< + SolidMechanicsSystem, H1>, P...>> + solid, + std::shared_ptr, H1, H1, H1, P...>>> + thermal, + const MaterialType& material, const std::string& domain_name) +{ + auto captured_disp_rule = solid->disp_time_rule; + auto captured_temp_rule = thermal->temperature_time_rule; + + // Solid contribution: inertia + PK1 stress + solid->solid_weak_form->addBodyIntegral( + domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, + auto temperature_old, auto... params) { + auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), + get(T), get(T), params...); + return smith::tuple{get(a_current) * material.density, pk}; + }); + + // Cycle-zero + if (solid->cycle_zero_solid_weak_form) { + solid->cycle_zero_solid_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), + get(T), params...); + return smith::tuple{get(a) * material.density, pk}; + }); + } + + // Thermal contribution + thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, + auto disp_old, auto v_old, auto a_old, auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T_current), + get(T_current), params...); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); +} + +struct ThermoMechanicsMeshFixture : public testing::Test { + void SetUp() + { + datastore_ = std::make_unique(); + smith::StateManager::initialize(*datastore_, "solid"); + mesh_ = std::make_shared( + mfem::Mesh::MakeCartesian3D(24, 2, 2, mfem::Element::HEXAHEDRON, 1.2, 0.03, 0.03), "mesh", 0, 0); + mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); + } + std::unique_ptr datastore_; + std::shared_ptr mesh_; +}; + +// 1. CreateDifferentiablePhysicsAllocatesReactionInfo +TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionInfo) +{ + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 4}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + FieldType> youngs_modulus("youngs_modulus"); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + auto solid_coupling_fields = + registerSolidMechanicsFields(field_store, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store); + + auto solid_res = buildSolidMechanicsSystem( + field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts, + youngs_modulus); + auto solid = solid_res.system; + + auto thermal_res = buildThermalSystem( + field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto thermal = thermal_res.system; + + auto [coupled, coupled_cz] = combineSystems(solid, thermal); + + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); + const auto& solid_dual_space = physics->dual("reactions").space(); + const auto& solid_state_space = physics->state("displacement_solve_state").space(); + const auto& thermal_dual_space = physics->dual("thermal_flux").space(); + const auto& thermal_state_space = physics->state("temperature_solve_state").space(); + + EXPECT_EQ(physics->dualNames().size(), 2); + EXPECT_EQ(physics->dualNames()[0], "reactions"); + EXPECT_EQ(physics->dualNames()[1], "thermal_flux"); + EXPECT_EQ(solid_dual_space.GetMesh(), solid_state_space.GetMesh()); + EXPECT_STREQ(solid_dual_space.FEColl()->Name(), solid_state_space.FEColl()->Name()); + EXPECT_EQ(solid_dual_space.GetVDim(), solid_state_space.GetVDim()); + EXPECT_EQ(solid_dual_space.TrueVSize(), solid_state_space.TrueVSize()); + EXPECT_EQ(thermal_dual_space.GetMesh(), thermal_state_space.GetMesh()); + EXPECT_STREQ(thermal_dual_space.FEColl()->Name(), thermal_state_space.FEColl()->Name()); + EXPECT_EQ(thermal_dual_space.GetVDim(), thermal_state_space.GetVDim()); + EXPECT_EQ(thermal_dual_space.TrueVSize(), thermal_state_space.TrueVSize()); +} + +// 2. BackpropagateThroughPhysics +TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) +{ + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 4}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + FieldType> youngs_modulus("youngs_modulus"); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + auto thermal_coupling = registerSolidMechanicsFields(field_store, youngs_modulus); + auto solid_coupling = registerThermalFields(field_store); + + auto solid_res = buildSolidMechanicsSystem( + field_store, solid_coupling, std::make_shared(solid_block_solver), solid_opts, youngs_modulus); + auto solid = solid_res.system; + + auto thermal_res = buildThermalSystem( + field_store, thermal_coupling, std::make_shared(thermal_block_solver), thermal_opts); + auto thermal = thermal_res.system; + + auto [coupled, coupled_cz] = combineSystems(solid, thermal); + + ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + + coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [=](smith::tensor) { return 100.0; }); + + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + + solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { + auto traction = 0.0 * X; + traction[0] = -0.015; + return traction; + }); + + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); + + // Run forward + double dt = 1.0; + for (int step = 0; step < 2; ++step) { + physics->advanceTimestep(dt); + } + + auto reactions = physics->getReactionStates(); + auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); + + gretl::set_as_objective(obj); + obj.data_store().back_prop(); + + auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + EXPECT_TRUE(param_sens->Norml2() > 0.0); +} + +// 3. StaggeredBucklingChallenge +// Replaces MonolithicBucklingChallenge: verifies convergence and displacement of the staggered combined solver. +TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) +{ + constexpr double compressive_traction = 0.015; + constexpr double lateral_body_force = 2.5e-5; + constexpr double thermal_source = 1.0; + + smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, + .preconditioner = smith::Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 120, + .print_level = 0}; + smith::NonlinearSolverOptions mech_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::TrustRegion, + .relative_tol = 1e-6, + .absolute_tol = 1e-7, + .max_iterations = 25, + .print_level = 0}; + + smith::LinearSolverOptions therm_lin_opts{.linear_solver = smith::LinearSolver::GMRES, + .preconditioner = smith::Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80, + .print_level = 0}; + smith::NonlinearSolverOptions therm_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-7, + .absolute_tol = 1e-7, + .max_iterations = 12, + .max_line_search_iterations = 6, + .print_level = 0}; + + auto solid_block_solver = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + auto thermal_coupling = registerSolidMechanicsFields(field_store); + auto solid_coupling = registerThermalFields(field_store); + + auto solid_res = buildSolidMechanicsSystem( + field_store, solid_coupling, std::make_shared(solid_block_solver), solid_opts); + auto solid = solid_res.system; + + auto thermal_res = buildThermalSystem( + field_store, thermal_coupling, std::make_shared(thermal_block_solver), thermal_opts); + auto thermal = thermal_res.system; + + auto [coupled, coupled_cz] = combineSystems(solid, thermal); + + ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); + + solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + + SLIC_INFO_ROOT("Starting staggered thermo-mechanics solve"); + + double dt = 1.0; + double time = 0.0; + auto shape_disp = field_store->getShapeDisp(); + auto states = field_store->getStateFields(); + auto params = field_store->getParameterFields(); + std::vector reactions; + for (size_t step = 0; step < 1; ++step) { + std::tie(states, reactions) = + makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + time += dt; + } + + mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); + + bool staggered_solid_converged = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); + int staggered_solid_iterations = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + bool staggered_thermal_converged = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); + int staggered_thermal_iterations = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + + double staggered_lateral_deflection = 0.0; + for (int i = 1; i < final_disp.Size(); i += dim) { + staggered_lateral_deflection = std::max(staggered_lateral_deflection, std::abs(final_disp(i))); + } + + SLIC_INFO_ROOT("Staggered solid converged: " << staggered_solid_converged + << ", iterations: " << staggered_solid_iterations); + SLIC_INFO_ROOT("Staggered thermal converged: " << staggered_thermal_converged + << ", iterations: " << staggered_thermal_iterations); + SLIC_INFO_ROOT("Staggered max lateral deflection: " << staggered_lateral_deflection); + + EXPECT_TRUE(staggered_solid_converged); + EXPECT_TRUE(staggered_thermal_converged); + EXPECT_GT(staggered_lateral_deflection, 1e-5); +} + +// 4. MonolithicBucklingChallenge +// Verifies convergence and displacement of the monolithic combined solver. +TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) +{ + constexpr double compressive_traction = 0.015; + constexpr double lateral_body_force = 2.5e-5; + constexpr double thermal_source = 1.0; + + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80, + .print_level = 0}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-7, + .absolute_tol = 1e-7, + .max_iterations = 12, + .print_level = 0}; + + auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto solver_ptr = std::make_shared(block_solver); + + auto field_store = std::make_shared(mesh_, 100, ""); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + + // Notice that the block_solver is the SAME solver for the whole system + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + auto thermal_coupling = registerSolidMechanicsFields(field_store); + auto solid_coupling = registerThermalFields(field_store); + + auto solid_res = + buildSolidMechanicsSystem(field_store, solid_coupling, nullptr, solid_opts); + auto solid = solid_res.system; + + auto thermal_res = + buildThermalSystem(field_store, thermal_coupling, nullptr, thermal_opts); + auto thermal = thermal_res.system; + + auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); + + ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + + solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); + + solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + + SLIC_INFO_ROOT("Starting monolithic thermo-mechanics solve"); + + double dt = 1.0; + double time = 0.0; + auto shape_disp = field_store->getShapeDisp(); + auto states = field_store->getStateFields(); + auto params = field_store->getParameterFields(); + std::vector reactions; + for (size_t step = 0; step < 1; ++step) { + std::tie(states, reactions) = + makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + time += dt; + } + + mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); + + bool converged = block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); + int iterations = block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + + double lateral_deflection = 0.0; + for (int i = 1; i < final_disp.Size(); i += dim) { + lateral_deflection = std::max(lateral_deflection, std::abs(final_disp(i))); + } + + SLIC_INFO_ROOT("Monolithic converged: " << converged << ", iterations: " << iterations); + SLIC_INFO_ROOT("Monolithic max lateral deflection: " << lateral_deflection); + + EXPECT_TRUE(converged); + EXPECT_GT(lateral_deflection, 1e-5); +} + +} // namespace smith + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + smith::ApplicationManager applicationManager(argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp index 3c487d7514..5f08360848 100644 --- a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp @@ -37,7 +37,7 @@ auto greenStrain(const tensor& grad_u) } // Material with E parameter -struct GreenSaintVenantThermoelasticMaterial { +struct ParameterizedGreenSaintVenantThermoelasticMaterial { double density; double E0; double nu; @@ -97,16 +97,12 @@ struct ThermoelasticMaterialNoParam { } }; -template +template void setCoupledThermoMechanicsMaterial( - std::shared_ptr< - SolidMechanicsSystem, H1>, P...>> - solid, - std::shared_ptr, H1, H1, H1, P...>>> - thermal, - const MaterialType& material, const std::string& domain_name) + std::shared_ptr> solid, + std::shared_ptr> thermal, const MaterialType& material, + const std::string& domain_name) { auto captured_disp_rule = solid->disp_time_rule; auto captured_temp_rule = thermal->temperature_time_rule; @@ -179,23 +175,21 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule temp_rule; - DispRule disp_rule; - TempRule temp_rule; - auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); + auto solid_coupling_fields = + registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule, youngs_modulus); auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts, - youngs_modulus); + field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( - field_store, temp_rule, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto [thermal, thermal_cz, thermal_end_steps] = + buildThermalSystem(field_store, temp_rule, solid_coupling_fields, + std::make_shared(thermal_block_solver), thermal_opts); auto [coupled, coupled_cz] = combineSystems(solid, thermal); @@ -233,27 +227,25 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - DispRule disp_rule; - TempRule temp_rule; - auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule temp_rule; + auto solid_coupling_fields = + registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule, youngs_modulus); auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts, - youngs_modulus); + field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( - field_store, temp_rule, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto [thermal, thermal_cz, thermal_end_steps] = + buildThermalSystem(field_store, temp_rule, solid_coupling_fields, + std::make_shared(thermal_block_solver), thermal_opts); auto [coupled, coupled_cz] = combineSystems(solid, thermal); - GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -324,22 +316,20 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto field_store = std::make_shared(mesh_, 100, ""); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - DispRule disp_rule; - TempRule temp_rule; + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule temp_rule; auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule); auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( - field_store, temp_rule, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto [thermal, thermal_cz, thermal_end_steps] = + buildThermalSystem(field_store, temp_rule, solid_coupling_fields, + std::make_shared(thermal_block_solver), thermal_opts); auto [coupled, coupled_cz] = combineSystems(solid, thermal); @@ -423,24 +413,21 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto field_store = std::make_shared(mesh_, 100, ""); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - // Notice that the block_solver is the SAME solver for the whole system SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - DispRule disp_rule; - TempRule temp_rule; + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule temp_rule; auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule); auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( field_store, disp_rule, thermal_coupling_fields, nullptr, solid_opts); - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( - field_store, temp_rule, solid_coupling_fields, nullptr, thermal_opts); + auto [thermal, thermal_cz, thermal_end_steps] = + buildThermalSystem(field_store, temp_rule, solid_coupling_fields, nullptr, thermal_opts); auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); @@ -494,6 +481,72 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) EXPECT_GT(lateral_deflection, 1e-5); } +// 5. NewAPIBackpropagateThroughPhysics +// Same as test 2 but using the new variadic build API with PhysicsFields and registerParameterFields. +TEST_F(ThermoMechanicsMeshFixture, NewAPIBackpropagateThroughPhysics) +{ + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 4}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + FieldType> youngs_modulus("youngs_modulus"); + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule temp_rule; + auto param_fields = registerParameterFields(youngs_modulus); + auto solid_fields = registerSolidMechanicsFields(field_store, disp_rule); + auto thermal_fields = registerThermalFields(field_store, temp_rule); + + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); + + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, solid_fields); + + auto [coupled, coupled_cz] = combineSystems(solid, thermal); + + ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + + coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [=](smith::tensor) { return 100.0; }); + + solid->setDisplacementBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("left")); + + solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { + auto traction = 0.0 * X; + traction[0] = -0.015; + return traction; + }); + + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); + + // Run forward + double dt = 1.0; + for (int step = 0; step < 2; ++step) { + physics->advanceTimestep(dt); + } + + auto reactions = physics->getReactionStates(); + auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); + + gretl::set_as_objective(obj); + obj.data_store().back_prop(); + + auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + EXPECT_TRUE(param_sens->Norml2() > 0.0); +} + } // namespace smith int main(int argc, char* argv[]) diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 95709f562a..46af1fffe0 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -37,10 +37,9 @@ namespace smith { * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). * Coupling fields occupy leading positions in the tail after the 2 time-rule state fields, * before user parameter_space fields. - * @tparam parameter_space Finite element spaces for optional parameters. */ template , typename... parameter_space> + typename Coupling = CouplingParams<>> struct ThermalSystem : public SystemBase { using SystemBase::SystemBase; @@ -49,8 +48,7 @@ struct ThermalSystem : public SystemBase { /// Thermal weak form: (temp, temp_old, coupling_fields..., params...) using ThermalWeakFormType = TimeDiscretizedWeakForm< dim, H1, - typename detail::TimeRuleParamsWithCoupling, Coupling, - parameter_space...>::type>; + typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; std::shared_ptr thermal_weak_form; ///< Thermal weak form. std::shared_ptr temperature_bc; ///< Temperature boundary conditions. @@ -109,7 +107,7 @@ struct ThermalSystem : public SystemBase { template void addHeatSource(const std::string& domain_name, HeatSourceType source_function) { - addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence<2 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence<2 + Coupling::num_coupling_fields>{}); } /** @@ -140,7 +138,17 @@ struct ThermalSystem : public SystemBase { template void addHeatFlux(const std::string& boundary_name, HeatFluxType flux_function) { - addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence<2 + Coupling::num_coupling_fields + sizeof...(parameter_space)>{}); + addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence<2 + Coupling::num_coupling_fields>{}); + } + + /// Set zero-temperature Dirichlet BC. + void setTemperatureBC(const Domain& domain) { temperature_bc->template setFixedScalarBCs(domain); } + + /// Set temperature BC with a prescribed function. + template + void setTemperatureBC(const Domain& domain, AppliedTemperatureFunction f) + { + temperature_bc->template setScalarBCs(domain, f); } private: @@ -166,9 +174,39 @@ struct ThermalOptions { * Phase 1 of the two-phase initialization. Pass an instance of the desired time integration rule * so its type is deduced; only `` need be specified explicitly. * + * @return PhysicsFields carrying the exported field tokens and time rule type. + */ +template +auto registerThermalFields( + std::shared_ptr field_store, + TemperatureTimeRule /*rule*/) +{ + FieldType> shape_disp_type("shape_displacement"); + if (!field_store->hasField(shape_disp_type.name)) { + field_store->addShapeDisp(shape_disp_type); + } + + auto temperature_time_rule_ptr = std::make_shared(); + FieldType> temperature_type("temperature_solve_state"); + field_store->addIndependent(temperature_type, temperature_time_rule_ptr); + field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); + + return PhysicsFields, H1>{ + field_store, + FieldType>(field_store->prefix("temperature_solve_state")), + FieldType>(field_store->prefix("temperature"))}; +} + +/** + * @brief Register all thermal fields into a FieldStore (with parameters). + * + * Legacy overload that also registers parameter fields directly. + * Prefer the no-params overload + registerParameterFields for new code. + * * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). */ template + requires(sizeof...(parameter_space) > 0) auto registerThermalFields( std::shared_ptr field_store, TemperatureTimeRule /*rule*/, @@ -207,15 +245,14 @@ auto registerThermalFields( * `cycle_zero_system` is always nullptr (thermal has no cycle-zero solve). * `end_step_systems` is always empty. */ -template +template requires detail::is_coupling_params_v auto buildThermalSystem( std::shared_ptr field_store, TemperatureTimeRule /*rule*/, const Coupling& coupling, std::shared_ptr solver, - const ThermalOptions& /*options*/, - FieldType... parameter_types) + const ThermalOptions& /*options*/) { auto temperature_time_rule_ptr = std::make_shared(); @@ -225,28 +262,18 @@ auto buildThermalSystem( auto temperature_bc = field_store->getBoundaryConditions(temperature_type.name); - auto prefix_param = [&](auto& pt) { - pt.name = field_store->prefix("param_" + pt.name); - }; - (prefix_param(parameter_types), ...); - auto parameter_types_tuple = std::make_tuple(parameter_types...); - - using SystemType = ThermalSystem; + using SystemType = ThermalSystem; std::string thermal_flux_name = field_store->prefix("thermal_flux"); auto thermal_weak_form = std::apply( - [&](auto&... params) { - return std::apply( - [&](auto&... cfs) { - return std::make_shared( - thermal_flux_name, field_store->getMesh(), - field_store->getField(temperature_type.name).get()->space(), - field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, - temperature_old_type, cfs..., params...)); - }, - coupling.fields); + [&](auto&... cfs) { + return std::make_shared( + thermal_flux_name, field_store->getMesh(), + field_store->getField(temperature_type.name).get()->space(), + field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, + temperature_old_type, cfs...)); }, - parameter_types_tuple); + coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{thermal_weak_form}); @@ -260,7 +287,8 @@ auto buildThermalSystem( /** * @brief Build a ThermalSystem without coupling, assuming fields are already registered. * - * Overload for the common case of no inter-physics coupling (Coupling defaults to CouplingParams<>). + * Overload for the common case of no inter-physics coupling. + * Parameters (if any) are wrapped into a CouplingParams so the system sees them. */ template auto buildThermalSystem( @@ -270,8 +298,42 @@ auto buildThermalSystem( const ThermalOptions& options, FieldType... parameter_types) { + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); + return buildThermalSystem( + field_store, rule, CouplingParams{parameter_types...}, solver, options); +} + +/** + * @brief Build a ThermalSystem from variadic field packs. + * + * New API: accepts any combination of PhysicsFields and CouplingParams packs. + * The FieldStore is extracted from the PhysicsFields pack matching TemperatureTimeRule. + * Non-self packs become coupling fields; CouplingParams packs are registered as parameters. + * + * Usage: + * @code + * auto [thermal, cz, end] = buildThermalSystem( + * solver, opts, param_fields, thermal_fields, solid_fields); + * @endcode + */ +template + requires(sizeof...(FieldPacks) > 0 && + (detail::is_physics_fields_v || ...) && + !(std::is_same_v, ThermalOptions> || ...)) +auto buildThermalSystem( + std::shared_ptr solver, + const ThermalOptions& options, + const FieldPacks&... field_packs) +{ + auto field_store = detail::findFieldStore(field_packs...); + (detail::registerParamsIfNeeded(field_store, field_packs), ...); + auto coupling = detail::collectCouplingFields(field_packs...); return buildThermalSystem( - field_store, rule, CouplingParams<>{}, solver, options, parameter_types...); + field_store, TemperatureTimeRule{}, coupling, solver, options); } } // namespace smith From dbfc11c217b228946603d1f2b1f9dc8cd8224aa2 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 17 Apr 2026 08:25:44 -0600 Subject: [PATCH 19/67] Working on simpler build methods. --- .../solid_mechanics_system.hpp | 62 +++++++++++-- .../differentiable_numerics/system_base.hpp | 16 ++++ .../tests/CMakeLists.txt | 1 + .../tests/test_combined_thermo_mechanics.cpp | 10 +-- .../tests/test_coupled_thermo_mechanics.cpp | 88 ++++++++++--------- .../tests/test_solid_dynamics.cpp | 29 +++--- .../tests/test_thermal_static.cpp | 24 +++-- .../thermal_system.hpp | 68 ++++++++++---- 8 files changed, 205 insertions(+), 93 deletions(-) diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 58e6da2225..0232381f49 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -356,6 +356,32 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, Displ FieldType>(field_store->prefix("acceleration"))}; } +/** + * @brief Register all solid mechanics fields (no rule instance — Rule given as explicit template param). + * + * Preferred form: Rule is deduced from the PhysicsFields type rather than a runtime instance. + * Equivalent to the rule-instance overload but avoids requiring a dummy rule object. + */ +template +auto registerSolidMechanicsFields(std::shared_ptr field_store) +{ + return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}); +} + +/** + * @brief Register solid mechanics fields with parameters (no rule instance). + * + * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). + */ +template + requires(sizeof...(parameter_space) > 0) +auto registerSolidMechanicsFields(std::shared_ptr field_store, + FieldType... parameter_types) +{ + return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}, + std::move(parameter_types)...); +} + /** * @brief Register all solid mechanics fields into a FieldStore (with parameters). * @@ -513,22 +539,40 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace } /** - * @brief Build a SolidMechanicsSystem without coupling, assuming fields are already registered. + * @brief Build a SolidMechanicsSystem from a coupling pack and optional parameter fields. + * + * Preferred API: Rule is given as explicit template param (no rule instance needed). + * The coupling argument carries the fields borrowed from another physics (or CouplingParams<> for none). + * Additional parameter_types are registered and appended after the coupling fields. + * + * Returns a @c SystemBuildResult so callers can write @c res.system, @c res.cycle_zero_system, etc. * - * Overload for the common case of no inter-physics coupling. - * Parameters (if any) are wrapped into a CouplingParams so the system sees them. + * Usage: + * @code + * auto res = buildSolidMechanicsSystem( + * field_store, thermal_fields, solver, opts, youngs_modulus); + * auto solid = res.system; + * @endcode */ -template -auto buildSolidMechanicsSystem(std::shared_ptr field_store, DisplacementTimeRule rule, +template + requires detail::is_coupling_params_v +auto buildSolidMechanicsSystem(std::shared_ptr field_store, const Coupling& coupling, std::shared_ptr solver, const SolidMechanicsOptions& options, FieldType... parameter_types) { - auto prefix_param = [&](auto& pt) { + ([&](auto& pt) { pt.name = "param_" + pt.name; field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - return buildSolidMechanicsSystem(field_store, rule, CouplingParams{parameter_types...}, solver, options); + }(parameter_types), + ...); + + auto combined = std::apply( + [&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); + + auto [sys, cz, ends] = + buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, combined, solver, options); + using SysType = typename decltype(sys)::element_type; + return SystemBuildResult{std::move(sys), std::move(cz), std::move(ends)}; } /** diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index b08b348fde..b8bcbed465 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -89,4 +89,20 @@ inline std::shared_ptr makeSystem(std::shared_ptr field_ return std::make_shared(std::move(field_store), std::move(solver), std::move(weak_forms)); } +/** + * @brief Return type for physics system build functions. + * + * Replaces the previous std::tuple return so callers can write + * @code + * auto res = buildSolidMechanicsSystem(...); + * auto system = res.system; + * @endcode + */ +template +struct SystemBuildResult { + std::shared_ptr system; + std::shared_ptr cycle_zero_system; + std::vector> end_step_systems; +}; + } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index e99a7e1a24..a7b2e2a375 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -13,6 +13,7 @@ set(differentiable_numerics_test_source test_explicit_dynamics.cpp test_porous_heat_sink.cpp test_coupled_thermo_mechanics.cpp + test_combined_thermo_mechanics.cpp test_thermal_static.cpp test_solid_static_with_internal_vars.cpp test_multiphysics_time_integrator.cpp diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 24ea7d85ac..f800560e06 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -99,12 +99,12 @@ struct ThermoelasticMaterialNoParam { template void setCoupledThermoMechanicsMaterial( - std::shared_ptr< - SolidMechanicsSystem, H1>, P...>> + std::shared_ptr, H1, P...>>> solid, - std::shared_ptr, H1, H1, H1, P...>>> + std::shared_ptr, H1, H1, + H1, P...>>> thermal, const MaterialType& material, const std::string& domain_name) { diff --git a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp index 5f08360848..52b0343044 100644 --- a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp @@ -175,21 +175,22 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule temp_rule; auto solid_coupling_fields = - registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule, youngs_modulus); + registerSolidMechanicsFields(field_store, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store, youngs_modulus); - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); + auto solid_res = buildSolidMechanicsSystem( + field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); + auto solid = solid_res.system; - auto [thermal, thermal_cz, thermal_end_steps] = - buildThermalSystem(field_store, temp_rule, solid_coupling_fields, - std::make_shared(thermal_block_solver), thermal_opts); + auto thermal_res = buildThermalSystem( + field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto thermal = thermal_res.system; auto [coupled, coupled_cz] = combineSystems(solid, thermal); @@ -227,21 +228,22 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule temp_rule; auto solid_coupling_fields = - registerSolidMechanicsFields(field_store, disp_rule, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule, youngs_modulus); + registerSolidMechanicsFields(field_store, youngs_modulus); + auto thermal_coupling_fields = registerThermalFields(field_store, youngs_modulus); - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); + auto solid_res = buildSolidMechanicsSystem( + field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); + auto solid = solid_res.system; - auto [thermal, thermal_cz, thermal_end_steps] = - buildThermalSystem(field_store, temp_rule, solid_coupling_fields, - std::make_shared(thermal_block_solver), thermal_opts); + auto thermal_res = buildThermalSystem( + field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto thermal = thermal_res.system; auto [coupled, coupled_cz] = combineSystems(solid, thermal); @@ -316,20 +318,21 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto field_store = std::make_shared(mesh_, 100, ""); + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule temp_rule; - auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule); - auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); + auto solid_coupling_fields = registerSolidMechanicsFields(field_store); + auto thermal_coupling_fields = registerThermalFields(field_store); - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - field_store, disp_rule, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); + auto solid_res = buildSolidMechanicsSystem( + field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); + auto solid = solid_res.system; - auto [thermal, thermal_cz, thermal_end_steps] = - buildThermalSystem(field_store, temp_rule, solid_coupling_fields, - std::make_shared(thermal_block_solver), thermal_opts); + auto thermal_res = buildThermalSystem( + field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); + auto thermal = thermal_res.system; auto [coupled, coupled_cz] = combineSystems(solid, thermal); @@ -414,20 +417,21 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto field_store = std::make_shared(mesh_, 100, ""); // Notice that the block_solver is the SAME solver for the whole system - + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule temp_rule; - auto solid_coupling_fields = registerSolidMechanicsFields(field_store, disp_rule); - auto thermal_coupling_fields = registerThermalFields(field_store, temp_rule); + auto solid_coupling_fields = registerSolidMechanicsFields(field_store); + auto thermal_coupling_fields = registerThermalFields(field_store); - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - field_store, disp_rule, thermal_coupling_fields, nullptr, solid_opts); + auto solid_res = buildSolidMechanicsSystem( + field_store, thermal_coupling_fields, nullptr, solid_opts); + auto solid = solid_res.system; - auto [thermal, thermal_cz, thermal_end_steps] = - buildThermalSystem(field_store, temp_rule, solid_coupling_fields, nullptr, thermal_opts); + auto thermal_res = buildThermalSystem( + field_store, solid_coupling_fields, nullptr, thermal_opts); + auto thermal = thermal_res.system; auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); @@ -497,19 +501,19 @@ TEST_F(ThermoMechanicsMeshFixture, NewAPIBackpropagateThroughPhysics) auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule temp_rule; auto param_fields = registerParameterFields(youngs_modulus); - auto solid_fields = registerSolidMechanicsFields(field_store, disp_rule); - auto thermal_fields = registerThermalFields(field_store, temp_rule); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto thermal_fields = registerThermalFields(field_store); - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, solid_fields); auto [coupled, coupled_cz] = combineSystems(solid, thermal); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index fbb8dc9cd6..2732b57c5d 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -105,14 +105,16 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto coupled_solver = std::make_shared(solid_block_solver); auto field_store = std::make_shared(mesh, 100, ""); - ImplicitNewmarkSecondOrderTimeIntegrationRule time_rule; - registerSolidMechanicsFields( - field_store, time_rule, FieldType("bulk"), FieldType("shear") - ); + using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + registerSolidMechanicsFields( + field_store, FieldType("bulk"), FieldType("shear")); - auto [system, cz_sys, end_steps] = buildSolidMechanicsSystem( - field_store, time_rule, coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, + auto solid_res = buildSolidMechanicsSystem( + field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, FieldType("bulk"), FieldType("shear")); + auto system = solid_res.system; + auto cz_sys = solid_res.cycle_zero_system; + auto end_steps = solid_res.end_step_systems; static constexpr double gravity = -9.0; @@ -210,18 +212,19 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr solid_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto time_rule = ImplicitNewmarkSecondOrderTimeIntegrationRule(); - auto coupled_solver = std::make_shared(solid_block_solver); auto field_store = std::make_shared(mesh, 100, physics_name); - registerSolidMechanicsFields( - field_store, time_rule, FieldType("bulk"), FieldType("shear") - ); + using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + registerSolidMechanicsFields( + field_store, FieldType("bulk"), FieldType("shear")); - auto [system, cz_sys, end_steps] = buildSolidMechanicsSystem( - field_store, time_rule, coupled_solver, SolidMechanicsOptions{}, + auto solid_res = buildSolidMechanicsSystem( + field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{}, FieldType("bulk"), FieldType("shear")); + auto system = solid_res.system; + auto cz_sys = solid_res.cycle_zero_system; + auto end_steps = solid_res.end_step_systems; auto physics = makeDifferentiablePhysics(system, physics_name, cz_sys, end_steps); auto bcs = system->disp_bc; diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 2af73c490a..eb3e9a57a9 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -53,11 +53,14 @@ struct ThermalStaticFixture : public testing::Test { auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, ""); - QuasiStaticFirstOrderTimeIntegrationRule temp_rule; - registerThermalFields<2, temp_order>(field_store, temp_rule); + using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; + registerThermalFields<2, temp_order, TempRule>(field_store); - auto [thermal_system, cz_sys, end_steps] = buildThermalSystem<2, temp_order>( - field_store, temp_rule, coupled_solver, ThermalOptions{}); + auto thermal_res = buildThermalSystem<2, temp_order, TempRule>( + field_store, CouplingParams<>{}, coupled_solver, ThermalOptions{}); + auto thermal_system = thermal_res.system; + auto cz_sys = thermal_res.cycle_zero_system; + auto end_steps = thermal_res.end_step_systems; double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -149,11 +152,14 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) FieldType> conductivity_param("conductivity"); auto field_store = std::make_shared(mesh, 100, ""); - QuasiStaticFirstOrderTimeIntegrationRule temp_rule; - registerThermalFields<2, 1>(field_store, temp_rule, conductivity_param); - - auto [thermal_system, cz_sys, end_steps] = buildThermalSystem<2, 1>( - field_store, temp_rule, coupled_solver, ThermalOptions{}, conductivity_param); + using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; + registerThermalFields<2, 1, TempRule>(field_store, conductivity_param); + + auto thermal_res = buildThermalSystem<2, 1, TempRule>( + field_store, CouplingParams<>{}, coupled_solver, ThermalOptions{}, conductivity_param); + auto thermal_system = thermal_res.system; + auto cz_sys = thermal_res.cycle_zero_system; + auto end_steps = thermal_res.end_step_systems; // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 46af1fffe0..c98bb561b2 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -197,6 +197,29 @@ auto registerThermalFields( FieldType>(field_store->prefix("temperature"))}; } +/** + * @brief Register thermal fields (no rule instance — Rule given as explicit template param). + */ +template +auto registerThermalFields(std::shared_ptr field_store) +{ + return registerThermalFields(field_store, TemperatureTimeRule{}); +} + +/** + * @brief Register thermal fields with parameters (no rule instance). + * + * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). + */ +template + requires(sizeof...(parameter_space) > 0) +auto registerThermalFields(std::shared_ptr field_store, + FieldType... parameter_types) +{ + return registerThermalFields(field_store, TemperatureTimeRule{}, + std::move(parameter_types)...); +} + /** * @brief Register all thermal fields into a FieldStore (with parameters). * @@ -285,26 +308,41 @@ auto buildThermalSystem( } /** - * @brief Build a ThermalSystem without coupling, assuming fields are already registered. + * @brief Build a ThermalSystem from a coupling pack and optional parameter fields. + * + * Preferred API: Rule is given as explicit template param (no rule instance needed). + * The coupling argument carries the fields borrowed from another physics (or CouplingParams<> for none). + * Additional parameter_types are registered and appended after the coupling fields. + * + * Returns a @c SystemBuildResult so callers can write @c res.system, @c res.cycle_zero_system, etc. * - * Overload for the common case of no inter-physics coupling. - * Parameters (if any) are wrapped into a CouplingParams so the system sees them. + * Usage: + * @code + * auto res = buildThermalSystem( + * field_store, solid_fields, solver, opts); + * auto thermal = res.system; + * @endcode */ -template -auto buildThermalSystem( - std::shared_ptr field_store, - TemperatureTimeRule rule, - std::shared_ptr solver, - const ThermalOptions& options, - FieldType... parameter_types) +template + requires detail::is_coupling_params_v +auto buildThermalSystem(std::shared_ptr field_store, const Coupling& coupling, + std::shared_ptr solver, const ThermalOptions& options, + FieldType... parameter_types) { - auto prefix_param = [&](auto& pt) { + ([&](auto& pt) { pt.name = "param_" + pt.name; field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - return buildThermalSystem( - field_store, rule, CouplingParams{parameter_types...}, solver, options); + }(parameter_types), + ...); + + auto combined = std::apply( + [&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); + + auto [sys, cz, ends] = + buildThermalSystem(field_store, TemperatureTimeRule{}, combined, solver, options); + using SysType = typename decltype(sys)::element_type; + return SystemBuildResult{std::move(sys), std::move(cz), std::move(ends)}; } /** From cad8ebb165e2bc1f3f778324d41c77d42decbdef Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 17 Apr 2026 11:24:19 -0600 Subject: [PATCH 20/67] trying to test 3 systems, also fix up some solve issues identifies as we go along. workout some stress output details. --- .../nonlinear_block_solver.cpp | 12 +- .../solid_mechanics_system.hpp | 23 +- ...id_mechanics_with_internal_vars_system.hpp | 2 +- .../tests/CMakeLists.txt | 1 + .../tests/test_combined_thermo_mechanics.cpp | 112 ++++----- .../tests/test_coupled_thermo_mechanics.cpp | 50 +--- .../test_thermo_mechanics_three_system.cpp | 221 ++++++++++++++++++ .../thermal_system.hpp | 4 +- .../thermo_mechanical_system.hpp | 100 ++++++++ 9 files changed, 413 insertions(+), 112 deletions(-) create mode 100644 src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp create mode 100644 src/smith/differentiable_numerics/thermo_mechanical_system.hpp diff --git a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp index d2bdc36189..b0ec967b1c 100644 --- a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp +++ b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp @@ -89,9 +89,17 @@ std::shared_ptr NonlinearBlockSolver::cloneFresh() const nonlinear_opts.relative_tol, nonlinear_opts, linear_opts); } -void NonlinearBlockSolver::completeSetup(const std::vector&) +void NonlinearBlockSolver::completeSetup(const std::vector& us) { - // TODO: eventually may need something like: initializeSolver(&nonlinear_solver_->preconditioner(), u); + if (us.empty() || !nonlinear_solver_) return; + if (!retained_linear_options_ || retained_linear_options_->preconditioner == Preconditioner::None) return; + + // Pick the field with the highest vdim (typically the displacement field for vector problems). + const FieldT* best = &us[0]; + for (const auto& u : us) { + if (u.space().GetVDim() > best->space().GetVDim()) best = &u; + } + initializeSolver(&nonlinear_solver_->preconditioner(), *best); } ConvergenceStatus NonlinearBlockSolver::convergenceStatus(double tolerance_multiplier, diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 0232381f49..a1b4ed6b1d 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -75,6 +75,7 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr stress_weak_form; ///< Stress projection weak form (nullptr if disabled). std::shared_ptr stress_output_system; ///< Post-solve system for stress projection. + bool output_cauchy_stress = false; ///< Project Cauchy stress instead of PK1 when true. /** * @brief Set the material model for a domain, defining integrals for the solid weak form. @@ -113,6 +114,7 @@ struct SolidMechanicsSystem : public SystemBase { // variable so the solver builds the mass matrix against it, and the (- pk_stress) term // becomes the RHS. if (stress_weak_form) { + bool do_cauchy = this->output_cauchy_stress; stress_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto stress, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); @@ -120,9 +122,18 @@ struct SolidMechanicsSystem : public SystemBase { typename MaterialType::State state; auto pk_stress = material(state, get(u_current), params...); - // Flatten dim x dim stress tensor into dim*dim vector, subtract from current stress unknown - auto pk_flat = make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); - return smith::tuple{get(stress) - pk_flat, tensor{}}; + // Flatten the chosen stress into a dim*dim vector and subtract from the unknown. + auto flat_stress = [&]() { + if (do_cauchy) { + static constexpr auto I_ = Identity(); + auto F = get(u_current) + I_; + auto J = det(F); + auto sigma = (1.0 / J) * dot(pk_stress, transpose(F)); + return make_tensor([&](int i) { return sigma[i / dim][i % dim]; }); + } + return make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); + }(); + return smith::tuple{get(stress) - flat_stress, tensor{}}; }); } } @@ -323,6 +334,7 @@ struct SolidMechanicsSystem : public SystemBase { struct SolidMechanicsOptions { bool enable_stress_output = false; + bool output_cauchy_stress = false; ///< When true, project Cauchy stress (sigma) instead of PK1 (P). }; /** @@ -337,7 +349,7 @@ template auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule /*rule*/) { FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(shape_disp_type.name)) { + if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { field_store->addShapeDisp(shape_disp_type); } @@ -396,7 +408,7 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, Displ FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(shape_disp_type.name)) { + if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { field_store->addShapeDisp(shape_disp_type); } @@ -462,6 +474,7 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace sys->disp_bc = disp_bc; sys->disp_time_rule = disp_time_rule_ptr; sys->solid_weak_form = solid_weak_form; + sys->output_cauchy_stress = options.output_cauchy_stress; std::shared_ptr cycle_zero_system; std::vector> end_step_systems; diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 20306cd205..979bf4fd66 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -384,7 +384,7 @@ auto registerSolidMechanicsWithInternalVarsFields( FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(shape_disp_type.name)) { + if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { field_store->addShapeDisp(shape_disp_type); } diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index a7b2e2a375..2fa5707c45 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -17,6 +17,7 @@ set(differentiable_numerics_test_source test_thermal_static.cpp test_solid_static_with_internal_vars.cpp test_multiphysics_time_integrator.cpp + test_thermo_mechanics_three_system.cpp ) smith_add_tests( SOURCES ${differentiable_numerics_test_source} diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index f800560e06..ad8892635c 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -17,6 +17,7 @@ #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" @@ -97,59 +98,6 @@ struct ThermoelasticMaterialNoParam { } }; -template -void setCoupledThermoMechanicsMaterial( - std::shared_ptr, H1, P...>>> - solid, - std::shared_ptr, H1, H1, - H1, P...>>> - thermal, - const MaterialType& material, const std::string& domain_name) -{ - auto captured_disp_rule = solid->disp_time_rule; - auto captured_temp_rule = thermal->temperature_time_rule; - - // Solid contribution: inertia + PK1 stress - solid->solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, - auto temperature_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), - get(T), get(T), params...); - return smith::tuple{get(a_current) * material.density, pk}; - }); - - // Cycle-zero - if (solid->cycle_zero_solid_weak_form) { - solid->cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), - get(T), params...); - return smith::tuple{get(a) * material.density, pk}; - }); - } - - // Thermal contribution - thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, - auto disp_old, auto v_old, auto a_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T_current), - get(T_current), params...); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; - }); -} - struct ThermoMechanicsMeshFixture : public testing::Test { void SetUp() { @@ -494,6 +442,64 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) EXPECT_GT(lateral_deflection, 1e-5); } +// Simple linear elastic material for stress output test (file scope — templates cannot be in local classes) +struct StressOutputLinearElastic { + double density = 1.0; + using State = Empty; + template + auto operator()(State&, DerivType grad_u) const + { + double E = 100.0, nu = 0.25; + double lam = E * nu / ((1 + nu) * (1 - 2 * nu)); + double mu = E / (2 * (1 + nu)); + auto eps = sym(grad_u); + static constexpr auto I = Identity(); + return lam * tr(eps) * I + 2.0 * mu * eps; + } +}; + +// 5. CauchyStressOutput +// Verifies that enable_stress_output + output_cauchy_stress writes a non-zero stress field. +TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) +{ + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 6}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + + SolidMechanicsOptions solid_opts{.enable_stress_output = true, .output_cauchy_stress = true}; + + registerSolidMechanicsFields(field_store); + auto [sys, cz, end_steps] = buildSolidMechanicsSystem( + field_store, CouplingParams<>{}, std::make_shared(solid_block_solver), solid_opts); + + sys->setMaterial(StressOutputLinearElastic{}, mesh_->entireBodyName()); + + sys->setDisplacementBC(mesh_->domain("left")); + sys->addTraction("right", [](double, auto X, auto, auto, auto, auto) { + auto t = 0.0 * X; + t[0] = -0.01; + return t; + }); + + auto physics = makeDifferentiablePhysics(sys, "cauchy_physics", cz, end_steps); + physics->advanceTimestep(1.0); + + // The stress projection system should have run and produced a non-zero stress field. + ASSERT_FALSE(end_steps.empty()) << "Stress output system should be present"; + auto states = physics->getFieldStates(); + size_t stress_idx = field_store->getFieldIndex("stress_solve_state"); + double stress_norm = norm(*states[stress_idx].get()); + EXPECT_GT(stress_norm, 1e-8) << "Cauchy stress field should be non-zero after deformation"; +} + } // namespace smith int main(int argc, char* argv[]) diff --git a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp index 52b0343044..57ffcb3ff6 100644 --- a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp @@ -17,6 +17,7 @@ #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" @@ -97,55 +98,6 @@ struct ThermoelasticMaterialNoParam { } }; -template -void setCoupledThermoMechanicsMaterial( - std::shared_ptr> solid, - std::shared_ptr> thermal, const MaterialType& material, - const std::string& domain_name) -{ - auto captured_disp_rule = solid->disp_time_rule; - auto captured_temp_rule = thermal->temperature_time_rule; - - // Solid contribution: inertia + PK1 stress - solid->solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, - auto temperature_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), - get(T), get(T), params...); - return smith::tuple{get(a_current) * material.density, pk}; - }); - - // Cycle-zero - if (solid->cycle_zero_solid_weak_form) { - solid->cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), - get(T), params...); - return smith::tuple{get(a) * material.density, pk}; - }); - } - - // Thermal contribution - thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, - auto disp_old, auto v_old, auto a_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T_current), - get(T_current), params...); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; - }); -} - struct ThermoMechanicsMeshFixture : public testing::Test { void SetUp() { diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp new file mode 100644 index 0000000000..40a1fa4a15 --- /dev/null +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -0,0 +1,221 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file test_thermo_mechanics_three_system.cpp + * @brief Tests 3-system coupling: SolidMechanicsSystem + ThermalSystem + StateVariableSystem. + * + * Validates N>2 system coupling end-to-end via combineSystems. + * + * Layout: + * - Solid: receives temperature coupling (1-way: temperature affects elastic modulus via expansion). + * - Thermal: standalone heat diffusion with a heat source. + * - State (damage): receives solid displacement coupling; damage evolves with strain norm. + */ + +#include +#include "gtest/gtest.h" + +#include "smith/smith_config.hpp" +#include "smith/infrastructure/application_manager.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/physics/state/state_manager.hpp" +#include "smith/physics/mesh.hpp" + +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/state_variable_system.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" +#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" +#include "smith/differentiable_numerics/differentiable_test_utils.hpp" + +namespace smith { + +static constexpr int dim3 = 3; +static constexpr int disp_ord = 1; +static constexpr int temp_ord = 1; +static constexpr int state_ord = 0; +using ThreeSystemStateSpace = L2; + +struct ThreeSystemMeshFixture : public testing::Test { + void SetUp() override + { + datastore_ = std::make_unique(); + smith::StateManager::initialize(*datastore_, "three_system"); + mesh_ = std::make_shared( + mfem::Mesh::MakeCartesian3D(4, 2, 2, mfem::Element::HEXAHEDRON, 1.0, 0.1, 0.1), "mesh", 0, 0); + mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); + } + + std::unique_ptr datastore_; + std::shared_ptr mesh_; +}; + +// 3-system staggered: solid (with thermal coupling) + thermal + state (with displacement coupling). +TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) +{ + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, + .relative_tol = 1e-8, + .absolute_tol = 1e-10, + .max_iterations = 200, + .print_level = 0}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-7, + .absolute_tol = 1e-8, + .max_iterations = 20, + .max_line_search_iterations = 6, + .print_level = 0}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto state_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, "three"); + + using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + using StateRule = BackwardEulerFirstOrderTimeIntegrationRule; + + DispRule disp_rule; + TempRule temp_rule; + StateRule state_rule; + + // Phase 1: register all fields. + // registerSolidMechanicsFields must come before registerThermalFields to get + // the displacement field tokens available for thermal coupling. + auto solid_exported = registerSolidMechanicsFields(field_store); + auto thermal_exported = registerThermalFields(field_store, temp_rule); + registerStateVariableFields(field_store, state_rule); + + // Phase 2: build each system. + + // Solid receives thermal coupling (temperature_solve_state, temperature). + auto [solid, solid_cz, solid_end] = buildSolidMechanicsSystem( + field_store, thermal_exported, std::make_shared(solid_block_solver), SolidMechanicsOptions{}); + + // Thermal is standalone (no coupling back from solid for this test). + auto [thermal, thermal_cz, thermal_end] = buildThermalSystem( + field_store, CouplingParams<>{}, std::make_shared(thermal_block_solver), ThermalOptions{}); + + // StateVariable receives solid displacement coupling (4 fields: disp_ss, displacement, velocity, acceleration). + auto [state_sys, state_cz, state_end] = buildStateVariableSystem( + field_store, state_rule, solid_exported, std::make_shared(state_block_solver), + StateVariableOptions{}); + + // Phase 3: register material integrands. + + // Solid: thermoelastic — temperature drives isotropic expansion. + // Lambda args: (t_info, X, u, u_old, v_old, a_old, temperature_ss, temperature_old) + { + auto captured_disp_rule = solid->disp_time_rule; + auto captured_temp_rule = thermal->temperature_time_rule; + solid->solid_weak_form->addBodyIntegral( + mesh_->entireBodyName(), + [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature_ss, + auto temperature_old) { + auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto T = captured_temp_rule->value(t_info, temperature_ss, temperature_old); + + double E = 100.0, nu = 0.25, alpha_th = 0.001, density = 1.0; + double lam = E * nu / ((1 + nu) * (1 - 2 * nu)); + double mu = E / (2 * (1 + nu)); + static constexpr auto I = Identity(); + auto eps = sym(get(u_c)); + auto T_val = get(T); + auto sigma = lam * (tr(eps) - 3.0 * alpha_th * T_val) * I + 2.0 * mu * eps; + return smith::tuple{get(a_c) * density, sigma}; + }); + } + + // Thermal: simple conduction with constant heat source. + // Lambda args: (t_info, X, T, T_old) + { + auto captured_temp_rule = thermal->temperature_time_rule; + const double kappa = 0.05, C_v = 1.0, heat_source = 0.5; + thermal->thermal_weak_form->addBodyIntegral( + mesh_->entireBodyName(), [=](auto t_info, auto /*X*/, auto T, auto T_old) { + auto [T_c, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto q = kappa * get(T_c); + return smith::tuple{C_v * get(T_dot) - heat_source, -q}; + }); + } + + // State (damage): evolves as d_dot = (1 - d) * eps_norm. + // addStateEvolution lambda args: (t_info, alpha_val, alpha_dot, disp_ss, displacement, velocity, acceleration) + { + auto captured_disp_rule = solid->disp_time_rule; + state_sys->addStateEvolution( + mesh_->entireBodyName(), + [=](auto t_info, auto alpha_val, auto alpha_dot, auto u_ss, auto u, auto v, auto a) { + auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u_ss, u, v, a); + auto eps = sym(get(u_c)); + using std::sqrt; + auto eps_norm = sqrt(inner(eps, eps) + 1e-16); + return alpha_dot - (1.0 - alpha_val) * eps_norm; + }); + } + + // Phase 4: boundary conditions. + solid->setDisplacementBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("left")); + + // Compressive traction on right face. + solid->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto, auto) { + auto t = 0.0 * X; + t[0] = -0.005; + return t; + }); + + // Phase 5: combine and solve. + auto [combined, combined_cz] = combineSystems(solid, thermal, state_sys); + + double dt = 1.0, time = 0.0; + auto shape_disp = field_store->getShapeDisp(); + auto states = field_store->getStateFields(); + auto params = field_store->getParameterFields(); + std::vector reactions; + + for (size_t step = 0; step < 2; ++step) { + std::tie(states, reactions) = + makeAdvancer(combined, combined_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + time += dt; + } + + // All subsystems should converge and produce non-trivial solutions. + EXPECT_TRUE(solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged()) + << "Solid solver did not converge"; + EXPECT_TRUE(thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged()) + << "Thermal solver did not converge"; + EXPECT_TRUE(state_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged()) + << "State solver did not converge"; + + // Displacement should be non-zero (compressive traction). + mfem::Vector final_disp(*states[field_store->getFieldIndex("three_displacement_solve_state")].get()); + double max_disp = final_disp.Normlinf(); + EXPECT_GT(max_disp, 1e-8) << "Displacement should be non-zero under compressive traction"; + + // Temperature should be non-zero (heat source applied). + mfem::Vector final_temp(*states[field_store->getFieldIndex("three_temperature_solve_state")].get()); + double max_temp = final_temp.Normlinf(); + EXPECT_GT(max_temp, 1e-10) << "Temperature should be non-zero under heat source"; + + // Damage should be non-zero (driven by strain norm). + mfem::Vector final_state(*states[field_store->getFieldIndex("three_state_solve_state")].get()); + double max_state = final_state.Normlinf(); + EXPECT_GT(max_state, 1e-10) << "Damage state should grow under deformation"; +} + +} // namespace smith + +int main(int argc, char* argv[]) +{ + ::testing::InitGoogleTest(&argc, argv); + smith::ApplicationManager applicationManager(argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index c98bb561b2..807bb93da7 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -182,7 +182,7 @@ auto registerThermalFields( TemperatureTimeRule /*rule*/) { FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(shape_disp_type.name)) { + if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { field_store->addShapeDisp(shape_disp_type); } @@ -236,7 +236,7 @@ auto registerThermalFields( FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(shape_disp_type.name)) { + if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { field_store->addShapeDisp(shape_disp_type); } diff --git a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp new file mode 100644 index 0000000000..031558d06b --- /dev/null +++ b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp @@ -0,0 +1,100 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file thermo_mechanical_system.hpp + * @brief Helper to wire a coupled thermo-mechanical material to a SolidMechanicsSystem and ThermalSystem. + */ + +#pragma once + +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" + +namespace smith { + +/** + * @brief Register a coupled thermo-mechanical material integrand on a SolidMechanicsSystem + * and ThermalSystem that were built with mutual coupling fields. + * + * Assumes: + * - solid was built with thermal fields as the leading coupling fields + * (first 2 coupling positions: temperature_solve_state, temperature). + * - thermal was built with solid displacement fields as the leading coupling fields + * (first 4 coupling positions: displacement_solve_state, displacement, velocity, acceleration). + * + * The solid integrand lambda receives: + * (t_info, X, u, u_old, v_old, a_old, temperature_ss, temperature_old, ...params) + * The thermal integrand lambda receives: + * (t_info, X, T, T_old, disp_ss, displacement, velocity, acceleration, ...params) + * + * The material callable must satisfy: + * material(dt, state, grad_u, grad_v, T_value, grad_T, params...) -> tuple{PK1, C_v, s0, q0} + * + * @tparam dim Spatial dimension (deduced from SolidMechanicsSystem template arg). + * @tparam disp_order_ Displacement polynomial order. + * @tparam temp_order_ Temperature polynomial order. + * @tparam DispRule Displacement time integration rule. + * @tparam TempRule Temperature time integration rule. + * @tparam SolidCoupling Coupling type on the solid system. + * @tparam ThermalCoupling Coupling type on the thermal system. + * @tparam MaterialType Material model type. + * @param solid Solid mechanics system with thermal coupling. + * @param thermal Thermal system with solid displacement coupling. + * @param material Material model instance. + * @param domain_name Domain on which to apply the material. + */ +template +void setCoupledThermoMechanicsMaterial( + std::shared_ptr> solid, + std::shared_ptr> thermal, + const MaterialType& material, const std::string& domain_name) +{ + auto captured_disp_rule = solid->disp_time_rule; + auto captured_temp_rule = thermal->temperature_time_rule; + + // Solid contribution: inertia + PK1 stress + solid->solid_weak_form->addBodyIntegral( + domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, + auto temperature_old, auto... params) { + auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), + get(T), get(T), params...); + return smith::tuple{get(a_current) * material.density, pk}; + }); + + // Cycle-zero: (u, v, a, temperature, temperature_old, ...params) + if (solid->cycle_zero_solid_weak_form) { + solid->cycle_zero_solid_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), + get(T), params...); + return smith::tuple{get(a) * material.density, pk}; + }); + } + + // Thermal contribution: (T, T_old, disp_ss, displacement, velocity, acceleration, ...params) + thermal->thermal_weak_form->addBodyIntegral( + domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old, + auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), + get(T_current), get(T_current), params...); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); +} + +} // namespace smith From 2cbf8df5156538d96115dac19c24210860a59246 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 17 Apr 2026 11:24:31 -0600 Subject: [PATCH 21/67] Style. --- .../combined_system.cpp | 3 +- .../combined_system.hpp | 4 +- .../multiphysics_time_integrator.hpp | 3 +- .../solid_mechanics_system.hpp | 21 ++-- ...id_mechanics_with_internal_vars_system.hpp | 108 ++++++++---------- .../state_variable_system.hpp | 50 +++----- .../tests/test_coupled_thermo_mechanics.cpp | 8 +- .../tests/test_solid_dynamics.cpp | 12 +- .../test_solid_static_with_internal_vars.cpp | 6 +- .../tests/test_thermal_static.cpp | 12 +- .../test_thermo_mechanics_three_system.cpp | 34 +++--- .../thermal_system.hpp | 73 +++++------- .../thermo_mechanical_system.hpp | 23 ++-- src/smith/numerics/solver_config.hpp | 12 +- .../tests/test_linear_solver_none.cpp | 6 +- 15 files changed, 158 insertions(+), 217 deletions(-) diff --git a/src/smith/differentiable_numerics/combined_system.cpp b/src/smith/differentiable_numerics/combined_system.cpp index 6ede296053..6c211da1b4 100644 --- a/src/smith/differentiable_numerics/combined_system.cpp +++ b/src/smith/differentiable_numerics/combined_system.cpp @@ -26,8 +26,7 @@ std::vector CombinedSystem::solve(const TimeInfo& time_info) const auto sub_unknowns = sub->solve(time_info); for (size_t i = 0; i < sub->weak_forms.size(); ++i) { - const std::string reaction_name = - field_store->getWeakFormReaction(sub->weak_forms[i]->name()); + const std::string reaction_name = field_store->getWeakFormReaction(sub->weak_forms[i]->name()); size_t u_idx = field_store->getFieldIndex(reaction_name); field_store->setField(u_idx, sub_unknowns[i]); } diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index ae4437ae45..2c52bc6bc5 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -88,8 +88,7 @@ struct CombinedSystem : public SystemBase { * @tparam MaterialType The material type. */ template - void setMaterialOn(std::shared_ptr sub, const MaterialType& material, - const std::string& domain_name) + void setMaterialOn(std::shared_ptr sub, const MaterialType& material, const std::string& domain_name) { sub->setMaterial(material, domain_name); } @@ -227,4 +226,3 @@ std::vector> mergeSystems(Vectors&&... vectors) } } // namespace smith - diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 4350312d99..7d3ddd70d5 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -54,8 +54,7 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { }; inline std::shared_ptr makeAdvancer( - std::shared_ptr system, - std::shared_ptr cycle_zero_system = nullptr, + std::shared_ptr system, std::shared_ptr cycle_zero_system = nullptr, std::vector> post_solve_systems = {}) { return std::make_shared(std::move(system), std::move(cycle_zero_system), diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index a1b4ed6b1d..2a54233978 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -75,7 +75,7 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr stress_weak_form; ///< Stress projection weak form (nullptr if disabled). std::shared_ptr stress_output_system; ///< Post-solve system for stress projection. - bool output_cauchy_stress = false; ///< Project Cauchy stress instead of PK1 when true. + bool output_cauchy_stress = false; ///< Project Cauchy stress instead of PK1 when true. /** * @brief Set the material model for a domain, defining integrals for the solid weak form. @@ -390,8 +390,7 @@ template field_store, FieldType... parameter_types) { - return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}, - std::move(parameter_types)...); + return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}, std::move(parameter_types)...); } /** @@ -573,14 +572,14 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, const Co std::shared_ptr solver, const SolidMechanicsOptions& options, FieldType... parameter_types) { - ([&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }(parameter_types), - ...); - - auto combined = std::apply( - [&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); + ( + [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }(parameter_types), + ...); + + auto combined = std::apply([&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); auto [sys, cz, ends] = buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, combined, solver, options); diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 979bf4fd66..8835649846 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -71,28 +71,25 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { // Primary weak form: residual for displacement (u). // Inputs: u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params... - using SolidWeakFormType = TimeDiscretizedWeakForm< - dim, H1, - typename detail::AppendCouplingToParams< - Coupling, - Parameters, H1, H1, H1, StateSpace, - StateSpace>>::type>; + using SolidWeakFormType = + TimeDiscretizedWeakForm, + typename detail::AppendCouplingToParams< + Coupling, Parameters, H1, H1, + H1, StateSpace, StateSpace>>::type>; // State weak form: residual for internal variable (alpha). // Inputs: alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params... - using StateWeakFormType = TimeDiscretizedWeakForm< - dim, StateSpace, - typename detail::AppendCouplingToParams< - Coupling, - Parameters, H1, H1, - H1>>::type>; + using StateWeakFormType = + TimeDiscretizedWeakForm, H1, + H1, H1>>::type>; // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, alpha, coupling_fields..., params... using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< dim, H1, typename detail::AppendCouplingToParams< - Coupling, - Parameters, H1, H1, StateSpace>>::type>; + Coupling, Parameters, H1, H1, StateSpace>>::type>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr state_weak_form; ///< Internal variable weak form. @@ -181,8 +178,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addBodyForce(const std::string& domain_name, BodyForceType force_function) { - addBodyForceAllParams(domain_name, force_function, - std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); + addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); } /** @@ -224,8 +220,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addTraction(const std::string& domain_name, TractionType traction_function) { - addTractionAllParams(domain_name, traction_function, - std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); + addTractionAllParams(domain_name, traction_function, std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); } /** @@ -283,8 +278,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { template void addPressure(const std::string& domain_name, PressureType pressure_function) { - addPressureAllParams(domain_name, pressure_function, - std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); + addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); } /** @@ -365,8 +359,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { // Options // --------------------------------------------------------------------------- -struct SolidMechanicsWithInternalVarsOptions { -}; +struct SolidMechanicsWithInternalVarsOptions {}; /** * @brief Register all solid mechanics with internal vars fields into a FieldStore. @@ -376,12 +369,12 @@ struct SolidMechanicsWithInternalVarsOptions { * * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). */ -template -auto registerSolidMechanicsWithInternalVarsFields( - std::shared_ptr field_store, - DisplacementTimeRule /*disp_rule*/, - InternalVarTimeRule /*state_rule*/, - FieldType... parameter_types) +template +auto registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_store, + DisplacementTimeRule /*disp_rule*/, + InternalVarTimeRule /*state_rule*/, + FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { @@ -409,15 +402,13 @@ auto registerSolidMechanicsWithInternalVarsFields( }; (prefix_param(parameter_types), ...); - return CouplingParams{ - FieldType>(field_store->prefix("displacement_solve_state")), - FieldType>(field_store->prefix("displacement")), - FieldType>(field_store->prefix("velocity")), - FieldType>(field_store->prefix("acceleration")), - FieldType(field_store->prefix("state_solve_state")), - FieldType(field_store->prefix("state")), - parameter_types... - }; + return CouplingParams{FieldType>(field_store->prefix("displacement_solve_state")), + FieldType>(field_store->prefix("displacement")), + FieldType>(field_store->prefix("velocity")), + FieldType>(field_store->prefix("acceleration")), + FieldType(field_store->prefix("state_solve_state")), + FieldType(field_store->prefix("state")), + parameter_types...}; } /** @@ -432,13 +423,10 @@ auto registerSolidMechanicsWithInternalVarsFields( template requires detail::is_coupling_params_v -auto buildSolidMechanicsWithInternalVarsSystem( - std::shared_ptr field_store, - DisplacementTimeRule /*disp_rule*/, - InternalVarTimeRule /*state_rule*/, - const Coupling& coupling, - std::shared_ptr solver, - const SolidMechanicsWithInternalVarsOptions& /*options*/) +auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr field_store, + DisplacementTimeRule /*disp_rule*/, InternalVarTimeRule /*state_rule*/, + const Coupling& coupling, std::shared_ptr solver, + const SolidMechanicsWithInternalVarsOptions& /*options*/) { auto disp_time_rule_ptr = std::make_shared(); auto state_time_rule_ptr = std::make_shared(); @@ -455,8 +443,8 @@ auto buildSolidMechanicsWithInternalVarsSystem( auto disp_bc = field_store->getBoundaryConditions(disp_type.name); auto state_bc = field_store->getBoundaryConditions(state_type.name); - using SystemType = SolidMechanicsWithInternalVarsSystem; + using SystemType = + SolidMechanicsWithInternalVarsSystem; // Solid weak form: (u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params...) std::string solid_res_name = field_store->prefix("solid_residual"); @@ -501,8 +489,7 @@ auto buildSolidMechanicsWithInternalVarsSystem( sys->cycle_zero_solid_weak_form = std::apply( [&](auto&... cfs) { return std::make_shared( - cycle_zero_name, field_store->getMesh(), - field_store->getField(accel_old_type.name).get()->space(), + cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, state_cz_input, cfs...)); }, @@ -516,13 +503,13 @@ auto buildSolidMechanicsWithInternalVarsSystem( .max_iterations = 2, .print_level = 0}; LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - auto cycle_zero_solver = std::make_shared( - buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + auto cycle_zero_solver = + std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; @@ -539,13 +526,10 @@ auto buildSolidMechanicsWithInternalVarsSystem( */ template -auto buildSolidMechanicsWithInternalVarsSystem( - std::shared_ptr field_store, - DisplacementTimeRule disp_rule, - InternalVarTimeRule state_rule, - std::shared_ptr solver, - const SolidMechanicsWithInternalVarsOptions& options, - FieldType... parameter_types) +auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr field_store, DisplacementTimeRule disp_rule, + InternalVarTimeRule state_rule, std::shared_ptr solver, + const SolidMechanicsWithInternalVarsOptions& options, + FieldType... parameter_types) { auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 40d4436e42..8d139d92cd 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -55,23 +55,20 @@ namespace smith { * @tparam InternalVarTimeRule Time integration rule (must have num_states == 2). * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). */ -template > struct StateVariableSystem : public SystemBase { using SystemBase::SystemBase; - static_assert(InternalVarTimeRule::num_states == 2, - "StateVariableSystem requires a 2-state time integration rule"); + static_assert(InternalVarTimeRule::num_states == 2, "StateVariableSystem requires a 2-state time integration rule"); /// State weak form: (alpha, alpha_old, coupling_fields..., params...) using StateWeakFormType = TimeDiscretizedWeakForm< - dim, StateSpace, - typename detail::TimeRuleParamsWithCoupling::type>; + dim, StateSpace, typename detail::TimeRuleParamsWithCoupling::type>; - std::shared_ptr state_weak_form; ///< Internal variable weak form. - std::shared_ptr state_bc; ///< Internal variable boundary conditions. - std::shared_ptr state_time_rule; ///< Time integration rule. + std::shared_ptr state_weak_form; ///< Internal variable weak form. + std::shared_ptr state_bc; ///< Internal variable boundary conditions. + std::shared_ptr state_time_rule; ///< Time integration rule. /** * @brief Register an ODE evolution law for the internal variable. @@ -122,10 +119,8 @@ struct StateVariableOptions {}; * suitable for injection into another physics system. */ template -auto registerStateVariableFields( - std::shared_ptr field_store, - InternalVarTimeRule /*rule*/, - FieldType... parameter_types) +auto registerStateVariableFields(std::shared_ptr field_store, InternalVarTimeRule /*rule*/, + FieldType... parameter_types) { auto state_time_rule_ptr = std::make_shared(); FieldType state_type("state_solve_state"); @@ -138,11 +133,8 @@ auto registerStateVariableFields( }; (prefix_param(parameter_types), ...); - return CouplingParams{ - FieldType(field_store->prefix("state_solve_state")), - FieldType(field_store->prefix("state")), - parameter_types... - }; + return CouplingParams{FieldType(field_store->prefix("state_solve_state")), + FieldType(field_store->prefix("state")), parameter_types...}; } // --------------------------------------------------------------------------- @@ -159,12 +151,9 @@ auto registerStateVariableFields( */ template requires detail::is_coupling_params_v -auto buildStateVariableSystem( - std::shared_ptr field_store, - InternalVarTimeRule /*rule*/, - const Coupling& coupling, - std::shared_ptr solver, - const StateVariableOptions& /*options*/) +auto buildStateVariableSystem(std::shared_ptr field_store, InternalVarTimeRule /*rule*/, + const Coupling& coupling, std::shared_ptr solver, + const StateVariableOptions& /*options*/) { auto state_time_rule_ptr = std::make_shared(); @@ -201,20 +190,17 @@ auto buildStateVariableSystem( * Parameters (if any) are wrapped into a CouplingParams so the system sees them. */ template -auto buildStateVariableSystem( - std::shared_ptr field_store, - InternalVarTimeRule rule, - std::shared_ptr solver, - const StateVariableOptions& options, - FieldType... parameter_types) +auto buildStateVariableSystem(std::shared_ptr field_store, InternalVarTimeRule rule, + std::shared_ptr solver, const StateVariableOptions& options, + FieldType... parameter_types) { auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; field_store->addParameter(pt); }; (prefix_param(parameter_types), ...); - return buildStateVariableSystem( - field_store, rule, CouplingParams{parameter_types...}, solver, options); + return buildStateVariableSystem(field_store, rule, CouplingParams{parameter_types...}, solver, + options); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp index 57ffcb3ff6..c77fce8c63 100644 --- a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp @@ -377,12 +377,12 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_coupling_fields = registerSolidMechanicsFields(field_store); auto thermal_coupling_fields = registerThermalFields(field_store); - auto solid_res = buildSolidMechanicsSystem( - field_store, thermal_coupling_fields, nullptr, solid_opts); + auto solid_res = buildSolidMechanicsSystem(field_store, thermal_coupling_fields, + nullptr, solid_opts); auto solid = solid_res.system; - auto thermal_res = buildThermalSystem( - field_store, solid_coupling_fields, nullptr, thermal_opts); + auto thermal_res = + buildThermalSystem(field_store, solid_coupling_fields, nullptr, thermal_opts); auto thermal = thermal_res.system; auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 2732b57c5d..b6ed5227b0 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -106,8 +106,8 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto field_store = std::make_shared(mesh, 100, ""); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - registerSolidMechanicsFields( - field_store, FieldType("bulk"), FieldType("shear")); + registerSolidMechanicsFields(field_store, FieldType("bulk"), + FieldType("shear")); auto solid_res = buildSolidMechanicsSystem( field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, @@ -216,12 +216,12 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(mesh, 100, physics_name); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - registerSolidMechanicsFields( - field_store, FieldType("bulk"), FieldType("shear")); + registerSolidMechanicsFields(field_store, FieldType("bulk"), + FieldType("shear")); auto solid_res = buildSolidMechanicsSystem( - field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{}, - FieldType("bulk"), FieldType("shear")); + field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{}, FieldType("bulk"), + FieldType("shear")); auto system = solid_res.system; auto cz_sys = solid_res.cycle_zero_system; auto end_steps = solid_res.end_step_systems; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 7b4bfe7e50..675277b986 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -108,7 +108,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); - + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; BackwardEulerFirstOrderTimeIntegrationRule state_rule; registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); @@ -158,7 +158,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) staggered_solver->addSubsystemSolver({1}, state_solver, 1.0); auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); - + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; BackwardEulerFirstOrderTimeIntegrationRule state_rule; registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); @@ -189,7 +189,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "body_force_test_"); - + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; BackwardEulerFirstOrderTimeIntegrationRule state_rule; registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index eb3e9a57a9..90af7ca512 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -52,12 +52,12 @@ struct ThermalStaticFixture : public testing::Test { auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, ""); - + using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; registerThermalFields<2, temp_order, TempRule>(field_store); - auto thermal_res = buildThermalSystem<2, temp_order, TempRule>( - field_store, CouplingParams<>{}, coupled_solver, ThermalOptions{}); + auto thermal_res = + buildThermalSystem<2, temp_order, TempRule>(field_store, CouplingParams<>{}, coupled_solver, ThermalOptions{}); auto thermal_system = thermal_res.system; auto cz_sys = thermal_res.cycle_zero_system; auto end_steps = thermal_res.end_step_systems; @@ -151,12 +151,12 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) FieldType> conductivity_param("conductivity"); auto field_store = std::make_shared(mesh, 100, ""); - + using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; registerThermalFields<2, 1, TempRule>(field_store, conductivity_param); - auto thermal_res = buildThermalSystem<2, 1, TempRule>( - field_store, CouplingParams<>{}, coupled_solver, ThermalOptions{}, conductivity_param); + auto thermal_res = buildThermalSystem<2, 1, TempRule>(field_store, CouplingParams<>{}, coupled_solver, + ThermalOptions{}, conductivity_param); auto thermal_system = thermal_res.system; auto cz_sys = thermal_res.cycle_zero_system; auto end_steps = thermal_res.end_step_systems; diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index 40a1fa4a15..ac9a952ced 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -116,9 +116,8 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) auto captured_disp_rule = solid->disp_time_rule; auto captured_temp_rule = thermal->temperature_time_rule; solid->solid_weak_form->addBodyIntegral( - mesh_->entireBodyName(), - [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature_ss, - auto temperature_old) { + mesh_->entireBodyName(), [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, + auto temperature_ss, auto temperature_old) { auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); auto T = captured_temp_rule->value(t_info, temperature_ss, temperature_old); @@ -138,27 +137,26 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) { auto captured_temp_rule = thermal->temperature_time_rule; const double kappa = 0.05, C_v = 1.0, heat_source = 0.5; - thermal->thermal_weak_form->addBodyIntegral( - mesh_->entireBodyName(), [=](auto t_info, auto /*X*/, auto T, auto T_old) { - auto [T_c, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto q = kappa * get(T_c); - return smith::tuple{C_v * get(T_dot) - heat_source, -q}; - }); + thermal->thermal_weak_form->addBodyIntegral(mesh_->entireBodyName(), + [=](auto t_info, auto /*X*/, auto T, auto T_old) { + auto [T_c, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto q = kappa * get(T_c); + return smith::tuple{C_v * get(T_dot) - heat_source, -q}; + }); } // State (damage): evolves as d_dot = (1 - d) * eps_norm. // addStateEvolution lambda args: (t_info, alpha_val, alpha_dot, disp_ss, displacement, velocity, acceleration) { auto captured_disp_rule = solid->disp_time_rule; - state_sys->addStateEvolution( - mesh_->entireBodyName(), - [=](auto t_info, auto alpha_val, auto alpha_dot, auto u_ss, auto u, auto v, auto a) { - auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u_ss, u, v, a); - auto eps = sym(get(u_c)); - using std::sqrt; - auto eps_norm = sqrt(inner(eps, eps) + 1e-16); - return alpha_dot - (1.0 - alpha_val) * eps_norm; - }); + state_sys->addStateEvolution(mesh_->entireBodyName(), + [=](auto t_info, auto alpha_val, auto alpha_dot, auto u_ss, auto u, auto v, auto a) { + auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u_ss, u, v, a); + auto eps = sym(get(u_c)); + using std::sqrt; + auto eps_norm = sqrt(inner(eps, eps) + 1e-16); + return alpha_dot - (1.0 - alpha_val) * eps_norm; + }); } // Phase 4: boundary conditions. diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 807bb93da7..3417cea8a2 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -165,8 +165,7 @@ struct ThermalSystem : public SystemBase { } }; -struct ThermalOptions { -}; +struct ThermalOptions {}; /** * @brief Register all thermal fields into a FieldStore. @@ -177,9 +176,7 @@ struct ThermalOptions { * @return PhysicsFields carrying the exported field tokens and time rule type. */ template -auto registerThermalFields( - std::shared_ptr field_store, - TemperatureTimeRule /*rule*/) +auto registerThermalFields(std::shared_ptr field_store, TemperatureTimeRule /*rule*/) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { @@ -192,8 +189,7 @@ auto registerThermalFields( field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); return PhysicsFields, H1>{ - field_store, - FieldType>(field_store->prefix("temperature_solve_state")), + field_store, FieldType>(field_store->prefix("temperature_solve_state")), FieldType>(field_store->prefix("temperature"))}; } @@ -213,11 +209,9 @@ auto registerThermalFields(std::shared_ptr field_store) */ template requires(sizeof...(parameter_space) > 0) -auto registerThermalFields(std::shared_ptr field_store, - FieldType... parameter_types) +auto registerThermalFields(std::shared_ptr field_store, FieldType... parameter_types) { - return registerThermalFields(field_store, TemperatureTimeRule{}, - std::move(parameter_types)...); + return registerThermalFields(field_store, TemperatureTimeRule{}, std::move(parameter_types)...); } /** @@ -230,10 +224,8 @@ auto registerThermalFields(std::shared_ptr field_store, */ template requires(sizeof...(parameter_space) > 0) -auto registerThermalFields( - std::shared_ptr field_store, - TemperatureTimeRule /*rule*/, - FieldType... parameter_types) +auto registerThermalFields(std::shared_ptr field_store, TemperatureTimeRule /*rule*/, + FieldType... parameter_types) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { @@ -251,11 +243,8 @@ auto registerThermalFields( }; (prefix_param(parameter_types), ...); - return CouplingParams{ - FieldType>(field_store->prefix("temperature_solve_state")), - FieldType>(field_store->prefix("temperature")), - parameter_types... - }; + return CouplingParams{FieldType>(field_store->prefix("temperature_solve_state")), + FieldType>(field_store->prefix("temperature")), parameter_types...}; } /** @@ -270,12 +259,8 @@ auto registerThermalFields( */ template requires detail::is_coupling_params_v -auto buildThermalSystem( - std::shared_ptr field_store, - TemperatureTimeRule /*rule*/, - const Coupling& coupling, - std::shared_ptr solver, - const ThermalOptions& /*options*/) +auto buildThermalSystem(std::shared_ptr field_store, TemperatureTimeRule /*rule*/, const Coupling& coupling, + std::shared_ptr solver, const ThermalOptions& /*options*/) { auto temperature_time_rule_ptr = std::make_shared(); @@ -291,10 +276,9 @@ auto buildThermalSystem( auto thermal_weak_form = std::apply( [&](auto&... cfs) { return std::make_shared( - thermal_flux_name, field_store->getMesh(), - field_store->getField(temperature_type.name).get()->space(), - field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, - temperature_old_type, cfs...)); + thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), + field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, + cfs...)); }, coupling.fields); @@ -323,21 +307,20 @@ auto buildThermalSystem( * auto thermal = res.system; * @endcode */ -template +template requires detail::is_coupling_params_v auto buildThermalSystem(std::shared_ptr field_store, const Coupling& coupling, std::shared_ptr solver, const ThermalOptions& options, FieldType... parameter_types) { - ([&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }(parameter_types), - ...); + ( + [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }(parameter_types), + ...); - auto combined = std::apply( - [&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); + auto combined = std::apply([&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); auto [sys, cz, ends] = buildThermalSystem(field_store, TemperatureTimeRule{}, combined, solver, options); @@ -359,19 +342,15 @@ auto buildThermalSystem(std::shared_ptr field_store, const Coupling& * @endcode */ template - requires(sizeof...(FieldPacks) > 0 && - (detail::is_physics_fields_v || ...) && + requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && !(std::is_same_v, ThermalOptions> || ...)) -auto buildThermalSystem( - std::shared_ptr solver, - const ThermalOptions& options, - const FieldPacks&... field_packs) +auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, + const FieldPacks&... field_packs) { auto field_store = detail::findFieldStore(field_packs...); (detail::registerParamsIfNeeded(field_store, field_packs), ...); auto coupling = detail::collectCouplingFields(field_packs...); - return buildThermalSystem( - field_store, TemperatureTimeRule{}, coupling, solver, options); + return buildThermalSystem(field_store, TemperatureTimeRule{}, coupling, solver, options); } } // namespace smith diff --git a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp index 031558d06b..d88d13d5f7 100644 --- a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp @@ -51,8 +51,8 @@ template void setCoupledThermoMechanicsMaterial( std::shared_ptr> solid, - std::shared_ptr> thermal, - const MaterialType& material, const std::string& domain_name) + std::shared_ptr> thermal, const MaterialType& material, + const std::string& domain_name) { auto captured_disp_rule = solid->disp_time_rule; auto captured_temp_rule = thermal->temperature_time_rule; @@ -84,17 +84,16 @@ void setCoupledThermoMechanicsMaterial( } // Thermal contribution: (T, T_old, disp_ss, displacement, velocity, acceleration, ...params) - thermal->thermal_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old, - auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, + auto disp_old, auto v_old, auto a_old, auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), - get(T_current), get(T_current), params...); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; - }); + typename MaterialType::State state; + auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T_current), + get(T_current), params...); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); } } // namespace smith diff --git a/src/smith/numerics/solver_config.hpp b/src/smith/numerics/solver_config.hpp index 5a203739cc..1dceeecacf 100644 --- a/src/smith/numerics/solver_config.hpp +++ b/src/smith/numerics/solver_config.hpp @@ -102,13 +102,13 @@ struct TimesteppingOptions { /// Linear solution method indicator enum class LinearSolver { - CG, /**< Conjugate gradient */ - GMRES, /**< Generalized minimal residual method */ - SuperLU, /**< SuperLU MPI-enabled direct nodal solver */ - Strumpack, /**< Strumpack MPI-enabled direct frontal solver*/ - PetscCG, /**< PETSc MPI-enabled conjugate gradient solver */ + CG, /**< Conjugate gradient */ + GMRES, /**< Generalized minimal residual method */ + SuperLU, /**< SuperLU MPI-enabled direct nodal solver */ + Strumpack, /**< Strumpack MPI-enabled direct frontal solver*/ + PetscCG, /**< PETSc MPI-enabled conjugate gradient solver */ PetscGMRES, /**< PETSc MPI-enabled generalize minimal residual solver */ - None /**< Preconditioner application only, No linear solver Krylov iterations */ + None /**< Preconditioner application only, No linear solver Krylov iterations */ }; // _linear_solvers_end diff --git a/src/smith/numerics/tests/test_linear_solver_none.cpp b/src/smith/numerics/tests/test_linear_solver_none.cpp index 9d0cd6246d..6d4994b522 100644 --- a/src/smith/numerics/tests/test_linear_solver_none.cpp +++ b/src/smith/numerics/tests/test_linear_solver_none.cpp @@ -58,7 +58,7 @@ TEST(LinearSolverNone, Identity) mfem::Vector x(size); x = 1.0; // Initial guess - // Residual will be f(x) = x. + // Residual will be f(x) = x. // x_new = x - [df/dx]^-1 * f(x) = x - I^-1 * x = 0. solver.solve(x); @@ -77,7 +77,7 @@ TEST(LinearSolverNone, Jacobi) LinearSolverOptions linear_opts; linear_opts.linear_solver = LinearSolver::None; - linear_opts.preconditioner = Preconditioner::None; // We'll set this manually if needed, but wait. + linear_opts.preconditioner = Preconditioner::None; // We'll set this manually if needed, but wait. // Actually, Preconditioner::None with LinearSolver::None should be Identity. // Let's test that first. @@ -89,7 +89,7 @@ TEST(LinearSolverNone, Jacobi) solver.setOperator(op); mfem::Vector x(size); - x = 1.0; + x = 1.0; // f(x) = 2x. df/dx = 2I. // If LinearSolver::None and Preconditioner::None, it uses identity: // x_new = x - I * (2x) = x - 2x = -x. From 91eeab6f8a0eee714117c1a377e9a320f3fb4bba Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 20 Apr 2026 15:03:20 -0600 Subject: [PATCH 22/67] Simplify field and system registration. --- .../solid_mechanics_system.hpp | 15 ++--------- ...id_mechanics_with_internal_vars_system.hpp | 25 +++---------------- .../test_thermo_mechanics_three_system.cpp | 5 +++- .../thermal_system.hpp | 12 ++------- 4 files changed, 12 insertions(+), 45 deletions(-) diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 2a54233978..8908bf21c4 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -403,21 +403,10 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, */ template requires(sizeof...(parameter_space) > 0) -auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule /*rule*/, +auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule rule, FieldType... parameter_types) { - FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { - field_store->addShapeDisp(shape_disp_type); - } - - auto disp_time_rule_ptr = std::make_shared(); - FieldType> disp_type("displacement_solve_state"); - field_store->addIndependent(disp_type, disp_time_rule_ptr); - - field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); - field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); - field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); + registerSolidMechanicsFields(field_store, rule); auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 8835649846..e7b0ba8ca1 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -372,30 +372,13 @@ struct SolidMechanicsWithInternalVarsOptions {}; template auto registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_store, - DisplacementTimeRule /*disp_rule*/, - InternalVarTimeRule /*state_rule*/, + DisplacementTimeRule disp_rule, + InternalVarTimeRule state_rule, FieldType... parameter_types) { - FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { - field_store->addShapeDisp(shape_disp_type); - } - - // Displacement fields (4-state second-order) - auto disp_time_rule_ptr = std::make_shared(); - FieldType> disp_type("displacement_solve_state"); - field_store->addIndependent(disp_type, disp_time_rule_ptr); - field_store->addDependent(disp_type, FieldStore::TimeDerivative::VAL, "displacement"); - field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); - field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); - - // Internal variable fields (2-state first-order) - auto state_time_rule_ptr = std::make_shared(); - FieldType state_type("state_solve_state"); - field_store->addIndependent(state_type, state_time_rule_ptr); - field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); + registerSolidMechanicsFields(field_store, disp_rule); + registerStateVariableFields(field_store, state_rule); - // Parameters auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; field_store->addParameter(pt); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index ac9a952ced..abc10dc745 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -164,7 +164,10 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) thermal->setTemperatureBC(mesh_->domain("left")); // Compressive traction on right face. - solid->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto, auto) { + // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old) + // — 6 self fields + 2 thermal coupling fields forwarded as trailing args. + solid->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, + auto /*temp_old*/) { auto t = 0.0 * X; t[0] = -0.005; return t; diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 3417cea8a2..5387dd06cd 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -224,18 +224,10 @@ auto registerThermalFields(std::shared_ptr field_store, FieldType requires(sizeof...(parameter_space) > 0) -auto registerThermalFields(std::shared_ptr field_store, TemperatureTimeRule /*rule*/, +auto registerThermalFields(std::shared_ptr field_store, TemperatureTimeRule rule, FieldType... parameter_types) { - FieldType> shape_disp_type("shape_displacement"); - if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { - field_store->addShapeDisp(shape_disp_type); - } - - auto temperature_time_rule_ptr = std::make_shared(); - FieldType> temperature_type("temperature_solve_state"); - field_store->addIndependent(temperature_type, temperature_time_rule_ptr); - field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); + registerThermalFields(field_store, rule); auto prefix_param = [&](auto& pt) { pt.name = "param_" + pt.name; From 122087883cf6ee9f9a6f457a124cf478e7e8e6d4 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 20 Apr 2026 15:57:40 -0600 Subject: [PATCH 23/67] Remove duplicated test. --- .../tests/CMakeLists.txt | 1 - .../tests/test_combined_thermo_mechanics.cpp | 19 +- .../tests/test_coupled_thermo_mechanics.cpp | 513 ------------------ 3 files changed, 9 insertions(+), 524 deletions(-) delete mode 100644 src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index 2fa5707c45..8848d097a4 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -12,7 +12,6 @@ set(differentiable_numerics_test_source test_solid_dynamics.cpp test_explicit_dynamics.cpp test_porous_heat_sink.cpp - test_coupled_thermo_mechanics.cpp test_combined_thermo_mechanics.cpp test_thermal_static.cpp test_solid_static_with_internal_vars.cpp diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index ad8892635c..d550e05854 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -188,16 +188,15 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) SolidMechanicsOptions solid_opts; ThermalOptions thermal_opts; - auto thermal_coupling = registerSolidMechanicsFields(field_store, youngs_modulus); - auto solid_coupling = registerThermalFields(field_store); + auto param_fields = registerParameterFields(youngs_modulus); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto thermal_fields = registerThermalFields(field_store); - auto solid_res = buildSolidMechanicsSystem( - field_store, solid_coupling, std::make_shared(solid_block_solver), solid_opts, youngs_modulus); - auto solid = solid_res.system; + auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( + std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); - auto thermal_res = buildThermalSystem( - field_store, thermal_coupling, std::make_shared(thermal_block_solver), thermal_opts); - auto thermal = thermal_res.system; + auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( + std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, solid_fields); auto [coupled, coupled_cz] = combineSystems(solid, thermal); @@ -207,8 +206,8 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( [=](smith::tensor) { return 100.0; }); - solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); + solid->setDisplacementBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("left")); solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { auto traction = 0.0 * X; diff --git a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp deleted file mode 100644 index c77fce8c63..0000000000 --- a/src/smith/differentiable_numerics/tests/test_coupled_thermo_mechanics.cpp +++ /dev/null @@ -1,513 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#include -#include "gtest/gtest.h" - -#include "smith/smith_config.hpp" -#include "smith/infrastructure/application_manager.hpp" -#include "smith/numerics/solver_config.hpp" -#include "smith/physics/state/state_manager.hpp" -#include "smith/physics/mesh.hpp" - -#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/system_solver.hpp" -#include "smith/differentiable_numerics/solid_mechanics_system.hpp" -#include "smith/differentiable_numerics/thermal_system.hpp" -#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" -#include "smith/differentiable_numerics/combined_system.hpp" -#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" -#include "smith/differentiable_numerics/differentiable_test_utils.hpp" -#include "smith/differentiable_numerics/nonlinear_solve.hpp" -#include "smith/physics/functional_objective.hpp" -#include "gretl/wang_checkpoint_strategy.hpp" - -namespace smith { - -static constexpr int dim = 3; -static constexpr int displacement_order = 1; -static constexpr int temperature_order = 1; - -template -auto greenStrain(const tensor& grad_u) -{ - return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); -} - -// Material with E parameter -struct ParameterizedGreenSaintVenantThermoelasticMaterial { - double density; - double E0; - double nu; - double C_v; - double alpha; - double theta_ref; - double kappa; - using State = Empty; - template - auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta, const T5& E_param) const - { - auto E = E0 + get<0>(E_param); - const auto K = E / (3.0 * (1.0 - 2.0 * nu)); - const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrain(grad_u); - const auto trEg = tr(Eg); - static constexpr auto I = Identity(); - const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; - auto F = grad_u + I; - const auto Piola = dot(F, S); - auto greenStrainRate = - 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; - const auto q0 = -kappa * grad_theta; - return smith::tuple{Piola, C_v, s0, q0}; - } -}; - -// Material without user parameters -struct ThermoelasticMaterialNoParam { - double density; - double E; - double nu; - double C_v; - double alpha; - double theta_ref; - double kappa; - using State = Empty; - template - auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta) const - { - const auto K = E / (3.0 * (1.0 - 2.0 * nu)); - const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrain(grad_u); - const auto trEg = tr(Eg); - static constexpr auto I = Identity(); - const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; - auto F = grad_u + I; - const auto Piola = dot(F, S); - auto greenStrainRate = - 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate); - const auto q0 = -kappa * grad_theta; - return smith::tuple{Piola, C_v, s0, q0}; - } -}; - -struct ThermoMechanicsMeshFixture : public testing::Test { - void SetUp() - { - datastore_ = std::make_unique(); - smith::StateManager::initialize(*datastore_, "solid"); - mesh_ = std::make_shared( - mfem::Mesh::MakeCartesian3D(24, 2, 2, mfem::Element::HEXAHEDRON, 1.2, 0.03, 0.03), "mesh", 0, 0); - mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); - mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); - } - std::unique_ptr datastore_; - std::shared_ptr mesh_; -}; - -// 1. CreateDifferentiablePhysicsAllocatesReactionInfo -TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionInfo) -{ - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - FieldType> youngs_modulus("youngs_modulus"); - - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto solid_coupling_fields = - registerSolidMechanicsFields(field_store, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store, youngs_modulus); - - auto solid_res = buildSolidMechanicsSystem( - field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto solid = solid_res.system; - - auto thermal_res = buildThermalSystem( - field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); - auto thermal = thermal_res.system; - - auto [coupled, coupled_cz] = combineSystems(solid, thermal); - - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); - const auto& solid_dual_space = physics->dual("reactions").space(); - const auto& solid_state_space = physics->state("displacement_solve_state").space(); - const auto& thermal_dual_space = physics->dual("thermal_flux").space(); - const auto& thermal_state_space = physics->state("temperature_solve_state").space(); - - EXPECT_EQ(physics->dualNames().size(), 2); - EXPECT_EQ(physics->dualNames()[0], "reactions"); - EXPECT_EQ(physics->dualNames()[1], "thermal_flux"); - EXPECT_EQ(solid_dual_space.GetMesh(), solid_state_space.GetMesh()); - EXPECT_STREQ(solid_dual_space.FEColl()->Name(), solid_state_space.FEColl()->Name()); - EXPECT_EQ(solid_dual_space.GetVDim(), solid_state_space.GetVDim()); - EXPECT_EQ(solid_dual_space.TrueVSize(), solid_state_space.TrueVSize()); - EXPECT_EQ(thermal_dual_space.GetMesh(), thermal_state_space.GetMesh()); - EXPECT_STREQ(thermal_dual_space.FEColl()->Name(), thermal_state_space.FEColl()->Name()); - EXPECT_EQ(thermal_dual_space.GetVDim(), thermal_state_space.GetVDim()); - EXPECT_EQ(thermal_dual_space.TrueVSize(), thermal_state_space.TrueVSize()); -} - -// 2. BackpropagateThroughPhysics -TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) -{ - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - FieldType> youngs_modulus("youngs_modulus"); - - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto solid_coupling_fields = - registerSolidMechanicsFields(field_store, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store, youngs_modulus); - - auto solid_res = buildSolidMechanicsSystem( - field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto solid = solid_res.system; - - auto thermal_res = buildThermalSystem( - field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); - auto thermal = thermal_res.system; - - auto [coupled, coupled_cz] = combineSystems(solid, thermal); - - ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - - coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( - [=](smith::tensor) { return 100.0; }); - - solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - - solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { - auto traction = 0.0 * X; - traction[0] = -0.015; - return traction; - }); - - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); - - // Run forward - double dt = 1.0; - for (int step = 0; step < 2; ++step) { - physics->advanceTimestep(dt); - } - - auto reactions = physics->getReactionStates(); - auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); - - gretl::set_as_objective(obj); - obj.data_store().back_prop(); - - auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); - EXPECT_TRUE(param_sens->Norml2() > 0.0); -} - -// 3. StaggeredBucklingChallenge -// Replaces MonolithicBucklingChallenge: verifies convergence and displacement of the staggered combined solver. -TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) -{ - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; - - smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 120, - .print_level = 0}; - smith::NonlinearSolverOptions mech_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::TrustRegion, - .relative_tol = 1e-6, - .absolute_tol = 1e-7, - .max_iterations = 25, - .print_level = 0}; - - smith::LinearSolverOptions therm_lin_opts{.linear_solver = smith::LinearSolver::GMRES, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 80, - .print_level = 0}; - smith::NonlinearSolverOptions therm_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, - .relative_tol = 1e-7, - .absolute_tol = 1e-7, - .max_iterations = 12, - .max_line_search_iterations = 6, - .print_level = 0}; - - auto solid_block_solver = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto solid_coupling_fields = registerSolidMechanicsFields(field_store); - auto thermal_coupling_fields = registerThermalFields(field_store); - - auto solid_res = buildSolidMechanicsSystem( - field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts); - auto solid = solid_res.system; - - auto thermal_res = buildThermalSystem( - field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); - auto thermal = thermal_res.system; - - auto [coupled, coupled_cz] = combineSystems(solid, thermal); - - ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - - solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); - - solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -compressive_traction; - return traction; - }); - solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); - - SLIC_INFO_ROOT("Starting staggered thermo-mechanics solve"); - - double dt = 1.0; - double time = 0.0; - auto shape_disp = field_store->getShapeDisp(); - auto states = field_store->getStateFields(); - auto params = field_store->getParameterFields(); - std::vector reactions; - for (size_t step = 0; step < 1; ++step) { - std::tie(states, reactions) = - makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); - time += dt; - } - - mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); - - bool staggered_solid_converged = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int staggered_solid_iterations = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - bool staggered_thermal_converged = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int staggered_thermal_iterations = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - - double staggered_lateral_deflection = 0.0; - for (int i = 1; i < final_disp.Size(); i += dim) { - staggered_lateral_deflection = std::max(staggered_lateral_deflection, std::abs(final_disp(i))); - } - - SLIC_INFO_ROOT("Staggered solid converged: " << staggered_solid_converged - << ", iterations: " << staggered_solid_iterations); - SLIC_INFO_ROOT("Staggered thermal converged: " << staggered_thermal_converged - << ", iterations: " << staggered_thermal_iterations); - SLIC_INFO_ROOT("Staggered max lateral deflection: " << staggered_lateral_deflection); - - EXPECT_TRUE(staggered_solid_converged); - EXPECT_TRUE(staggered_thermal_converged); - EXPECT_GT(staggered_lateral_deflection, 1e-5); -} - -// 4. MonolithicBucklingChallenge -// Verifies convergence and displacement of the monolithic combined solver. -TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) -{ - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; - - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 80, - .print_level = 0}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-7, - .absolute_tol = 1e-7, - .max_iterations = 12, - .print_level = 0}; - - auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto solver_ptr = std::make_shared(block_solver); - - auto field_store = std::make_shared(mesh_, 100, ""); - - // Notice that the block_solver is the SAME solver for the whole system - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto solid_coupling_fields = registerSolidMechanicsFields(field_store); - auto thermal_coupling_fields = registerThermalFields(field_store); - - auto solid_res = buildSolidMechanicsSystem(field_store, thermal_coupling_fields, - nullptr, solid_opts); - auto solid = solid_res.system; - - auto thermal_res = - buildThermalSystem(field_store, solid_coupling_fields, nullptr, thermal_opts); - auto thermal = thermal_res.system; - - auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); - - ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - - solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); - - solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -compressive_traction; - return traction; - }); - solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); - - SLIC_INFO_ROOT("Starting monolithic thermo-mechanics solve"); - - double dt = 1.0; - double time = 0.0; - auto shape_disp = field_store->getShapeDisp(); - auto states = field_store->getStateFields(); - auto params = field_store->getParameterFields(); - std::vector reactions; - for (size_t step = 0; step < 1; ++step) { - std::tie(states, reactions) = - makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); - time += dt; - } - - mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); - - bool converged = block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int iterations = block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - - double lateral_deflection = 0.0; - for (int i = 1; i < final_disp.Size(); i += dim) { - lateral_deflection = std::max(lateral_deflection, std::abs(final_disp(i))); - } - - SLIC_INFO_ROOT("Monolithic converged: " << converged << ", iterations: " << iterations); - SLIC_INFO_ROOT("Monolithic max lateral deflection: " << lateral_deflection); - - EXPECT_TRUE(converged); - EXPECT_GT(lateral_deflection, 1e-5); -} - -// 5. NewAPIBackpropagateThroughPhysics -// Same as test 2 but using the new variadic build API with PhysicsFields and registerParameterFields. -TEST_F(ThermoMechanicsMeshFixture, NewAPIBackpropagateThroughPhysics) -{ - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - FieldType> youngs_modulus("youngs_modulus"); - - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto param_fields = registerParameterFields(youngs_modulus); - auto solid_fields = registerSolidMechanicsFields(field_store); - auto thermal_fields = registerThermalFields(field_store); - - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); - - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( - std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, solid_fields); - - auto [coupled, coupled_cz] = combineSystems(solid, thermal); - - ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - - coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( - [=](smith::tensor) { return 100.0; }); - - solid->setDisplacementBC(mesh_->domain("left")); - thermal->setTemperatureBC(mesh_->domain("left")); - - solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { - auto traction = 0.0 * X; - traction[0] = -0.015; - return traction; - }); - - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); - - // Run forward - double dt = 1.0; - for (int step = 0; step < 2; ++step) { - physics->advanceTimestep(dt); - } - - auto reactions = physics->getReactionStates(); - auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); - - gretl::set_as_objective(obj); - obj.data_store().back_prop(); - - auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); - EXPECT_TRUE(param_sens->Norml2() > 0.0); -} - -} // namespace smith - -int main(int argc, char* argv[]) -{ - ::testing::InitGoogleTest(&argc, argv); - smith::ApplicationManager applicationManager(argc, argv); - return RUN_ALL_TESTS(); -} From f7b90f62deab62e7c78261ce8a979296053559bf Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 20 Apr 2026 15:57:59 -0600 Subject: [PATCH 24/67] style. --- compile_commands.json | 1 + ideal_test.cpp | 69 +++++++++++++++++++ ...id_mechanics_with_internal_vars_system.hpp | 3 +- .../test_thermo_mechanics_three_system.cpp | 12 ++-- 4 files changed, 77 insertions(+), 8 deletions(-) create mode 120000 compile_commands.json create mode 100644 ideal_test.cpp diff --git a/compile_commands.json b/compile_commands.json new file mode 120000 index 0000000000..bcf4942cb0 --- /dev/null +++ b/compile_commands.json @@ -0,0 +1 @@ +build-Michaels-Mac-mini.local-darwin-sequoia-aarch64-apple-clang@17.0.0-release/compile_commands.json \ No newline at end of file diff --git a/ideal_test.cpp b/ideal_test.cpp new file mode 100644 index 0000000000..a87b46e5e5 --- /dev/null +++ b/ideal_test.cpp @@ -0,0 +1,69 @@ +// 2. BackpropagateThroughPhysics +TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) +{ + smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; + smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, + .relative_tol = 1e-10, + .absolute_tol = 1e-10, + .max_iterations = 4}; + + auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + + auto field_store = std::make_shared(mesh_, 100, ""); + FieldType> youngs_modulus("youngs_modulus"); + + SolidMechanicsOptions solid_opts; + ThermalOptions thermal_opts; + + QuasiStaticSecondOrderTimeIntegrationRule disp_rule; + BackwardEulerFirstOrderTimeIntegrationRule temp_rule; + auto param_fields = registerParameterFields(youngs_modulus); + auto solid_fields = registerSolidMechanicsFields(field_store, disp_rule); + auto thermal_fields = registerThermalFields(field_store, temp_rule); + + auto [solid_system, solid_cycle_zero_system, solid_end_step_systems] = + buildSolidMechanicsSystem( + std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); + + auto [thermal_system, thermal_cycle_zero_system, thermal_end_step_systems] = + buildThermalSystem( + std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, + solid_fields); + + auto coupled = combineSystems(solid_system, thermal_system); + auto coupled_cycle_zero_system = combineSystems(solid_cycle_zero_system, thermal_cycle_zero_system); + auto end_step_systems = combineSystems(solid_end_step_systems, thermal_end_step_systems); + + GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); + + field_store->getParameterFields()[0].get()->setFromFieldFunction([=](smith::tensor) { return 100.0; }); + + solid_system->setDisplacementBC(mesh_->domain("left")); + thermal_system->setTemperatureBC(mesh_->domain("left")); + + solid_system->addTraction("right", + [=](double, auto X, auto, auto, auto, auto, auto temp, auto temperature_dot, auto) { + auto traction = 0.0 * X; + traction[0] = -0.015; + return traction; + }); + + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cycle_zero_system, end_step_systems); + + // Run forward + double dt = 1.0; + for (int step = 0; step < 2; ++step) { + physics->advanceTimestep(dt); + } + + auto reactions = physics->getReactionStates(); + auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); + + gretl::set_as_objective(obj); + obj.data_store().back_prop(); + + auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + EXPECT_TRUE(param_sens->Norml2() > 0.0); +} \ No newline at end of file diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index e7b0ba8ca1..d3eac170af 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -372,8 +372,7 @@ struct SolidMechanicsWithInternalVarsOptions {}; template auto registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_store, - DisplacementTimeRule disp_rule, - InternalVarTimeRule state_rule, + DisplacementTimeRule disp_rule, InternalVarTimeRule state_rule, FieldType... parameter_types) { registerSolidMechanicsFields(field_store, disp_rule); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index abc10dc745..14d80f4cc3 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -166,12 +166,12 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) // Compressive traction on right face. // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old) // — 6 self fields + 2 thermal coupling fields forwarded as trailing args. - solid->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, - auto /*temp_old*/) { - auto t = 0.0 * X; - t[0] = -0.005; - return t; - }); + solid->addTraction( + "right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, auto /*temp_old*/) { + auto t = 0.0 * X; + t[0] = -0.005; + return t; + }); // Phase 5: combine and solve. auto [combined, combined_cz] = combineSystems(solid, thermal, state_sys); From 8231f28a836ef5a97e6de5ec768237f2b810ffd4 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 11:32:49 -0600 Subject: [PATCH 25/67] Clean up some interface issues, rename cz to be clearer. --- .../combined_system.hpp | 16 +- .../coupling_params.hpp | 17 +- .../multiphysics_time_integrator.cpp | 8 +- .../nonlinear_block_solver.cpp | 39 +- .../solid_mechanics_system.hpp | 67 +-- ...id_mechanics_with_internal_vars_system.hpp | 6 +- .../state_variable_system.hpp | 2 +- .../differentiable_numerics/system_base.hpp | 2 +- .../tests/test_combined_thermo_mechanics.cpp | 385 ++++++------------ .../test_multiphysics_time_integrator.cpp | 20 +- .../tests/test_solid_dynamics.cpp | 54 ++- .../test_solid_static_with_internal_vars.cpp | 12 +- .../tests/test_thermal_static.cpp | 19 +- .../test_thermo_mechanics_three_system.cpp | 10 +- .../thermal_system.hpp | 58 +-- 15 files changed, 275 insertions(+), 440 deletions(-) diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index 2c52bc6bc5..e19e28f99f 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -137,16 +137,16 @@ std::pair, std::shared_ptr> combineS std::shared_ptr cycle_zero_combined = nullptr; if (!cycle_zero_subs.empty()) { - auto cz = std::make_shared(); - cz->field_store = field_store; - cz->max_stagger_iters = 1; // Cycle-zero solves are one-shot + auto cycle_zero = std::make_shared(); + cycle_zero->field_store = field_store; + cycle_zero->max_stagger_iters = 1; // Cycle-zero solves are one-shot for (auto& sub : cycle_zero_subs) { - cz->subsystems.push_back(sub); + cycle_zero->subsystems.push_back(sub); for (auto& wf : sub->weak_forms) { - cz->weak_forms.push_back(wf); + cycle_zero->weak_forms.push_back(wf); } } - cycle_zero_combined = cz; + cycle_zero_combined = cycle_zero; } return {combined, cycle_zero_combined}; @@ -195,8 +195,8 @@ std::pair, std::shared_ptr } if constexpr (requires { sub->cycle_zero_system; }) { if (sub->cycle_zero_system) { - for (auto& cz_wf : sub->cycle_zero_system->weak_forms) { - cycle_zero_wfs.push_back(cz_wf); + for (auto& cycle_zero_wf : sub->cycle_zero_system->weak_forms) { + cycle_zero_wfs.push_back(cycle_zero_wf); } } } diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 445dc3177f..73080acdbc 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -155,18 +155,19 @@ auto collectPhysicsFromPack(const Pack& pack) } } -/// Extract parameter fields (CouplingParams packs that are NOT PhysicsFields), with "param_" prefix. +/// Extract parameter fields (CouplingParams packs that are NOT PhysicsFields) with fully-qualified +/// names matching what the FieldStore uses: `{store_prefix}param_{name}`. template -auto collectParamsFromPack(const Pack& pack) +auto collectParamsFromPack(const std::shared_ptr& fs, const Pack& pack) { if constexpr (is_coupling_params_v> && !is_physics_fields_v) { return std::apply( - [](auto... pts) { - auto prefix = [](auto pt) { - pt.name = "param_" + pt.name; + [&](auto... pts) { + auto qualify = [&](auto pt) { + pt.name = fs->prefix("param_" + pt.name); return pt; }; - return std::make_tuple(prefix(pts)...); + return std::make_tuple(qualify(pts)...); }, pack.fields); } else { @@ -177,10 +178,10 @@ auto collectParamsFromPack(const Pack& pack) /// Collect non-self fields from all packs into a single CouplingParams. /// Order: physics coupling fields first (in pack order), then parameter fields. template -auto collectCouplingFields(const Packs&... packs) +auto collectCouplingFields(const std::shared_ptr& fs, const Packs&... packs) { auto physics = std::tuple_cat(collectPhysicsFromPack(packs)...); - auto params = std::tuple_cat(collectParamsFromPack(packs)...); + auto params = std::tuple_cat(collectParamsFromPack(fs, packs)...); auto combined = std::tuple_cat(physics, params); return std::apply([](auto&... all) { return CouplingParams{all...}; }, combined); } diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index f5908422a5..c456a8d218 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -55,19 +55,19 @@ std::pair, std::vector> MultiphysicsTimeI }); if (time_info.cycle() == 0 && cycle_zero_system_ && requires_cycle_zero_solve) { - auto cz_unknowns = cycle_zero_system_->solve(time_info); + auto cycle_zero_unknowns = cycle_zero_system_->solve(time_info); // Cycle zero system solves for the initial acceleration, but by convention the solved value // is returned through the first (and only) block of the cycle-zero subsystem — the weak form // uses an aliased unknown trial space that matches the acceleration test space. Copy that // single result into the acceleration state slot for the main solve. - SLIC_ERROR_ROOT_IF(cz_unknowns.size() != 1, + SLIC_ERROR_ROOT_IF(cycle_zero_unknowns.size() != 1, "Cycle zero system is expected to be a single-block solve producing one unknown"); std::string test_field_name = system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms.front()->name()); size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); - current_states[test_field_state_idx] = cz_unknowns[0]; - system_->field_store->setField(test_field_state_idx, cz_unknowns[0]); + current_states[test_field_state_idx] = cycle_zero_unknowns[0]; + system_->field_store->setField(test_field_state_idx, cycle_zero_unknowns[0]); } std::vector primary_unknowns = system_->solve(time_info); diff --git a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp index b0ec967b1c..eceff3ac72 100644 --- a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp +++ b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp @@ -41,27 +41,6 @@ double skewMatrixNorm(std::unique_ptr& K) return Hfronorm; } -/// @brief Initialize mfem solver if near-nullspace is needed -void initializeSolver(mfem::Solver* mfem_solver, const smith::FiniteElementState& u) -{ - // If the user wants the AMG preconditioner with a linear solver, set the pfes - // to be the displacement - auto* amg_prec = dynamic_cast(mfem_solver); - if (amg_prec) { - amg_prec->SetSystemsOptions(u.space().GetVDim(), smith::ordering == mfem::Ordering::byNODES); - } - -#ifdef SMITH_USE_PETSC - auto* space_dep_pc = dynamic_cast(mfem_solver); - if (space_dep_pc) { - // This call sets the displacement ParFiniteElementSpace used to get the spatial coordinates and to - // generate the near null space for the PCGAMG preconditioner - mfem::ParFiniteElementSpace* space = const_cast(&u.space()); - space_dep_pc->SetFESpace(space); - } -#endif -} - NonlinearBlockSolver::NonlinearBlockSolver(std::unique_ptr s, MPI_Comm comm, double abs_tol, double rel_tol, std::optional retained_nonlinear_options, @@ -99,7 +78,23 @@ void NonlinearBlockSolver::completeSetup(const std::vector& us) for (const auto& u : us) { if (u.space().GetVDim() > best->space().GetVDim()) best = &u; } - initializeSolver(&nonlinear_solver_->preconditioner(), *best); + + auto* mfem_solver = &nonlinear_solver_->preconditioner(); + + auto* amg_prec = dynamic_cast(mfem_solver); + if (amg_prec) { + amg_prec->SetSystemsOptions(best->space().GetVDim(), smith::ordering == mfem::Ordering::byNODES); + } + +#ifdef SMITH_USE_PETSC + auto* space_dep_pc = dynamic_cast(mfem_solver); + if (space_dep_pc) { + // This call sets the displacement ParFiniteElementSpace used to get the spatial coordinates and to + // generate the near null space for the PCGAMG preconditioner + auto* space = const_cast(&best->space()); + space_dep_pc->SetFESpace(space); + } +#endif } ConvergenceStatus NonlinearBlockSolver::convergenceStatus(double tolerance_multiplier, diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 8908bf21c4..70c1efec0b 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -380,46 +380,6 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store) return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}); } -/** - * @brief Register solid mechanics fields with parameters (no rule instance). - * - * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). - */ -template - requires(sizeof...(parameter_space) > 0) -auto registerSolidMechanicsFields(std::shared_ptr field_store, - FieldType... parameter_types) -{ - return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}, std::move(parameter_types)...); -} - -/** - * @brief Register all solid mechanics fields into a FieldStore (with parameters). - * - * Legacy overload that also registers parameter fields directly. - * Prefer the no-params overload + registerParameterFields for new code. - * - * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). - */ -template - requires(sizeof...(parameter_space) > 0) -auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule rule, - FieldType... parameter_types) -{ - registerSolidMechanicsFields(field_store, rule); - - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - - return CouplingParams{FieldType>(field_store->prefix("displacement_solve_state")), - FieldType>(field_store->prefix("displacement")), - FieldType>(field_store->prefix("velocity")), - FieldType>(field_store->prefix("acceleration")), parameter_types...}; -} - /** * @brief Build a SolidMechanicsSystem with coupling, assuming fields are already registered. * @@ -483,19 +443,19 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, .absolute_tol = 1e-14, .max_iterations = 2, .print_level = 0}; - LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, .preconditioner = Preconditioner::HypreJacobi, .relative_tol = 1e-14, .absolute_tol = 1e-14, .max_iterations = 1000, .print_level = 0}; auto cycle_zero_solver = - std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); + std::make_shared(buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; @@ -570,10 +530,10 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, const Co auto combined = std::apply([&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); - auto [sys, cz, ends] = + auto [sys, cycle_zero, ends] = buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, combined, solver, options); using SysType = typename decltype(sys)::element_type; - return SystemBuildResult{std::move(sys), std::move(cz), std::move(ends)}; + return SystemBuildResult{std::move(sys), std::move(cycle_zero), std::move(ends)}; } /** @@ -585,10 +545,23 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, const Co * * Usage: * @code - * auto [solid, cz, end] = buildSolidMechanicsSystem( + * auto [solid, cycle_zero, end] = buildSolidMechanicsSystem( * solver, opts, param_fields, solid_fields, thermal_fields); * @endcode */ +/** + * @brief Build a standalone SolidMechanicsSystem with no coupling and no parameters. + * + * Convenience overload for single-physics tests and demos — avoids the `CouplingParams<>{}` + * placeholder at the call site. + */ +template +auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::shared_ptr solver, + const SolidMechanicsOptions& options) +{ + return buildSolidMechanicsSystem(field_store, CouplingParams<>{}, solver, options); +} + template requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && !(std::is_same_v, SolidMechanicsOptions> || ...)) @@ -597,7 +570,7 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid { auto field_store = detail::findFieldStore(field_packs...); (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_packs...); + auto coupling = detail::collectCouplingFields(field_store, field_packs...); return buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, coupling, solver, options); } diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index d3eac170af..c5751cb91a 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -479,19 +479,19 @@ auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr field field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - NonlinearSolverOptions cz_nonlin{.nonlin_solver = NonlinearSolver::Newton, + NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, .absolute_tol = 1e-14, .max_iterations = 2, .print_level = 0}; - LinearSolverOptions cz_lin{.linear_solver = LinearSolver::CG, + LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, .preconditioner = Preconditioner::HypreJacobi, .relative_tol = 1e-14, .absolute_tol = 1e-14, .max_iterations = 1000, .print_level = 0}; auto cycle_zero_solver = - std::make_shared(buildNonlinearBlockSolver(cz_nonlin, cz_lin, *field_store->getMesh())); + std::make_shared(buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 8d139d92cd..8c4cb54256 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -12,7 +12,7 @@ * auto state_coupling_fields = registerStateVariableFields( * field_store, state_rule, params...); * - * auto [state_sys, cz, ends] = buildStateVariableSystem( + * auto [state_sys, cycle_zero, ends] = buildStateVariableSystem( * field_store, state_rule, [solid_coupling,] solver, opts, params...); * * The returned CouplingParams from registerStateVariableFields carries field tokens diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index b8bcbed465..f3d240e3b3 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -92,7 +92,7 @@ inline std::shared_ptr makeSystem(std::shared_ptr field_ /** * @brief Return type for physics system build functions. * - * Replaces the previous std::tuple return so callers can write + * Replaces the previous std::tuple return so callers can write * @code * auto res = buildSolidMechanicsSystem(...); * auto system = res.system; diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index d550e05854..43e58871fc 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -31,6 +31,9 @@ static constexpr int dim = 3; static constexpr int displacement_order = 1; static constexpr int temperature_order = 1; +using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; +using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + template auto greenStrain(const tensor& grad_u) { @@ -107,48 +110,83 @@ struct ThermoMechanicsMeshFixture : public testing::Test { mfem::Mesh::MakeCartesian3D(24, 2, 2, mfem::Element::HEXAHEDRON, 1.2, 0.03, 0.03), "mesh", 0, 0); mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); + field_store_ = std::make_shared(mesh_, 100, ""); + } + + std::shared_ptr makeSolver(const NonlinearSolverOptions& nonlin, const LinearSolverOptions& lin) + { + return std::make_shared(buildNonlinearBlockSolver(nonlin, lin, *mesh_)); + } + + // Advance one step, return final states + lateral deflection. + template + double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, std::shared_ptr coupled_cycle_zero, + double dt = 1.0) + { + auto shape_disp = field_store_->getShapeDisp(); + auto states = field_store_->getStateFields(); + auto params = field_store_->getParameterFields(); + std::vector reactions; + std::tie(states, reactions) = + makeAdvancer(coupled, coupled_cycle_zero)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, states, params); + + mfem::Vector final_disp(*states[field_store_->getFieldIndex("displacement_solve_state")].get()); + double deflection = 0.0; + for (int i = 1; i < final_disp.Size(); i += dim) { + deflection = std::max(deflection, std::abs(final_disp(i))); + } + return deflection; } + + template + void applyBucklingLoads(Solid& solid, Thermal& thermal, double compressive_traction, double lateral_body_force, + double thermal_source) + { + solid->setDisplacementBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("right")); + + solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -compressive_traction; + return traction; + }); + solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { + auto force = 0.0 * X; + force[1] = lateral_body_force; + return force; + }); + thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); + } + std::unique_ptr datastore_; std::shared_ptr mesh_; + std::shared_ptr field_store_; }; +// Defaults used by multiple tests. +static const LinearSolverOptions directLinOpts{.linear_solver = LinearSolver::SuperLU}; +static const NonlinearSolverOptions newtonNonlinOpts{ + .nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-10, .absolute_tol = 1e-10, .max_iterations = 4}; + // 1. CreateDifferentiablePhysicsAllocatesReactionInfo TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionInfo) { - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto solid_coupling_fields = - registerSolidMechanicsFields(field_store, youngs_modulus); - auto thermal_coupling_fields = registerThermalFields(field_store); + auto param_fields = registerParameterFields(youngs_modulus); + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); - auto solid_res = buildSolidMechanicsSystem( - field_store, thermal_coupling_fields, std::make_shared(solid_block_solver), solid_opts, - youngs_modulus); - auto solid = solid_res.system; + auto [solid, solid_cycle_zero, solid_end_steps] = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, param_fields, solid_fields, thermal_fields); - auto thermal_res = buildThermalSystem( - field_store, solid_coupling_fields, std::make_shared(thermal_block_solver), thermal_opts); - auto thermal = thermal_res.system; + auto [thermal, thermal_cycle_zero, thermal_end_steps] = buildThermalSystem( + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, param_fields, thermal_fields, solid_fields); - auto [coupled, coupled_cz] = combineSystems(solid, thermal); + auto [coupled, coupled_cycle_zero] = combineSystems(solid, thermal); - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cycle_zero); const auto& solid_dual_space = physics->dual("reactions").space(); const auto& solid_state_space = physics->state("displacement_solve_state").space(); const auto& thermal_dual_space = physics->dual("thermal_flux").space(); @@ -170,35 +208,19 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI // 2. BackpropagateThroughPhysics TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) { - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); FieldType> youngs_modulus("youngs_modulus"); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - auto param_fields = registerParameterFields(youngs_modulus); - auto solid_fields = registerSolidMechanicsFields(field_store); - auto thermal_fields = registerThermalFields(field_store); + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); - auto [solid, solid_cz, solid_end_steps] = buildSolidMechanicsSystem( - std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); + auto [solid, solid_cycle_zero, solid_end_steps] = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, param_fields, solid_fields, thermal_fields); - auto [thermal, thermal_cz, thermal_end_steps] = buildThermalSystem( - std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, solid_fields); + auto [thermal, thermal_cycle_zero, thermal_end_steps] = buildThermalSystem( + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, param_fields, thermal_fields, solid_fields); - auto [coupled, coupled_cz] = combineSystems(solid, thermal); + auto [coupled, coupled_cycle_zero] = combineSystems(solid, thermal); ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -215,9 +237,8 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) return traction; }); - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cz); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cycle_zero); - // Run forward double dt = 1.0; for (int step = 0; step < 2; ++step) { physics->advanceTimestep(dt); @@ -233,215 +254,83 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) EXPECT_TRUE(param_sens->Norml2() > 0.0); } +// Shared buckling-load magnitudes (used by staggered + monolithic buckling tests). +static constexpr double kBucklingTraction = 0.015; +static constexpr double kBucklingBodyForce = 2.5e-5; +static constexpr double kBucklingHeatSource = 1.0; + // 3. StaggeredBucklingChallenge -// Replaces MonolithicBucklingChallenge: verifies convergence and displacement of the staggered combined solver. TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) { - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; - - smith::LinearSolverOptions mech_lin_opts{.linear_solver = smith::LinearSolver::CG, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 120, - .print_level = 0}; - smith::NonlinearSolverOptions mech_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::TrustRegion, - .relative_tol = 1e-6, - .absolute_tol = 1e-7, - .max_iterations = 25, - .print_level = 0}; - - smith::LinearSolverOptions therm_lin_opts{.linear_solver = smith::LinearSolver::GMRES, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 80, - .print_level = 0}; - smith::NonlinearSolverOptions therm_nonlin_opts{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, - .relative_tol = 1e-7, - .absolute_tol = 1e-7, - .max_iterations = 12, - .max_line_search_iterations = 6, - .print_level = 0}; - - auto solid_block_solver = buildNonlinearBlockSolver(mech_nonlin_opts, mech_lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(therm_nonlin_opts, therm_lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto thermal_coupling = registerSolidMechanicsFields(field_store); - auto solid_coupling = registerThermalFields(field_store); - - auto solid_res = buildSolidMechanicsSystem( - field_store, solid_coupling, std::make_shared(solid_block_solver), solid_opts); - auto solid = solid_res.system; - - auto thermal_res = buildThermalSystem( - field_store, thermal_coupling, std::make_shared(thermal_block_solver), thermal_opts); - auto thermal = thermal_res.system; - - auto [coupled, coupled_cz] = combineSystems(solid, thermal); + LinearSolverOptions mech_lin_opts{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 120}; + NonlinearSolverOptions mech_nonlin_opts{ + .nonlin_solver = NonlinearSolver::TrustRegion, .relative_tol = 1e-6, .absolute_tol = 1e-7, .max_iterations = 25}; + LinearSolverOptions therm_lin_opts{.linear_solver = LinearSolver::GMRES, + .preconditioner = Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80}; + NonlinearSolverOptions therm_nonlin_opts{.nonlin_solver = NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-7, + .absolute_tol = 1e-7, + .max_iterations = 12, + .max_line_search_iterations = 6}; + + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); + + auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( + field_store_, thermal_fields, makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}); + auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( + field_store_, solid_fields, makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}); + + auto [coupled, coupled_cycle_zero] = combineSystems(solid, thermal); ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); + applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -compressive_traction; - return traction; - }); - solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); - - SLIC_INFO_ROOT("Starting staggered thermo-mechanics solve"); + double deflection = advanceOneStepAndGetLateralDeflection(coupled, coupled_cycle_zero); - double dt = 1.0; - double time = 0.0; - auto shape_disp = field_store->getShapeDisp(); - auto states = field_store->getStateFields(); - auto params = field_store->getParameterFields(); - std::vector reactions; - for (size_t step = 0; step < 1; ++step) { - std::tie(states, reactions) = - makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); - time += dt; - } - - mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); - - bool staggered_solid_converged = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int staggered_solid_iterations = solid_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - bool staggered_thermal_converged = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int staggered_thermal_iterations = thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); - - double staggered_lateral_deflection = 0.0; - for (int i = 1; i < final_disp.Size(); i += dim) { - staggered_lateral_deflection = std::max(staggered_lateral_deflection, std::abs(final_disp(i))); - } - - SLIC_INFO_ROOT("Staggered solid converged: " << staggered_solid_converged - << ", iterations: " << staggered_solid_iterations); - SLIC_INFO_ROOT("Staggered thermal converged: " << staggered_thermal_converged - << ", iterations: " << staggered_thermal_iterations); - SLIC_INFO_ROOT("Staggered max lateral deflection: " << staggered_lateral_deflection); - - EXPECT_TRUE(staggered_solid_converged); - EXPECT_TRUE(staggered_thermal_converged); - EXPECT_GT(staggered_lateral_deflection, 1e-5); + EXPECT_GT(deflection, 1e-5); } // 4. MonolithicBucklingChallenge -// Verifies convergence and displacement of the monolithic combined solver. TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) { - constexpr double compressive_traction = 0.015; - constexpr double lateral_body_force = 2.5e-5; - constexpr double thermal_source = 1.0; - - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 80, - .print_level = 0}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-7, - .absolute_tol = 1e-7, - .max_iterations = 12, - .print_level = 0}; - - auto block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto solver_ptr = std::make_shared(block_solver); + LinearSolverOptions lin_opts{ + .linear_solver = LinearSolver::SuperLU, .relative_tol = 1e-6, .absolute_tol = 1e-10, .max_iterations = 80}; + NonlinearSolverOptions nonlin_opts{ + .nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-7, .absolute_tol = 1e-7, .max_iterations = 12}; - auto field_store = std::make_shared(mesh_, 100, ""); + auto solver_ptr = makeSolver(nonlin_opts, lin_opts); - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); - // Notice that the block_solver is the SAME solver for the whole system + auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( + field_store_, thermal_fields, nullptr, SolidMechanicsOptions{}); + auto [thermal, thermal_cycle_zero, thermal_end] = + buildThermalSystem(field_store_, solid_fields, nullptr, ThermalOptions{}); - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - auto thermal_coupling = registerSolidMechanicsFields(field_store); - auto solid_coupling = registerThermalFields(field_store); - - auto solid_res = - buildSolidMechanicsSystem(field_store, solid_coupling, nullptr, solid_opts); - auto solid = solid_res.system; - - auto thermal_res = - buildThermalSystem(field_store, thermal_coupling, nullptr, thermal_opts); - auto thermal = thermal_res.system; - - auto [coupled, coupled_cz] = combineSystems(solver_ptr, solid, thermal); + auto [coupled, coupled_cycle_zero] = combineSystems(solver_ptr, solid, thermal); ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); - solid->disp_bc->setFixedVectorBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("left")); - thermal->temperature_bc->setFixedScalarBCs(mesh_->domain("right")); - - solid->addTraction("right", [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -compressive_traction; - return traction; - }); - solid->addBodyForce(mesh_->entireBodyName(), [=](auto, auto X, auto, auto, auto, auto, auto... /*args*/) { - auto force = 0.0 * X; - force[1] = lateral_body_force; - return force; - }); - thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return thermal_source; }); - - SLIC_INFO_ROOT("Starting monolithic thermo-mechanics solve"); - - double dt = 1.0; - double time = 0.0; - auto shape_disp = field_store->getShapeDisp(); - auto states = field_store->getStateFields(); - auto params = field_store->getParameterFields(); - std::vector reactions; - for (size_t step = 0; step < 1; ++step) { - std::tie(states, reactions) = - makeAdvancer(coupled, coupled_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); - time += dt; - } - - mfem::Vector final_disp(*states[field_store->getFieldIndex("displacement_solve_state")].get()); + applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - bool converged = block_solver->nonlinear_solver_->nonlinearSolver().GetConverged(); - int iterations = block_solver->nonlinear_solver_->nonlinearSolver().GetNumIterations(); + double deflection = advanceOneStepAndGetLateralDeflection(coupled, coupled_cycle_zero); - double lateral_deflection = 0.0; - for (int i = 1; i < final_disp.Size(); i += dim) { - lateral_deflection = std::max(lateral_deflection, std::abs(final_disp(i))); - } - - SLIC_INFO_ROOT("Monolithic converged: " << converged << ", iterations: " << iterations); - SLIC_INFO_ROOT("Monolithic max lateral deflection: " << lateral_deflection); - - EXPECT_TRUE(converged); - EXPECT_GT(lateral_deflection, 1e-5); + EXPECT_GT(deflection, 1e-5); } -// Simple linear elastic material for stress output test (file scope — templates cannot be in local classes) +// Simple linear elastic material for stress output test. struct StressOutputLinearElastic { double density = 1.0; using State = Empty; @@ -458,26 +347,13 @@ struct StressOutputLinearElastic { }; // 5. CauchyStressOutput -// Verifies that enable_stress_output + output_cauchy_stress writes a non-zero stress field. TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) { - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 6}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - - using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; - SolidMechanicsOptions solid_opts{.enable_stress_output = true, .output_cauchy_stress = true}; - registerSolidMechanicsFields(field_store); - auto [sys, cz, end_steps] = buildSolidMechanicsSystem( - field_store, CouplingParams<>{}, std::make_shared(solid_block_solver), solid_opts); + registerSolidMechanicsFields(field_store_); + auto [sys, cycle_zero, end_steps] = buildSolidMechanicsSystem( + field_store_, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts); sys->setMaterial(StressOutputLinearElastic{}, mesh_->entireBodyName()); @@ -488,13 +364,12 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) return t; }); - auto physics = makeDifferentiablePhysics(sys, "cauchy_physics", cz, end_steps); + auto physics = makeDifferentiablePhysics(sys, "cauchy_physics", cycle_zero, end_steps); physics->advanceTimestep(1.0); - // The stress projection system should have run and produced a non-zero stress field. ASSERT_FALSE(end_steps.empty()) << "Stress output system should be present"; auto states = physics->getFieldStates(); - size_t stress_idx = field_store->getFieldIndex("stress_solve_state"); + size_t stress_idx = field_store_->getFieldIndex("stress_solve_state"); double stress_norm = norm(*states[stress_idx].get()); EXPECT_GT(stress_norm, 1e-8) << "Cauchy stress field should be non-zero after deformation"; } diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 94ece37443..520ddff6f3 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -175,12 +175,12 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroUsesBcsForReactionFieldNotUnknownZero) main_system->weak_forms = {temperature_wf, displacement_wf}; main_system->solver = main_solver; - auto cz_system = std::make_shared(); - cz_system->field_store = field_store; - cz_system->weak_forms = {cycle_zero_wf}; - cz_system->solver = cycle_zero_solver; + auto cycle_zero_system = std::make_shared(); + cycle_zero_system->field_store = field_store; + cycle_zero_system->weak_forms = {cycle_zero_wf}; + cycle_zero_system->solver = cycle_zero_solver; - MultiphysicsTimeIntegrator advancer(main_system, cz_system); + MultiphysicsTimeIntegrator advancer(main_system, cycle_zero_system); auto [new_states, reactions] = advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); @@ -231,12 +231,12 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) main_system->weak_forms = {main_wf}; main_system->solver = main_solver; - auto cz_system = std::make_shared(); - cz_system->field_store = field_store; - cz_system->weak_forms = {cycle_zero_wf}; - cz_system->solver = cycle_zero_solver; + auto cycle_zero_system = std::make_shared(); + cycle_zero_system->field_store = field_store; + cycle_zero_system->weak_forms = {cycle_zero_wf}; + cycle_zero_system->solver = cycle_zero_solver; - MultiphysicsTimeIntegrator advancer(main_system, cz_system); + MultiphysicsTimeIntegrator advancer(main_system, cycle_zero_system); auto [new_states, reactions] = advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index b6ed5227b0..3f4c00d23f 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -96,6 +96,30 @@ struct SolidMechanicsMeshFixture : public testing::Test { std::shared_ptr mesh; }; +// Verifies the cycle-zero contract: rules that report requiresInitialAccelerationSolve() +// produce a non-null cycle_zero_system; rules that don't (QuasiStatic) produce nullptr. +TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) +{ + auto solver = std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + + { + auto field_store = std::make_shared(mesh, 100, "impl"); + using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + registerSolidMechanicsFields(field_store); + auto [sys, cycle_zero, ends] = + buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); + EXPECT_NE(cycle_zero, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; + } + { + auto field_store = std::make_shared(mesh, 100, "qs"); + using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; + registerSolidMechanicsFields(field_store); + auto [sys, cycle_zero, ends] = + buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); + EXPECT_EQ(cycle_zero, nullptr) << "QuasiStatic has no initial acceleration solve"; + } +} + TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; @@ -106,15 +130,12 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto field_store = std::make_shared(mesh, 100, ""); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - registerSolidMechanicsFields(field_store, FieldType("bulk"), - FieldType("shear")); + auto param_fields = registerParameterFields(FieldType("bulk"), + FieldType("shear")); + auto solid_fields = registerSolidMechanicsFields(field_store); - auto solid_res = buildSolidMechanicsSystem( - field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, - FieldType("bulk"), FieldType("shear")); - auto system = solid_res.system; - auto cz_sys = solid_res.cycle_zero_system; - auto end_steps = solid_res.end_step_systems; + auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsSystem( + coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, param_fields, solid_fields); static constexpr double gravity = -9.0; @@ -156,7 +177,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) size_t cycle = 0; std::vector reactions; - auto advancer = makeAdvancer(system, cz_sys, end_steps); + auto advancer = makeAdvancer(system, cycle_zero_sys, end_steps); for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); @@ -216,17 +237,14 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(mesh, 100, physics_name); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - registerSolidMechanicsFields(field_store, FieldType("bulk"), - FieldType("shear")); + auto param_fields = registerParameterFields(FieldType("bulk"), + FieldType("shear")); + auto solid_fields = registerSolidMechanicsFields(field_store); - auto solid_res = buildSolidMechanicsSystem( - field_store, CouplingParams<>{}, coupled_solver, SolidMechanicsOptions{}, FieldType("bulk"), - FieldType("shear")); - auto system = solid_res.system; - auto cz_sys = solid_res.cycle_zero_system; - auto end_steps = solid_res.end_step_systems; + auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsSystem( + coupled_solver, SolidMechanicsOptions{}, param_fields, solid_fields); - auto physics = makeDifferentiablePhysics(system, physics_name, cz_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, physics_name, cycle_zero_sys, end_steps); auto bcs = system->disp_bc; bcs->setFixedVectorBCs(mesh->domain("right")); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 675277b986..36e1c6bd17 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -113,7 +113,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) BackwardEulerFirstOrderTimeIntegrationRule state_rule; registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); - auto [system, cz_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( + auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( field_store, disp_rule, state_rule, coupled_solver, SolidMechanicsWithInternalVarsOptions{}); // Material and Evolution @@ -133,7 +133,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) return u; }); - auto physics = makeDifferentiablePhysics(system, "physics", cz_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, "physics", cycle_zero_sys, end_steps); // Create ParaView writer auto writer = createParaviewWriter(*mesh, system->field_store->getOutputFieldStates(), "solid_state_output"); @@ -163,7 +163,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) BackwardEulerFirstOrderTimeIntegrationRule state_rule; registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); - auto [system, cz_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( + auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( field_store, disp_rule, state_rule, staggered_solver, SolidMechanicsWithInternalVarsOptions{}); system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); @@ -177,7 +177,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) return u; }); - auto physics = makeDifferentiablePhysics(system, "physics_relaxed", cz_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, "physics_relaxed", cycle_zero_sys, end_steps); for (int step = 1; step <= 3; ++step) { physics->advanceTimestep(1.0); SLIC_INFO("Staggered relaxation step " << step << " completed"); @@ -194,7 +194,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) BackwardEulerFirstOrderTimeIntegrationRule state_rule; registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); - auto [system, cz_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( + auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( field_store, disp_rule, state_rule, coupled_solver, SolidMechanicsWithInternalVarsOptions{}); system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); @@ -219,7 +219,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) return t; }); - auto physics = makeDifferentiablePhysics(system, "physics_bf", cz_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, "physics_bf", cycle_zero_sys, end_steps); physics->advanceTimestep(1.0); // Check that the displacement field is non-zero (the body force + traction produced deformation) diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 90af7ca512..e19d1095b2 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -56,10 +56,9 @@ struct ThermalStaticFixture : public testing::Test { using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; registerThermalFields<2, temp_order, TempRule>(field_store); - auto thermal_res = - buildThermalSystem<2, temp_order, TempRule>(field_store, CouplingParams<>{}, coupled_solver, ThermalOptions{}); + auto thermal_res = buildThermalSystem<2, temp_order, TempRule>(field_store, coupled_solver, ThermalOptions{}); auto thermal_system = thermal_res.system; - auto cz_sys = thermal_res.cycle_zero_system; + auto cycle_zero_sys = thermal_res.cycle_zero_system; auto end_steps = thermal_res.end_step_systems; double k = 1.0; @@ -80,7 +79,7 @@ struct ThermalStaticFixture : public testing::Test { [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system, cz_sys, end_steps) + auto [new_states, reactions] = makeAdvancer(thermal_system, cycle_zero_sys, end_steps) ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); @@ -153,13 +152,11 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto field_store = std::make_shared(mesh, 100, ""); using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; - registerThermalFields<2, 1, TempRule>(field_store, conductivity_param); + auto param_fields = registerParameterFields(conductivity_param); + auto thermal_fields = registerThermalFields<2, 1, TempRule>(field_store); - auto thermal_res = buildThermalSystem<2, 1, TempRule>(field_store, CouplingParams<>{}, coupled_solver, - ThermalOptions{}, conductivity_param); - auto thermal_system = thermal_res.system; - auto cz_sys = thermal_res.cycle_zero_system; - auto end_steps = thermal_res.end_step_systems; + auto [thermal_system, cycle_zero_sys, end_steps] = + buildThermalSystem<2, 1, TempRule>(coupled_solver, ThermalOptions{}, param_fields, thermal_fields); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -185,7 +182,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system, cz_sys, end_steps) + auto [new_states, reactions] = makeAdvancer(thermal_system, cycle_zero_sys, end_steps) ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index 14d80f4cc3..b8987d3ce7 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -96,15 +96,15 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) // Phase 2: build each system. // Solid receives thermal coupling (temperature_solve_state, temperature). - auto [solid, solid_cz, solid_end] = buildSolidMechanicsSystem( + auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( field_store, thermal_exported, std::make_shared(solid_block_solver), SolidMechanicsOptions{}); // Thermal is standalone (no coupling back from solid for this test). - auto [thermal, thermal_cz, thermal_end] = buildThermalSystem( + auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( field_store, CouplingParams<>{}, std::make_shared(thermal_block_solver), ThermalOptions{}); // StateVariable receives solid displacement coupling (4 fields: disp_ss, displacement, velocity, acceleration). - auto [state_sys, state_cz, state_end] = buildStateVariableSystem( + auto [state_sys, state_cycle_zero, state_end] = buildStateVariableSystem( field_store, state_rule, solid_exported, std::make_shared(state_block_solver), StateVariableOptions{}); @@ -174,7 +174,7 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) }); // Phase 5: combine and solve. - auto [combined, combined_cz] = combineSystems(solid, thermal, state_sys); + auto [combined, combined_cycle_zero] = combineSystems(solid, thermal, state_sys); double dt = 1.0, time = 0.0; auto shape_disp = field_store->getShapeDisp(); @@ -184,7 +184,7 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) for (size_t step = 0; step < 2; ++step) { std::tie(states, reactions) = - makeAdvancer(combined, combined_cz)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + makeAdvancer(combined, combined_cycle_zero)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 5387dd06cd..49b6cf01b7 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -202,43 +202,6 @@ auto registerThermalFields(std::shared_ptr field_store) return registerThermalFields(field_store, TemperatureTimeRule{}); } -/** - * @brief Register thermal fields with parameters (no rule instance). - * - * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). - */ -template - requires(sizeof...(parameter_space) > 0) -auto registerThermalFields(std::shared_ptr field_store, FieldType... parameter_types) -{ - return registerThermalFields(field_store, TemperatureTimeRule{}, std::move(parameter_types)...); -} - -/** - * @brief Register all thermal fields into a FieldStore (with parameters). - * - * Legacy overload that also registers parameter fields directly. - * Prefer the no-params overload + registerParameterFields for new code. - * - * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). - */ -template - requires(sizeof...(parameter_space) > 0) -auto registerThermalFields(std::shared_ptr field_store, TemperatureTimeRule rule, - FieldType... parameter_types) -{ - registerThermalFields(field_store, rule); - - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - - return CouplingParams{FieldType>(field_store->prefix("temperature_solve_state")), - FieldType>(field_store->prefix("temperature")), parameter_types...}; -} - /** * @brief Build a ThermalSystem with coupling, assuming fields are already registered. * @@ -314,10 +277,10 @@ auto buildThermalSystem(std::shared_ptr field_store, const Coupling& auto combined = std::apply([&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); - auto [sys, cz, ends] = + auto [sys, cycle_zero, ends] = buildThermalSystem(field_store, TemperatureTimeRule{}, combined, solver, options); using SysType = typename decltype(sys)::element_type; - return SystemBuildResult{std::move(sys), std::move(cz), std::move(ends)}; + return SystemBuildResult{std::move(sys), std::move(cycle_zero), std::move(ends)}; } /** @@ -329,10 +292,23 @@ auto buildThermalSystem(std::shared_ptr field_store, const Coupling& * * Usage: * @code - * auto [thermal, cz, end] = buildThermalSystem( + * auto [thermal, cycle_zero, end] = buildThermalSystem( * solver, opts, param_fields, thermal_fields, solid_fields); * @endcode */ +/** + * @brief Build a standalone ThermalSystem with no coupling and no parameters. + * + * Convenience overload for single-physics tests and demos — avoids the `CouplingParams<>{}` + * placeholder at the call site. + */ +template +auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr solver, + const ThermalOptions& options) +{ + return buildThermalSystem(field_store, CouplingParams<>{}, solver, options); +} + template requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && !(std::is_same_v, ThermalOptions> || ...)) @@ -341,7 +317,7 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio { auto field_store = detail::findFieldStore(field_packs...); (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_packs...); + auto coupling = detail::collectCouplingFields(field_store, field_packs...); return buildThermalSystem(field_store, TemperatureTimeRule{}, coupling, solver, options); } From cbc7ed78496c7571f2f4383eaa17faf67aa07b67 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 11:33:03 -0600 Subject: [PATCH 26/67] Fix style. --- .../solid_mechanics_system.hpp | 22 +++++++++---------- ...id_mechanics_with_internal_vars_system.hpp | 22 +++++++++---------- .../tests/test_combined_thermo_mechanics.cpp | 8 +++---- .../tests/test_solid_dynamics.cpp | 11 +++++----- .../test_thermo_mechanics_three_system.cpp | 4 ++-- 5 files changed, 34 insertions(+), 33 deletions(-) diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 70c1efec0b..fdff7d26a7 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -444,18 +444,18 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - auto cycle_zero_solver = - std::make_shared(buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + auto cycle_zero_solver = std::make_shared( + buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index c5751cb91a..cd27fef608 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -480,18 +480,18 @@ auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr field field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - auto cycle_zero_solver = - std::make_shared(buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + auto cycle_zero_solver = std::make_shared( + buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); cycle_zero_system = sys->cycle_zero_system; diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 43e58871fc..61875a3a50 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -120,15 +120,15 @@ struct ThermoMechanicsMeshFixture : public testing::Test { // Advance one step, return final states + lateral deflection. template - double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, std::shared_ptr coupled_cycle_zero, - double dt = 1.0) + double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, + std::shared_ptr coupled_cycle_zero, double dt = 1.0) { auto shape_disp = field_store_->getShapeDisp(); auto states = field_store_->getStateFields(); auto params = field_store_->getParameterFields(); std::vector reactions; - std::tie(states, reactions) = - makeAdvancer(coupled, coupled_cycle_zero)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, states, params); + std::tie(states, reactions) = makeAdvancer(coupled, coupled_cycle_zero) + ->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, states, params); mfem::Vector final_disp(*states[field_store_->getFieldIndex("displacement_solve_state")].get()); double deflection = 0.0; diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 3f4c00d23f..93e289f14b 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -100,7 +100,8 @@ struct SolidMechanicsMeshFixture : public testing::Test { // produce a non-null cycle_zero_system; rules that don't (QuasiStatic) produce nullptr. TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { - auto solver = std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); { auto field_store = std::make_shared(mesh, 100, "impl"); @@ -130,8 +131,8 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto field_store = std::make_shared(mesh, 100, ""); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - auto param_fields = registerParameterFields(FieldType("bulk"), - FieldType("shear")); + auto param_fields = + registerParameterFields(FieldType("bulk"), FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsSystem( @@ -237,8 +238,8 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(mesh, 100, physics_name); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - auto param_fields = registerParameterFields(FieldType("bulk"), - FieldType("shear")); + auto param_fields = + registerParameterFields(FieldType("bulk"), FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsSystem( diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index b8987d3ce7..29bec48b8e 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -183,8 +183,8 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) std::vector reactions; for (size_t step = 0; step < 2; ++step) { - std::tie(states, reactions) = - makeAdvancer(combined, combined_cycle_zero)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + std::tie(states, reactions) = makeAdvancer(combined, combined_cycle_zero) + ->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } From 396392f88f39d6c32ff9ee0c6452b757b5ffe21d Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 11:43:34 -0600 Subject: [PATCH 27/67] Update submodules. --- axom | 2 +- gretl | 2 +- mfem | 2 +- tribol | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/axom b/axom index 52ef76c55c..f81109cea1 160000 --- a/axom +++ b/axom @@ -1 +1 @@ -Subproject commit 52ef76c55c9f1651c71e795b0b27723033209fe5 +Subproject commit f81109cea1507cd9bbbd2f549c3fb33be18a3936 diff --git a/gretl b/gretl index c1d8af52ac..d9f26d2b80 160000 --- a/gretl +++ b/gretl @@ -1 +1 @@ -Subproject commit c1d8af52ac7c26ddf30a52b8cbfd4ddf977c7a91 +Subproject commit d9f26d2b809e8af2d6a57effac39609f2124d566 diff --git a/mfem b/mfem index c4bd4b0cdb..dbbd425a22 160000 --- a/mfem +++ b/mfem @@ -1 +1 @@ -Subproject commit c4bd4b0cdb1890228c020a961f31d9b7968dc44c +Subproject commit dbbd425a225b2ad0c3e1dc0e2b3992899e27556a diff --git a/tribol b/tribol index 0c280ec635..48a9dd6c7d 160000 --- a/tribol +++ b/tribol @@ -1 +1 @@ -Subproject commit 0c280ec635aba34ddcba7f09b8662f4064f20800 +Subproject commit 48a9dd6c7dba8efd690768f2ce3437e52487f662 From b461e9c7d8fe242ce968932e87a9892ccab8d05e Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 11:14:50 -0700 Subject: [PATCH 28/67] Remove files that were not indended to be saved --- compile_commands.json | 1 - gretl_notracking.md | 49 ------------------------------ ideal_test.cpp | 69 ------------------------------------------- 3 files changed, 119 deletions(-) delete mode 120000 compile_commands.json delete mode 100644 gretl_notracking.md delete mode 100644 ideal_test.cpp diff --git a/compile_commands.json b/compile_commands.json deleted file mode 120000 index bcf4942cb0..0000000000 --- a/compile_commands.json +++ /dev/null @@ -1 +0,0 @@ -build-Michaels-Mac-mini.local-darwin-sequoia-aarch64-apple-clang@17.0.0-release/compile_commands.json \ No newline at end of file diff --git a/gretl_notracking.md b/gretl_notracking.md deleted file mode 100644 index d6abd608f3..0000000000 --- a/gretl_notracking.md +++ /dev/null @@ -1,49 +0,0 @@ -# Gretl Stop-Gradient Feature (Phase 5) - -## Overview -This document captures the design decisions for the ability to compute output quantities (like stress visualization) or run preconditioners/iterative solvers using `gretl::State` for the forward evaluation syntax, without computing adjoints or propagating sensitivities backward through these operations. - -## The "Stop-Gradient" Plan (Finalized) - -To achieve this without breaking the graph's internal indexing or dynamic checkpointing logic, we register states normally in the graph, but replace their Vector-Jacobian Product (VJP) function with a **no-op**. - -They effectively act as "Stop-Gradient" or `.detach()` nodes in the computational graph. - -### Mechanism -- **`DataStore::gradients_enabled()`**: A boolean flag on the `DataStore` that controls whether newly created states record their actual VJP closure or a no-op. -- **`DataStore::set_gradients_enabled(bool)`**: A simple setter to toggle this behavior on or off. - -### Important Optimization for Checkpointing -If gradients are disabled, `gretl` skips calling `fetch_state_data()` during the backprop pass. This prevents costly dynamic recomputations of evicted states just to feed a no-op VJP. - -### Example Usage: Picard Iteration -A key use-case is performing intermediate iterative solves without tracking every step, then performing one final tracked step to capture parameter sensitivities. - -```cpp -data_store.set_gradients_enabled(false); - -// Iterate without tracking gradients (stop-gradient nodes) -for (int i = 0; i < 10; ++i) { - x = create_state(..., x, p); -} - -// Re-enable gradients for the final step -data_store.set_gradients_enabled(true); - -// One final iteration to connect the parameter sensitivity -auto x_final = create_state(..., x, p); -``` - -During `back_prop()`, `x_final` will correctly pass gradient information to `p`. However, the gradient passed to the 10th intermediate step `x` will hit a no-op VJP and stop, saving us from recomputing or propagating derivatives through the 10 loop iterations. - -### Properties of "Stop-Gradient" States - -1. **Graph Presence**: The state *is* added to the graph normally. It receives a valid `step` index, and it is added to `states_` and `upstreamSteps_`. -2. **Forward Evaluation & Checkpointing**: The forward pass executes exactly the same way. The state participates in dynamic checkpointing logic if necessary. -3. **VJP Skipping (Stop-Gradient)**: The core optimization is that we assign a **no-op VJP closure** to these states during creation. When back-propagation reaches this state, the no-op VJP is skipped, preventing recomputations. -4. **Duals & Mixing**: Because they have a `step`, a tracked downstream can safely use them as an input. The downstream's VJP will accumulate derivatives into this stop-gradient state's dual, which is perfectly safe. The dual exists and accumulates, but the sensitivity chain dies there because the stop-gradient state's own VJP is a no-op. - -### Advantages -- **Simplicity**: No changes to `DataStore` memory management, dynamic checkpointing, or primal/dual indexing logic. -- **Mixing Support**: Tracked states and stop-gradient states can be mixed seamlessly. -- **Performance**: Achieves the primary user goal—skipping the costly back-propagation (and checkpointer recomputations) of operations they don't care about. diff --git a/ideal_test.cpp b/ideal_test.cpp deleted file mode 100644 index a87b46e5e5..0000000000 --- a/ideal_test.cpp +++ /dev/null @@ -1,69 +0,0 @@ -// 2. BackpropagateThroughPhysics -TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) -{ - smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU}; - smith::NonlinearSolverOptions nonlin_opts{.nonlin_solver = smith::NonlinearSolver::Newton, - .relative_tol = 1e-10, - .absolute_tol = 1e-10, - .max_iterations = 4}; - - auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - - auto field_store = std::make_shared(mesh_, 100, ""); - FieldType> youngs_modulus("youngs_modulus"); - - SolidMechanicsOptions solid_opts; - ThermalOptions thermal_opts; - - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule temp_rule; - auto param_fields = registerParameterFields(youngs_modulus); - auto solid_fields = registerSolidMechanicsFields(field_store, disp_rule); - auto thermal_fields = registerThermalFields(field_store, temp_rule); - - auto [solid_system, solid_cycle_zero_system, solid_end_step_systems] = - buildSolidMechanicsSystem( - std::make_shared(solid_block_solver), solid_opts, param_fields, solid_fields, thermal_fields); - - auto [thermal_system, thermal_cycle_zero_system, thermal_end_step_systems] = - buildThermalSystem( - std::make_shared(thermal_block_solver), thermal_opts, param_fields, thermal_fields, - solid_fields); - - auto coupled = combineSystems(solid_system, thermal_system); - auto coupled_cycle_zero_system = combineSystems(solid_cycle_zero_system, thermal_cycle_zero_system); - auto end_step_systems = combineSystems(solid_end_step_systems, thermal_end_step_systems); - - GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); - - field_store->getParameterFields()[0].get()->setFromFieldFunction([=](smith::tensor) { return 100.0; }); - - solid_system->setDisplacementBC(mesh_->domain("left")); - thermal_system->setTemperatureBC(mesh_->domain("left")); - - solid_system->addTraction("right", - [=](double, auto X, auto, auto, auto, auto, auto temp, auto temperature_dot, auto) { - auto traction = 0.0 * X; - traction[0] = -0.015; - return traction; - }); - - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cycle_zero_system, end_step_systems); - - // Run forward - double dt = 1.0; - for (int step = 0; step < 2; ++step) { - physics->advanceTimestep(dt); - } - - auto reactions = physics->getReactionStates(); - auto obj = 0.5 * (innerProduct(reactions[0], reactions[0]) + innerProduct(reactions[1], reactions[1])); - - gretl::set_as_objective(obj); - obj.data_store().back_prop(); - - auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); - EXPECT_TRUE(param_sens->Norml2() > 0.0); -} \ No newline at end of file From 54a04a036aa36da407b579fcb5d3fd9669dec205 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 11:34:44 -0700 Subject: [PATCH 29/67] Try to make sure the solver and system responsibilities are clear, cleanup cycle zero handling. --- .../combined_system.cpp | 61 ------------------- .../combined_system.hpp | 61 +++++++++++++------ .../multiphysics_time_integrator.cpp | 20 +++--- .../multiphysics_time_integrator.hpp | 6 ++ .../solid_mechanics_system.hpp | 2 + .../differentiable_numerics/system_base.hpp | 2 + .../differentiable_numerics/system_solver.cpp | 22 +++++++ .../differentiable_numerics/system_solver.hpp | 9 +++ .../tests/test_combined_thermo_mechanics.cpp | 39 ++++++++++-- .../test_multiphysics_time_integrator.cpp | 57 +++++++++++++++++ .../test_thermo_mechanics_three_system.cpp | 4 +- 11 files changed, 187 insertions(+), 96 deletions(-) delete mode 100644 src/smith/differentiable_numerics/combined_system.cpp diff --git a/src/smith/differentiable_numerics/combined_system.cpp b/src/smith/differentiable_numerics/combined_system.cpp deleted file mode 100644 index 6c211da1b4..0000000000 --- a/src/smith/differentiable_numerics/combined_system.cpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#include "smith/differentiable_numerics/combined_system.hpp" - -namespace smith { - -std::vector CombinedSystem::solve(const TimeInfo& time_info) const -{ - // Snapshot the current solve-state unknowns for convergence checking. - // One mfem::Vector copy per combined weak form (indexed same as weak_forms). - std::vector prev(weak_forms.size()); - for (size_t k = 0; k < weak_forms.size(); ++k) { - const std::string reaction_name = field_store->getWeakFormReaction(weak_forms[k]->name()); - size_t u_idx = field_store->getFieldIndex(reaction_name); - prev[k] = mfem::Vector(*field_store->getAllFields()[u_idx].get()); - } - - // Staggered iteration: each sub-system reads from the shared FieldStore and writes its - // updated unknowns back before the next sub-system reads. - for (int iter = 0; iter < max_stagger_iters; ++iter) { - for (const auto& sub : subsystems) { - auto sub_unknowns = sub->solve(time_info); - - for (size_t i = 0; i < sub->weak_forms.size(); ++i) { - const std::string reaction_name = field_store->getWeakFormReaction(sub->weak_forms[i]->name()); - size_t u_idx = field_store->getFieldIndex(reaction_name); - field_store->setField(u_idx, sub_unknowns[i]); - } - } - - // Convergence check: relative change in each unknown must be below stagger_tolerance. - double max_change = 0.0; - for (size_t k = 0; k < weak_forms.size(); ++k) { - const std::string reaction_name = field_store->getWeakFormReaction(weak_forms[k]->name()); - size_t u_idx = field_store->getFieldIndex(reaction_name); - mfem::Vector curr(*field_store->getAllFields()[u_idx].get()); - mfem::Vector diff(curr); - diff -= prev[k]; - const double change = diff.Norml2() / (1.0 + curr.Norml2()); - if (change > max_change) max_change = change; - prev[k] = curr; - } - if (max_change < stagger_tolerance) break; - } - - // Return one FieldState per combined weak_form. - std::vector result; - result.reserve(weak_forms.size()); - for (const auto& wf : weak_forms) { - const std::string reaction_name = field_store->getWeakFormReaction(wf->name()); - size_t u_idx = field_store->getFieldIndex(reaction_name); - result.push_back(field_store->getAllFields()[u_idx]); - } - return result; -} - -} // namespace smith diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index e19e28f99f..1d67fb16bd 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -63,20 +63,10 @@ namespace smith { */ struct CombinedSystem : public SystemBase { std::vector> subsystems; - int max_stagger_iters = 10; - double stagger_tolerance = 1e-8; /// @brief Construct a CombinedSystem. weak_forms is populated by combineSystems. using SystemBase::SystemBase; - /** - * @brief Staggered solve: iterate over sub-systems, writing each result to the shared - * FieldStore before the next sub-system reads it. - * - * Returns one FieldState per combined weak_form (same contract as SystemBase::solve). - */ - std::vector solve(const TimeInfo& time_info) const override; - /** * @brief Set a coupled material on all sub-systems. * @@ -108,8 +98,7 @@ struct CombinedSystem : public SystemBase { * @param subs Two or more sub-systems that share a FieldStore. */ template -std::pair, std::shared_ptr> combineSystems( - std::shared_ptr... subs) +SystemBuildResult combineSystems(std::shared_ptr... subs) { static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); @@ -119,37 +108,67 @@ std::pair, std::shared_ptr> combineS auto combined = std::make_shared(); combined->field_store = field_store; + int max_stagger_iters = 1; + bool exact_staggered_steps = false; + std::vector> cycle_zero_subs; + std::vector> post_solve_systems; + std::vector> subsystem_global_block_indices; ( [&](auto& sub) { + std::vector global_block_indices; combined->subsystems.push_back(sub); for (auto& wf : sub->weak_forms) { + global_block_indices.push_back(combined->weak_forms.size()); combined->weak_forms.push_back(wf); } + subsystem_global_block_indices.push_back(global_block_indices); + if (sub->solver) { + max_stagger_iters = std::max(max_stagger_iters, sub->solver->maxStaggeredIterations()); + exact_staggered_steps = exact_staggered_steps || sub->solver->exactStaggeredSteps(); + } if constexpr (requires { sub->cycle_zero_system; }) { if (sub->cycle_zero_system) { cycle_zero_subs.push_back(sub->cycle_zero_system); } } + if constexpr (requires { sub->post_solve_systems; }) { + post_solve_systems.insert(post_solve_systems.end(), sub->post_solve_systems.begin(), + sub->post_solve_systems.end()); + } }(subs), ...); + combined->solver = std::make_shared(max_stagger_iters, exact_staggered_steps); + for (size_t i = 0; i < combined->subsystems.size(); ++i) { + const auto& sub = combined->subsystems[i]; + SLIC_ERROR_IF(!sub->solver, "Combined subsystem must have a solver"); + combined->solver->appendRemappedStages(*sub->solver, subsystem_global_block_indices[i]); + } + std::shared_ptr cycle_zero_combined = nullptr; if (!cycle_zero_subs.empty()) { auto cycle_zero = std::make_shared(); cycle_zero->field_store = field_store; - cycle_zero->max_stagger_iters = 1; // Cycle-zero solves are one-shot + cycle_zero->solver = std::make_shared(1, true); for (auto& sub : cycle_zero_subs) { + std::vector global_block_indices; cycle_zero->subsystems.push_back(sub); for (auto& wf : sub->weak_forms) { + global_block_indices.push_back(cycle_zero->weak_forms.size()); cycle_zero->weak_forms.push_back(wf); } + SLIC_ERROR_IF(!sub->solver, "Combined cycle-zero subsystem must have a solver"); + cycle_zero->solver->appendRemappedStages(*sub->solver, global_block_indices); } cycle_zero_combined = cycle_zero; } - return {combined, cycle_zero_combined}; + combined->cycle_zero_system = cycle_zero_combined; + combined->post_solve_systems = post_solve_systems; + + return {combined, cycle_zero_combined, post_solve_systems}; } /** @@ -178,8 +197,8 @@ struct MonolithicCombinedSystem : public SystemBase { * @param subs Two or more sub-systems that share a FieldStore. */ template -std::pair, std::shared_ptr> combineSystems( - std::shared_ptr solver, std::shared_ptr... subs) +SystemBuildResult combineSystems(std::shared_ptr solver, + std::shared_ptr... subs) { static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); @@ -187,6 +206,7 @@ std::pair, std::shared_ptr std::vector> wfs; std::vector> cycle_zero_wfs; + std::vector> post_solve_systems; ( [&](auto& sub) { @@ -200,6 +220,10 @@ std::pair, std::shared_ptr } } } + if constexpr (requires { sub->post_solve_systems; }) { + post_solve_systems.insert(post_solve_systems.end(), sub->post_solve_systems.begin(), + sub->post_solve_systems.end()); + } }(subs), ...); @@ -209,7 +233,10 @@ std::pair, std::shared_ptr cycle_zero_combined = std::make_shared(field_store, solver, cycle_zero_wfs); } - return {combined, cycle_zero_combined}; + combined->cycle_zero_system = cycle_zero_combined; + combined->post_solve_systems = post_solve_systems; + + return {combined, cycle_zero_combined, post_solve_systems}; } /** diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index c456a8d218..7ccac9c665 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -57,17 +57,15 @@ std::pair, std::vector> MultiphysicsTimeI if (time_info.cycle() == 0 && cycle_zero_system_ && requires_cycle_zero_solve) { auto cycle_zero_unknowns = cycle_zero_system_->solve(time_info); - // Cycle zero system solves for the initial acceleration, but by convention the solved value - // is returned through the first (and only) block of the cycle-zero subsystem — the weak form - // uses an aliased unknown trial space that matches the acceleration test space. Copy that - // single result into the acceleration state slot for the main solve. - SLIC_ERROR_ROOT_IF(cycle_zero_unknowns.size() != 1, - "Cycle zero system is expected to be a single-block solve producing one unknown"); - std::string test_field_name = - system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms.front()->name()); - size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); - current_states[test_field_state_idx] = cycle_zero_unknowns[0]; - system_->field_store->setField(test_field_state_idx, cycle_zero_unknowns[0]); + SLIC_ERROR_ROOT_IF(cycle_zero_unknowns.size() != cycle_zero_system_->weak_forms.size(), + "Cycle zero system result count does not match number of cycle-zero weak forms"); + for (size_t i = 0; i < cycle_zero_system_->weak_forms.size(); ++i) { + std::string test_field_name = + system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms[i]->name()); + size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); + current_states[test_field_state_idx] = cycle_zero_unknowns[i]; + system_->field_store->setField(test_field_state_idx, cycle_zero_unknowns[i]); + } } std::vector primary_unknowns = system_->solve(time_info); diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 7d3ddd70d5..736c354d08 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -57,6 +57,12 @@ inline std::shared_ptr makeAdvancer( std::shared_ptr system, std::shared_ptr cycle_zero_system = nullptr, std::vector> post_solve_systems = {}) { + if (cycle_zero_system == nullptr) { + cycle_zero_system = system->cycle_zero_system; + } + if (post_solve_systems.empty()) { + post_solve_systems = system->post_solve_systems; + } return std::make_shared(std::move(system), std::move(cycle_zero_system), std::move(post_solve_systems)); } diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index fdff7d26a7..804d372fbe 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -496,6 +496,8 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace end_step_systems.push_back(sys->stress_output_system); } + sys->post_solve_systems = end_step_systems; + return std::make_tuple(sys, cycle_zero_system, end_step_systems); } diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index f3d240e3b3..695e3d8155 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -56,6 +56,8 @@ struct SystemBase { // --- infrastructure --- std::shared_ptr field_store; ///< Field store managing the system's fields. std::shared_ptr solver; ///< The solver for the system. + std::shared_ptr cycle_zero_system; + std::vector> post_solve_systems; SystemBase() = default; explicit SystemBase(std::shared_ptr fs, std::shared_ptr sol = nullptr, diff --git a/src/smith/differentiable_numerics/system_solver.cpp b/src/smith/differentiable_numerics/system_solver.cpp index 11d5c95ad7..8171cb8557 100644 --- a/src/smith/differentiable_numerics/system_solver.cpp +++ b/src/smith/differentiable_numerics/system_solver.cpp @@ -40,6 +40,28 @@ void SystemSolver::addSubsystemSolver(const std::vector& block_indices, stages_.push_back(Stage{block_indices, std::move(solver), relaxation_factor}); } +void SystemSolver::appendRemappedStages(const SystemSolver& subsystem_solver, + const std::vector& global_block_indices) +{ + SLIC_ERROR_IF(global_block_indices.empty(), "Global block index map must be non-empty"); + + for (const auto& stage : subsystem_solver.stages_) { + std::vector remapped_block_indices; + if (stage.block_indices.empty()) { + remapped_block_indices = global_block_indices; + } else { + remapped_block_indices.reserve(stage.block_indices.size()); + for (size_t local_block_index : stage.block_indices) { + SLIC_ERROR_IF(local_block_index >= global_block_indices.size(), + axom::fmt::format("Local block index {} exceeds subsystem size {}", local_block_index, + global_block_indices.size())); + remapped_block_indices.push_back(global_block_indices[local_block_index]); + } + } + addSubsystemSolver(remapped_block_indices, stage.solver, stage.relaxation_factor); + } +} + std::vector SystemSolver::solve(const std::vector& residual_evals, const std::vector>& block_indices, const FieldState& shape_disp, diff --git a/src/smith/differentiable_numerics/system_solver.hpp b/src/smith/differentiable_numerics/system_solver.hpp index 76ccd93d0d..008f961d12 100644 --- a/src/smith/differentiable_numerics/system_solver.hpp +++ b/src/smith/differentiable_numerics/system_solver.hpp @@ -51,6 +51,11 @@ class SystemSolver { void addSubsystemSolver(const std::vector& block_indices, std::shared_ptr solver, double relaxation_factor = 1.0); + /// @brief Append stages from another solver, remapped onto global block indices. + /// @param subsystem_solver Source solver whose stages operate on subsystem-local block indices. + /// @param global_block_indices Mapping from subsystem-local block index to global block index. + void appendRemappedStages(const SystemSolver& subsystem_solver, const std::vector& global_block_indices); + /// @brief Solves the multiphysics system using staggered iterations. /// @param residual_evals Vector of WeakForm evaluations for each block. /// @param block_indices Block indices for each residual evaluation. @@ -70,6 +75,10 @@ class SystemSolver { /// Prefers constructing a fresh solver instance when the underlying stage solver retains rebuildable config. std::shared_ptr singleBlockSolver(size_t block_index) const; + int maxStaggeredIterations() const { return max_staggered_iterations_; } + + bool exactStaggeredSteps() const { return exact_staggered_steps_; } + private: int max_staggered_iterations_; ///< Maximum number of staggered iterations. bool exact_staggered_steps_; ///< If true, no early-exit convergence check. diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 61875a3a50..a5a9af1119 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -184,9 +184,11 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto [thermal, thermal_cycle_zero, thermal_end_steps] = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, param_fields, thermal_fields, solid_fields); - auto [coupled, coupled_cycle_zero] = combineSystems(solid, thermal); + auto combined = combineSystems(solid, thermal); + auto coupled = combined.system; + auto coupled_cycle_zero = combined.cycle_zero_system; - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cycle_zero); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); const auto& solid_dual_space = physics->dual("reactions").space(); const auto& solid_state_space = physics->state("displacement_solve_state").space(); const auto& thermal_dual_space = physics->dual("thermal_flux").space(); @@ -220,7 +222,9 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto [thermal, thermal_cycle_zero, thermal_end_steps] = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, param_fields, thermal_fields, solid_fields); - auto [coupled, coupled_cycle_zero] = combineSystems(solid, thermal); + auto combined = combineSystems(solid, thermal); + auto coupled = combined.system; + auto coupled_cycle_zero = combined.cycle_zero_system; ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -237,7 +241,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) return traction; }); - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics", coupled_cycle_zero); + auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); double dt = 1.0; for (int step = 0; step < 2; ++step) { @@ -288,7 +292,9 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( field_store_, solid_fields, makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}); - auto [coupled, coupled_cycle_zero] = combineSystems(solid, thermal); + auto combined = combineSystems(solid, thermal); + auto coupled = combined.system; + auto coupled_cycle_zero = combined.cycle_zero_system; ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -318,7 +324,9 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem(field_store_, solid_fields, nullptr, ThermalOptions{}); - auto [coupled, coupled_cycle_zero] = combineSystems(solver_ptr, solid, thermal); + auto combined = combineSystems(solver_ptr, solid, thermal); + auto coupled = combined.system; + auto coupled_cycle_zero = combined.cycle_zero_system; ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -374,6 +382,25 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) EXPECT_GT(stress_norm, 1e-8) << "Cauchy stress field should be non-zero after deformation"; } +TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) +{ + SolidMechanicsOptions solid_opts{.enable_stress_output = true}; + + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); + + auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( + field_store_, thermal_fields, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts); + auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( + field_store_, solid_fields, makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}); + + auto combined = combineSystems(solid, thermal); + + ASSERT_EQ(combined.end_step_systems.size(), solid_end.size()); + ASSERT_EQ(combined.system->post_solve_systems.size(), solid_end.size()); + EXPECT_FALSE(combined.end_step_systems.empty()); +} + } // namespace smith int main(int argc, char* argv[]) diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 520ddff6f3..af3fd72c22 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -293,6 +293,63 @@ TEST(SystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) StateManager::reset(); } +TEST(SystemSolver, AppendsRemappedStagesForCombinedSubsystems) +{ + auto first_solver = std::make_shared(); + auto second_solver = std::make_shared(); + + SystemSolver subsystem_a(3, false); + subsystem_a.addSubsystemSolver({0}, first_solver, 0.5); + + SystemSolver subsystem_b(3, false); + subsystem_b.addSubsystemSolver({0, 1}, second_solver, 1.0); + + SystemSolver combined(3, false); + combined.appendRemappedStages(subsystem_a, {2}); + combined.appendRemappedStages(subsystem_b, {4, 5}); + + axom::sidre::DataStore datastore; + StateManager::initialize(datastore, "combined_solver_stage_mapping"); + + auto mesh = std::make_shared(mfem::Mesh::MakeCartesian2D(2, 2, mfem::Element::QUADRILATERAL, true, 1.0, 1.0), + "combined_solver_stage_mapping_mesh"); + + auto field_store = std::make_shared(mesh, 20); + FieldType> shape_disp_type("shape_displacement"); + field_store->addShapeDisp(shape_disp_type); + + auto quasi_static = std::make_shared(); + FieldType> field0_type("field0"); + FieldType> field1_type("field1"); + FieldType> field2_type("field2"); + field_store->addIndependent(field0_type, quasi_static); + field_store->addIndependent(field1_type, quasi_static); + field_store->addIndependent(field2_type, quasi_static); + + auto wf0 = buildScalarDiffusionWeakForm("wf0", mesh, field_store, field0_type); + auto wf1 = buildScalarDiffusionWeakForm("wf1", mesh, field_store, field1_type); + auto wf2 = buildScalarDiffusionWeakForm("wf2", mesh, field_store, field2_type); + + const std::vector residuals = {wf0.get(), wf1.get(), wf2.get()}; + const std::vector residual_names = {"wf0", "wf1", "wf2"}; + const auto block_indices = field_store->indexMap(residual_names); + const std::vector> states = {field_store->getStates("wf0"), field_store->getStates("wf1"), + field_store->getStates("wf2")}; + const std::vector> params(residuals.size()); + const auto bc_managers = field_store->getBoundaryConditionManagers(residual_names); + + auto solved_states = combined.solve(residuals, block_indices, field_store->getShapeDisp(), states, params, + TimeInfo(0.0, 1.0, 0), bc_managers); + + EXPECT_EQ(solved_states.size(), 3); + EXPECT_EQ(first_solver->solveCalls(), 1); + EXPECT_EQ(first_solver->lastNumUnknowns(), 1); + EXPECT_EQ(second_solver->solveCalls(), 1); + EXPECT_EQ(second_solver->lastNumUnknowns(), 2); + + StateManager::reset(); +} + } // namespace smith int main(int argc, char* argv[]) diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index 29bec48b8e..cb7bf3302c 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -174,7 +174,9 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) }); // Phase 5: combine and solve. - auto [combined, combined_cycle_zero] = combineSystems(solid, thermal, state_sys); + auto combined_res = combineSystems(solid, thermal, state_sys); + auto combined = combined_res.system; + auto combined_cycle_zero = combined_res.cycle_zero_system; double dt = 1.0, time = 0.0; auto shape_disp = field_store->getShapeDisp(); From 6a69b69e81bdcc327811238258a739102d614483 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 11:49:25 -0700 Subject: [PATCH 30/67] Fix cmake. --- src/smith/differentiable_numerics/CMakeLists.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 3e869690b4..5905db11ee 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -16,7 +16,6 @@ set(differentiable_numerics_sources evaluate_objective.cpp dirichlet_boundary_conditions.cpp multiphysics_time_integrator.cpp - combined_system.cpp ) set(differentiable_numerics_headers From 63ca16d47312e7d3bb2c1ab2d2da13485a8c5a90 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 14:04:57 -0700 Subject: [PATCH 31/67] Fix docs. --- .../combined_system.hpp | 3 +- .../coupling_params.hpp | 54 +++++++++++++------ .../differentiable_physics.hpp | 17 ++++++ .../differentiable_numerics/field_store.hpp | 6 +++ .../multiphysics_time_integrator.hpp | 12 +++++ .../solid_mechanics_system.hpp | 12 ++++- ...id_mechanics_with_internal_vars_system.hpp | 3 ++ .../differentiable_numerics/system_base.hpp | 26 ++++++--- .../differentiable_numerics/system_solver.hpp | 2 + .../thermal_system.hpp | 7 +++ 10 files changed, 115 insertions(+), 27 deletions(-) diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index 1d67fb16bd..364cb282ab 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -62,7 +62,7 @@ namespace smith { * via the template setMaterial below. */ struct CombinedSystem : public SystemBase { - std::vector> subsystems; + std::vector> subsystems; ///< Ordered sub-systems solved during each staggered sweep. /// @brief Construct a CombinedSystem. weak_forms is populated by combineSystems. using SystemBase::SystemBase; @@ -178,6 +178,7 @@ SystemBuildResult combineSystems(std::shared_ptr... * concatenates all weak forms and solves them simultaneously using a single global SystemSolver. */ struct MonolithicCombinedSystem : public SystemBase { + /// @brief Inherit `SystemBase` constructors for monolithic wrappers. using SystemBase::SystemBase; }; diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 73080acdbc..03881c5e07 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -39,18 +39,27 @@ namespace smith { */ template struct CouplingParams { - static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); - std::tuple...> fields; + static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); ///< Number of borrowed or parameter fields. + std::tuple...> fields; ///< Coupling field descriptors in weak-form argument order. + /// @brief Construct a coupling pack from field descriptors. CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} }; -/// Deduction guide: CouplingParams{FieldType("a"), FieldType("b")} -> CouplingParams +/** + * @brief Deduction guide for `CouplingParams`. + * + * Example: + * @code + * CouplingParams{FieldType("a"), FieldType("b")} + * @endcode + * yields `CouplingParams`. + */ template CouplingParams(FieldType...) -> CouplingParams; /// Sentinel: no time integration rule (used for parameter-only packs). struct NoTimeRule { - static constexpr int num_states = 0; + static constexpr int num_states = 0; ///< Number of time states contributed by this sentinel rule. }; /** @@ -65,13 +74,14 @@ struct NoTimeRule { */ template struct PhysicsFields { - using time_rule_type = TimeRule; - static constexpr std::size_t num_rule_states = TimeRule::num_states; - static constexpr std::size_t num_fields = sizeof...(Spaces); - static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); - std::shared_ptr field_store; - std::tuple...> fields; - + using time_rule_type = TimeRule; ///< Time integration rule governing these fields. + static constexpr std::size_t num_rule_states = TimeRule::num_states; ///< Number of state slots from `TimeRule`. + static constexpr std::size_t num_fields = sizeof...(Spaces); ///< Total number of exported fields. + static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); ///< Number of fields exposed for coupling. + std::shared_ptr field_store; ///< Store owning the registered fields. + std::tuple...> fields; ///< Exported field descriptors in registration order. + + /// @brief Construct a registered-physics field pack. PhysicsFields(std::shared_ptr fs, FieldType... f) : field_store(std::move(fs)), fields(std::move(f)...) { @@ -104,7 +114,8 @@ template struct is_coupling_params_impl> : std::true_type {}; template -inline constexpr bool is_coupling_params_v = is_coupling_params_impl>::value; +inline constexpr bool is_coupling_params_v = + is_coupling_params_impl>::value; ///< True for `CouplingParams` and `PhysicsFields`. /// Type trait: true if T is a PhysicsFields<...> specialization. template @@ -114,7 +125,8 @@ template struct is_physics_fields_impl> : std::true_type {}; template -inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; +inline constexpr bool is_physics_fields_v = + is_physics_fields_impl>::value; ///< True only for `PhysicsFields`. /// True if T is a PhysicsFields with a real time rule (not NoTimeRule). template @@ -213,13 +225,17 @@ template struct TimeRuleParamsWithCoupling; template +/// @brief Specialization for coupling packs expressed as `CouplingParams`. struct TimeRuleParamsWithCoupling, Tail...> { - using type = TimeRuleParams; + using type = TimeRuleParams; ///< Expanded parameter list with coupling inserted after rule states. }; template +/// @brief Specialization for coupling packs expressed as `PhysicsFields`. struct TimeRuleParamsWithCoupling, Tail...> { - using type = TimeRuleParams; + using type = TimeRuleParams; ///< Expanded parameter list with coupling inserted after rule states. }; /** @@ -232,13 +248,17 @@ template struct AppendCouplingToParams; template +/// @brief Specialization appending `CouplingParams` field spaces after fixed weak-form parameters. struct AppendCouplingToParams, Parameters, Tail...> { - using type = Parameters; + using type = + Parameters; ///< Base parameter list extended with coupling and trailing parameters. }; template +/// @brief Specialization appending `PhysicsFields` field spaces after fixed weak-form parameters. struct AppendCouplingToParams, Parameters, Tail...> { - using type = Parameters; + using type = + Parameters; ///< Base parameter list extended with coupling and trailing parameters. }; } // namespace detail diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index 20689800bb..59070cc956 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -199,6 +199,13 @@ class DifferentiablePhysics : public BasePhysics { }; template +/** + * @brief Build a `DifferentiablePhysics` from a preconfigured system and advancer. + * + * @param system System whose field store owns states, parameters, mesh, and reactions. + * @param advancer Time integrator used for forward solves. + * @param physics_name Name exposed through the `BasePhysics` interface. + */ std::unique_ptr makeDifferentiablePhysics(std::shared_ptr system, std::shared_ptr advancer, const std::string& physics_name) @@ -210,6 +217,16 @@ std::unique_ptr makeDifferentiablePhysics(std::shared_ptr } template +/** + * @brief Build a `DifferentiablePhysics` and default multiphysics advancer from a system. + * + * If optional cycle-zero or post-solve systems are omitted, values stored on `system` are used. + * + * @param system Main system to wrap. + * @param physics_name Name exposed through the `BasePhysics` interface. + * @param cycle_zero_system Optional startup solve system. + * @param post_solve_systems Optional systems solved after each main step. + */ std::unique_ptr makeDifferentiablePhysics( std::shared_ptr system, const std::string& physics_name, std::shared_ptr cycle_zero_system = nullptr, diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 6bf8f42411..762b016928 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -363,6 +363,9 @@ struct FieldStore { */ void shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc); + /** + * @brief Check whether a field with the given fully-qualified name exists. + */ bool hasField(const std::string& field_name) const; /** @@ -455,6 +458,9 @@ struct FieldStore { */ std::vector getReactionInfos() const; + /** + * @brief Get associated mesh shared by all registered fields. + */ const std::shared_ptr& getMesh() const; /** diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 736c354d08..5682215599 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -28,6 +28,12 @@ class BoundaryConditionManager; */ class MultiphysicsTimeIntegrator : public StateAdvancer { public: + /** + * @brief Construct a multiphysics advancer around main and auxiliary systems. + * @param system Main system solved every timestep. + * @param cycle_zero_system Optional startup system solved before first regular step. + * @param post_solve_systems Optional systems solved after the main step. + */ MultiphysicsTimeIntegrator(std::shared_ptr system, std::shared_ptr cycle_zero_system = nullptr, std::vector> post_solve_systems = {}); @@ -53,6 +59,12 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { std::vector> post_solve_systems_; }; +/** + * @brief Build a `MultiphysicsTimeIntegrator` from system-owned or explicit auxiliary systems. + * + * Missing optional arguments fall back to `system->cycle_zero_system` and + * `system->post_solve_systems`. + */ inline std::shared_ptr makeAdvancer( std::shared_ptr system, std::shared_ptr cycle_zero_system = nullptr, std::vector> post_solve_systems = {}) diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 804d372fbe..ba7c0fc757 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -332,8 +332,11 @@ struct SolidMechanicsSystem : public SystemBase { } }; +/** + * @brief Optional auxiliary systems and outputs for solid mechanics. + */ struct SolidMechanicsOptions { - bool enable_stress_output = false; + bool enable_stress_output = false; ///< Build post-solve stress projection system. bool output_cauchy_stress = false; ///< When true, project Cauchy stress (sigma) instead of PK1 (P). }; @@ -564,6 +567,13 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::sha return buildSolidMechanicsSystem(field_store, CouplingParams<>{}, solver, options); } +/** + * @brief Build a solid mechanics system from registered field packs. + * + * One `PhysicsFields` pack must come from the solid registration. Other `PhysicsFields` packs are + * treated as coupling inputs, while non-physics `CouplingParams` packs are registered as parameter + * fields. + */ template requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && !(std::is_same_v, SolidMechanicsOptions> || ...)) diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index cd27fef608..b49778d77b 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -71,6 +71,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { // Primary weak form: residual for displacement (u). // Inputs: u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params... + /// @brief Weak form type for displacement residual assembly. using SolidWeakFormType = TimeDiscretizedWeakForm, typename detail::AppendCouplingToParams< @@ -79,6 +80,7 @@ struct SolidMechanicsWithInternalVarsSystem : public SystemBase { // State weak form: residual for internal variable (alpha). // Inputs: alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params... + /// @brief Weak form type for internal-variable residual assembly. using StateWeakFormType = TimeDiscretizedWeakForm, H1>>::type>; // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, alpha, coupling_fields..., params... + /// @brief Weak form type for cycle-zero acceleration solve. using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< dim, H1, typename detail::AppendCouplingToParams< diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index 695e3d8155..ca9a5a6689 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -51,15 +51,22 @@ using TimeRuleParams = */ struct SystemBase { // --- equations --- - std::vector> weak_forms; + std::vector> weak_forms; ///< Weak forms solved together by this system. // --- infrastructure --- - std::shared_ptr field_store; ///< Field store managing the system's fields. - std::shared_ptr solver; ///< The solver for the system. - std::shared_ptr cycle_zero_system; - std::vector> post_solve_systems; + std::shared_ptr field_store; ///< Field store managing the system's fields. + std::shared_ptr solver; ///< The solver for the system. + std::shared_ptr cycle_zero_system; ///< Optional startup solve executed before first timestep. + std::vector> post_solve_systems; ///< Optional systems solved after main state update. + /// @brief Construct an empty system shell. SystemBase() = default; + /** + * @brief Construct a system from a field store, solver, and weak forms. + * @param fs Field store shared by all weak forms. + * @param sol Solver used for `solve`. + * @param wfs Weak forms owned by this system. + */ explicit SystemBase(std::shared_ptr fs, std::shared_ptr sol = nullptr, std::vector> wfs = {}) : weak_forms(std::move(wfs)), field_store(std::move(fs)), solver(std::move(sol)) @@ -84,6 +91,9 @@ struct SystemBase { const std::vector& states_for_reactions) const; }; +/** + * @brief Convenience factory for a plain `SystemBase`. + */ inline std::shared_ptr makeSystem(std::shared_ptr field_store, std::shared_ptr solver, std::vector> weak_forms) @@ -102,9 +112,9 @@ inline std::shared_ptr makeSystem(std::shared_ptr field_ */ template struct SystemBuildResult { - std::shared_ptr system; - std::shared_ptr cycle_zero_system; - std::vector> end_step_systems; + std::shared_ptr system; ///< Main system returned by the factory. + std::shared_ptr cycle_zero_system; ///< Optional startup system. + std::vector> end_step_systems; ///< Optional systems solved after each main step. }; } // namespace smith diff --git a/src/smith/differentiable_numerics/system_solver.hpp b/src/smith/differentiable_numerics/system_solver.hpp index 008f961d12..1598dbd071 100644 --- a/src/smith/differentiable_numerics/system_solver.hpp +++ b/src/smith/differentiable_numerics/system_solver.hpp @@ -75,8 +75,10 @@ class SystemSolver { /// Prefers constructing a fresh solver instance when the underlying stage solver retains rebuildable config. std::shared_ptr singleBlockSolver(size_t block_index) const; + /// @brief Maximum number of staggered sweeps allowed for this solver. int maxStaggeredIterations() const { return max_staggered_iterations_; } + /// @brief Whether solver always performs exactly `maxStaggeredIterations()` sweeps. bool exactStaggeredSteps() const { return exact_staggered_steps_; } private: diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 49b6cf01b7..7f759049dd 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -309,6 +309,13 @@ auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr return buildThermalSystem(field_store, CouplingParams<>{}, solver, options); } +/** + * @brief Build a thermal system from registered field packs. + * + * One `PhysicsFields` pack must come from the thermal registration. Other `PhysicsFields` packs are + * treated as coupling inputs, while non-physics `CouplingParams` packs are registered as parameter + * fields. + */ template requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && !(std::is_same_v, ThermalOptions> || ...)) From ce5c10e416eaf82710bf38782e393929b4d0b9cb Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 16:22:05 -0700 Subject: [PATCH 32/67] Use coupled system for interal vars. --- ...id_mechanics_with_internal_vars_system.hpp | 543 ++---------------- .../state_variable_system.hpp | 24 +- .../test_solid_static_with_internal_vars.cpp | 114 ++-- src/smith/numerics/equation_solver.cpp | 2 +- 4 files changed, 134 insertions(+), 549 deletions(-) diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index b49778d77b..ab546076ce 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -6,523 +6,58 @@ /** * @file solid_mechanics_with_internal_vars_system.hpp - * @brief Defines SolidMechanicsWithInternalVarsSystem and its two-phase factory functions. - * - * Two-phase factory (for coupling via combineSystems): - * auto info = registerSolidMechanicsWithInternalVarsFields( - * field_store, disp_rule, state_rule, params...); - * CouplingParams coupling{...}; - * auto sys = buildSolidMechanicsWithInternalVarsSystemFromStore<...>( - * info, solver, opts, coupling); - * - * Standalone factory (backwards-compatible, allocates its own FieldStore): - * auto sys = buildSolidMechanicsWithInternalVarsSystem( - * mesh, solver, disp_rule, state_rule, options); + * @brief Helper to wire an internal-variable material to composed solid and state systems. */ #pragma once -#include "smith/differentiable_numerics/field_store.hpp" -#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" -#include "smith/differentiable_numerics/state_advancer.hpp" -#include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" -#include "smith/differentiable_numerics/time_integration_rule.hpp" -#include "smith/numerics/functional/tuple.hpp" -#include "smith/numerics/functional/tensor.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" -#include "smith/differentiable_numerics/differentiable_physics.hpp" -#include "smith/physics/weak_form.hpp" -#include "smith/differentiable_numerics/system_base.hpp" -#include "smith/differentiable_numerics/coupling_params.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/state_variable_system.hpp" namespace smith { /** - * @brief System struct for solid mechanics with an additional internal variable (L2 state). - * - * Displacement uses a 4-state second-order layout (displacement_solve_state, displacement, velocity, acceleration). - * Internal variable uses a 2-state first-order layout (state_solve_state, state). - * Total: 6 state fields. - * - * With a non-empty Coupling, coupling fields appear immediately after the hardcoded state fields - * (after alpha_old for the solid form; after a_old for the state form) and before user parameter fields. - * setMaterial and addStateEvolution work correctly only when Coupling = CouplingParams<> (default). - * For coupled systems, register integrands directly on solid_weak_form / state_weak_form. - * - * @tparam dim Spatial dimension. - * @tparam disp_order Polynomial order for displacement field. - * @tparam StateSpace Finite element space for the internal variable (e.g., L2). - * @tparam DisplacementTimeRule Time integration rule for displacement (must have num_states == 4). - * @tparam InternalVarTimeRule Time integration rule for the internal variable (must have num_states == 2). - * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: no coupling). - */ -template > -struct SolidMechanicsWithInternalVarsSystem : public SystemBase { - using SystemBase::SystemBase; - - static_assert(DisplacementTimeRule::num_states == 4, - "SolidMechanicsWithInternalVarsSystem requires a 4-state displacement rule"); - static_assert(InternalVarTimeRule::num_states == 2, - "SolidMechanicsWithInternalVarsSystem requires a 2-state internal variable rule"); - - // Primary weak form: residual for displacement (u). - // Inputs: u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params... - /// @brief Weak form type for displacement residual assembly. - using SolidWeakFormType = - TimeDiscretizedWeakForm, - typename detail::AppendCouplingToParams< - Coupling, Parameters, H1, H1, - H1, StateSpace, StateSpace>>::type>; - - // State weak form: residual for internal variable (alpha). - // Inputs: alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params... - /// @brief Weak form type for internal-variable residual assembly. - using StateWeakFormType = - TimeDiscretizedWeakForm, H1, - H1, H1>>::type>; - - // Cycle-zero weak form: test field = acceleration, inputs: u, v, a, alpha, coupling_fields..., params... - /// @brief Weak form type for cycle-zero acceleration solve. - using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm< - dim, H1, - typename detail::AppendCouplingToParams< - Coupling, Parameters, H1, H1, StateSpace>>::type>; - - std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. - std::shared_ptr state_weak_form; ///< Internal variable weak form. - std::shared_ptr - cycle_zero_solid_weak_form; ///< Typed cycle zero solid mechanics weak form. - std::shared_ptr cycle_zero_system; ///< Cycle-zero system. - std::shared_ptr disp_bc; ///< Displacement boundary conditions. - std::shared_ptr state_bc; ///< Internal variable boundary conditions. - std::shared_ptr disp_time_rule; ///< Time integration for displacement. - std::shared_ptr state_time_rule; ///< Time integration for internal variable. - - /** - * @brief Set the material model for the solid mechanics part. - * - * NOTE: works correctly only when Coupling = CouplingParams<> (default). When coupling is active, - * register integrands directly on solid_weak_form. - * - * @tparam MaterialType The material model type. - * @param material The material model instance. - * @param domain_name The name of the domain to apply the material to. - */ - template - void setMaterial(const MaterialType& material, const std::string& domain_name) - { - auto captured_disp_rule = disp_time_rule; - auto captured_state_rule = state_time_rule; - - solid_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, - auto a_old, auto alpha, auto alpha_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto alpha_current = captured_state_rule->value(t_info, alpha, alpha_old); - - typename MaterialType::State state; - auto pk_stress = material(state, get(u_current), get(alpha_current), params...); - - return smith::tuple{get(a_current) * material.density, pk_stress}; - }); - - // Cycle-zero: u and v are given, solve for a; alpha at initial condition - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { - auto alpha_current = alpha; // at cycle 0, use initial alpha - typename MaterialType::State state; - auto pk_stress = material(state, get(u), get(alpha_current), params...); - return smith::tuple{get(a) * material.density, pk_stress}; - }); - } - } - - /** - * @brief Add a body force to the solid mechanics part (with DependsOn). - * @param depends_on Selects which primal and parameter fields the contribution depends on. - * @param domain_name The name of the domain where the body force is applied. - * @param force_function (t, X, u, v, a, alpha, alpha_dot, params...) -> force vector. - */ - template - void addBodyForce(DependsOn depends_on, const std::string& domain_name, - BodyForceType force_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_state_rule = state_time_rule; - solid_weak_form->addBodySource( - depends_on, domain_name, - [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto alpha, auto alpha_old, - auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [alpha_current, alpha_dot] = captured_state_rule->interpolate(t_info, alpha, alpha_old); - return force_function(t_info.time(), X, u_current, v_current, a_current, alpha_current, alpha_dot, params...); - }); - - addCycleZeroBodySourceImpl( - domain_name, - [=](auto t_info, auto X, auto u, auto v, auto a, auto alpha, auto... params) { - auto alpha_dot = 0.0 * alpha; - return force_function(t_info.time(), X, u, v, a, alpha, alpha_dot, params...); - }, - std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); - } - - /** - * @brief Add a body force that depends on all state and parameter fields. - * @param domain_name The name of the domain where the body force is applied. - * @param force_function (t, X, u, v, a, alpha, alpha_dot, params...) -> force vector. - */ - template - void addBodyForce(const std::string& domain_name, BodyForceType force_function) - { - addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); - } - - /** - * @brief Add a surface traction to the solid mechanics part (with DependsOn). - * @param depends_on Selects which primal and parameter fields the contribution depends on. - * @param domain_name The name of the boundary where the traction is applied. - * @param traction_function (t, X, n, u, v, a, alpha, alpha_dot, params...) -> traction vector. - */ - template - void addTraction(DependsOn depends_on, const std::string& domain_name, - TractionType traction_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_state_rule = state_time_rule; - solid_weak_form->addBoundaryFlux( - depends_on, domain_name, - [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto alpha, auto alpha_old, - auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [alpha_current, alpha_dot] = captured_state_rule->interpolate(t_info, alpha, alpha_old); - return traction_function(t_info.time(), X, n, u_current, v_current, a_current, alpha_current, alpha_dot, - params...); - }); - - addCycleZeroBoundaryFluxImpl( - domain_name, - [=](auto t_info, auto X, auto n, auto u, auto v, auto a, auto alpha, auto... params) { - auto alpha_dot = 0.0 * alpha; - return traction_function(t_info.time(), X, n, u, v, a, alpha, alpha_dot, params...); - }, - std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); - } - - /** - * @brief Add a surface traction that depends on all state and parameter fields. - * @param domain_name The name of the boundary where the traction is applied. - * @param traction_function (t, X, n, u, v, a, alpha, alpha_dot, params...) -> traction vector. - */ - template - void addTraction(const std::string& domain_name, TractionType traction_function) - { - addTractionAllParams(domain_name, traction_function, std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); - } - - /** - * @brief Add a pressure boundary condition (follower force) (with DependsOn). - * @param depends_on Selects which primal and parameter fields the contribution depends on. - * @param domain_name The name of the boundary where the pressure is applied. - * @param pressure_function (t, X, u, v, a, alpha, alpha_dot, params...) -> pressure scalar. - */ - template - void addPressure(DependsOn depends_on, const std::string& domain_name, - PressureType pressure_function) - { - auto captured_disp_rule = disp_time_rule; - auto captured_state_rule = state_time_rule; - solid_weak_form->addBoundaryIntegral( - depends_on, domain_name, - [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto alpha, auto alpha_old, - auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [alpha_current, alpha_dot] = captured_state_rule->interpolate(t_info, alpha, alpha_old); - - auto x_current = X + u_current; - auto n_deformed = cross(get(x_current)); - auto n_shape_norm = norm(cross(get(X))); - - auto pressure = pressure_function(t_info.time(), get(X), u_current, v_current, a_current, - alpha_current, alpha_dot, get(params)...); - - return pressure * n_deformed * (1.0 / n_shape_norm); - }); - - addCycleZeroBoundaryIntegralImpl( - domain_name, - [=](auto t_info, auto X, auto u, auto v, auto a, auto alpha, auto... params) { - auto alpha_val = get(alpha); - auto alpha_dot = 0.0 * alpha_val; - - auto x_current = X + u; - auto n_deformed = cross(get(x_current)); - auto n_shape_norm = norm(cross(get(X))); - - auto pressure = pressure_function(t_info.time(), get(X), get(u), get(v), get(a), - alpha_val, alpha_dot, get(params)...); - - return pressure * n_deformed * (1.0 / n_shape_norm); - }, - std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); - } - - /** - * @brief Add a pressure boundary condition that depends on all state and parameter fields. - * @param domain_name The name of the boundary where the pressure is applied. - * @param pressure_function (t, X, u, v, a, alpha, alpha_dot, params...) -> pressure scalar. - */ - template - void addPressure(const std::string& domain_name, PressureType pressure_function) - { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<6 + Coupling::num_coupling_fields>{}); - } - - /** - * @brief Add the evolution law for the internal variable. - * - * NOTE: works correctly only when Coupling = CouplingParams<> (default). When coupling is active, - * register integrands directly on state_weak_form. - * - * @tparam EvolutionType The evolution law function type. - * @param domain_name The name of the domain. - * @param evolution_law Function (t_info, alpha, alpha_dot, grad_u, params...) returning the ODE residual. - */ - template - void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) - { - auto captured_disp_rule = disp_time_rule; - auto captured_state_rule = state_time_rule; - - state_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto alpha, auto alpha_old, auto u, - auto u_old, auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [alpha_current, alpha_dot] = captured_state_rule->interpolate(t_info, alpha, alpha_old); - - auto residual_val = evolution_law(t_info, get(alpha_current), get(alpha_dot), - get(u_current), params...); - - tensor flux{}; - return smith::tuple{residual_val, flux}; - }); - } - - private: - template - void addBodyForceAllParams(const std::string& domain_name, BodyForceType force_function, std::index_sequence) - { - addBodyForce(DependsOn(Is)...>{}, domain_name, force_function); - } - - template - void addTractionAllParams(const std::string& domain_name, TractionType traction_function, std::index_sequence) - { - addTraction(DependsOn(Is)...>{}, domain_name, traction_function); - } - - template - void addPressureAllParams(const std::string& domain_name, PressureType pressure_function, std::index_sequence) - { - addPressure(DependsOn(Is)...>{}, domain_name, pressure_function); - } - - // Cycle-zero helpers: use all-params DependsOn with the 4-state cycle-zero form (u, v, a, alpha) - template - void addCycleZeroBodySourceImpl(const std::string& name, IntegrandType f, std::index_sequence) - { - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBodySource(DependsOn(Is)...>{}, name, f); - } - } - - template - void addCycleZeroBoundaryFluxImpl(const std::string& name, IntegrandType f, std::index_sequence) - { - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBoundaryFlux(DependsOn(Is)...>{}, name, f); - } - } - - template - void addCycleZeroBoundaryIntegralImpl(const std::string& name, IntegrandType f, std::index_sequence) - { - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBoundaryIntegral(DependsOn(Is)...>{}, name, f); - } - } -}; - -// --------------------------------------------------------------------------- -// Options -// --------------------------------------------------------------------------- - -struct SolidMechanicsWithInternalVarsOptions {}; - -/** - * @brief Register all solid mechanics with internal vars fields into a FieldStore. + * @brief Register a solid material integrand on a SolidMechanicsSystem that is coupled to + * a StateVariableSystem carrying internal variables. * - * Phase 1 of the two-phase initialization. Pass instances of the desired time integration rules - * so their types are deduced; only `` need be specified explicitly. + * Assumes: + * - solid was built with state fields as leading coupling fields + * (first 2 coupling positions: state_solve_state, state). + * - state was built with solid displacement fields as leading coupling fields + * (first 4 coupling positions: displacement_solve_state, displacement, velocity, acceleration). * - * @return CouplingParams carrying the exported field tokens (for use as coupling input to other systems). + * Solid material callable must satisfy: + * material(state, grad_u, alpha_value, params...) -> PK1 */ -template -auto registerSolidMechanicsWithInternalVarsFields(std::shared_ptr field_store, - DisplacementTimeRule disp_rule, InternalVarTimeRule state_rule, - FieldType... parameter_types) +template +void setCoupledSolidMechanicsInternalVarsMaterial( + std::shared_ptr> solid, + std::shared_ptr> state, const MaterialType& material, + const std::string& domain_name) { - registerSolidMechanicsFields(field_store, disp_rule); - registerStateVariableFields(field_store, state_rule); - - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - - return CouplingParams{FieldType>(field_store->prefix("displacement_solve_state")), - FieldType>(field_store->prefix("displacement")), - FieldType>(field_store->prefix("velocity")), - FieldType>(field_store->prefix("acceleration")), - FieldType(field_store->prefix("state_solve_state")), - FieldType(field_store->prefix("state")), - parameter_types...}; -} - -/** - * @brief Build a SolidMechanicsWithInternalVarsSystem with coupling, assuming fields are already registered. - * - * Phase 2 of the two-phase initialization. Pass the same rule instances used in - * registerSolidMechanicsWithInternalVarsFields so their types are deduced; - * only `` need be specified. - * - * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. - */ -template - requires detail::is_coupling_params_v -auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr field_store, - DisplacementTimeRule /*disp_rule*/, InternalVarTimeRule /*state_rule*/, - const Coupling& coupling, std::shared_ptr solver, - const SolidMechanicsWithInternalVarsOptions& /*options*/) -{ - auto disp_time_rule_ptr = std::make_shared(); - auto state_time_rule_ptr = std::make_shared(); - - FieldType> shape_disp_type(field_store->prefix("shape_displacement")); - FieldType> disp_type(field_store->prefix("displacement_solve_state"), true); - FieldType> disp_old_type(field_store->prefix("displacement")); - FieldType> velo_old_type(field_store->prefix("velocity")); - FieldType> accel_old_type(field_store->prefix("acceleration")); - - FieldType state_type(field_store->prefix("state_solve_state"), true); - FieldType state_old_type(field_store->prefix("state")); - - auto disp_bc = field_store->getBoundaryConditions(disp_type.name); - auto state_bc = field_store->getBoundaryConditions(state_type.name); - - using SystemType = - SolidMechanicsWithInternalVarsSystem; - - // Solid weak form: (u, u_old, v_old, a_old, alpha, alpha_old, coupling_fields..., params...) - std::string solid_res_name = field_store->prefix("solid_residual"); - auto solid_weak_form = std::apply( - [&](auto&... cfs) { - return std::make_shared( - solid_res_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(solid_res_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, state_type, state_old_type, cfs...)); - }, - coupling.fields); - - // State weak form: (alpha, alpha_old, u, u_old, v_old, a_old, coupling_fields..., params...) - std::string state_res_name = field_store->prefix("state_residual"); - auto state_weak_form = std::apply( - [&](auto&... cfs) { - return std::make_shared( - state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), - field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, disp_type, - disp_old_type, velo_old_type, accel_old_type, cfs...)); - }, - coupling.fields); - - auto sys = std::make_shared(field_store, solver, - std::vector>{solid_weak_form, state_weak_form}); - sys->disp_bc = disp_bc; - sys->state_bc = state_bc; - sys->disp_time_rule = disp_time_rule_ptr; - sys->state_time_rule = state_time_rule_ptr; - sys->solid_weak_form = solid_weak_form; - sys->state_weak_form = state_weak_form; - - std::shared_ptr cycle_zero_system; - std::vector> end_step_systems; - - if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - std::string cycle_zero_name = field_store->prefix("solid_reaction"); - auto accel_as_unknown = accel_old_type; - accel_as_unknown.is_unknown = true; - FieldType> disp_cz_input(disp_type.name); - FieldType state_cz_input(state_type.name); - sys->cycle_zero_solid_weak_form = std::apply( - [&](auto&... cfs) { - return std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, state_cz_input, cfs...)); - }, - coupling.fields); - field_store->markWeakFormInternal(cycle_zero_name); - field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - - NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; - LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - auto cycle_zero_solver = std::make_shared( - buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); - - sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); - cycle_zero_system = sys->cycle_zero_system; + auto captured_disp_rule = solid->disp_time_rule; + auto captured_state_rule = state->state_time_rule; + + solid->solid_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, + auto a_old, auto alpha, auto alpha_old, auto... params) { + auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto alpha_current = captured_state_rule->value(t_info, alpha, alpha_old); + + typename MaterialType::State material_state; + auto pk_stress = material(material_state, get(u_current), get(alpha_current), params...); + + return smith::tuple{get(a_current) * material.density, pk_stress}; + }); + + if (solid->cycle_zero_solid_weak_form) { + solid->cycle_zero_solid_weak_form->addBodyIntegral( + domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { + typename MaterialType::State material_state; + auto pk_stress = material(material_state, get(u), get(alpha), params...); + return smith::tuple{get(a) * material.density, pk_stress}; + }); } - - return std::make_tuple(sys, cycle_zero_system, end_step_systems); -} - -/** - * @brief Build a SolidMechanicsWithInternalVarsSystem without coupling, assuming fields are already registered. - * - * Overload for the common case of no inter-physics coupling. - * Parameters (if any) are wrapped into a CouplingParams so the system sees them. - */ -template -auto buildSolidMechanicsWithInternalVarsSystem(std::shared_ptr field_store, DisplacementTimeRule disp_rule, - InternalVarTimeRule state_rule, std::shared_ptr solver, - const SolidMechanicsWithInternalVarsOptions& options, - FieldType... parameter_types) -{ - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - return buildSolidMechanicsWithInternalVarsSystem( - field_store, disp_rule, state_rule, CouplingParams{parameter_types...}, solver, options); } } // namespace smith diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 8c4cb54256..d6a6d0e0bd 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -127,11 +127,13 @@ auto registerStateVariableFields(std::shared_ptr field_store, Intern field_store->addIndependent(state_type, state_time_rule_ptr); field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); + if constexpr (sizeof...(parameter_space) > 0) { + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); + } return CouplingParams{FieldType(field_store->prefix("state_solve_state")), FieldType(field_store->prefix("state")), parameter_types...}; @@ -194,11 +196,13 @@ auto buildStateVariableSystem(std::shared_ptr field_store, InternalV std::shared_ptr solver, const StateVariableOptions& options, FieldType... parameter_types) { - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); + if constexpr (sizeof...(parameter_space) > 0) { + auto prefix_param = [&](auto& pt) { + pt.name = "param_" + pt.name; + field_store->addParameter(pt); + }; + (prefix_param(parameter_types), ...); + } return buildStateVariableSystem(field_store, rule, CouplingParams{parameter_types...}, solver, options); } diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 36e1c6bd17..0e7bdd59ca 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -104,30 +104,45 @@ struct StrainNormEvolution { TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) { + auto solid_solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto state_solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule state_rule; - registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); + auto solid_fields = + registerSolidMechanicsFields(field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); + auto state_fields = + registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( - field_store, disp_rule, state_rule, coupled_solver, SolidMechanicsWithInternalVarsOptions{}); + auto solid_res = buildSolidMechanicsSystem( + field_store, state_fields, solid_solver, SolidMechanicsOptions{}); + auto solid = solid_res.system; + auto [state, state_cycle_zero, state_end_steps] = buildStateVariableSystem( + field_store, BackwardEulerFirstOrderTimeIntegrationRule{}, solid_fields, state_solver, StateVariableOptions{}); + + auto combined = combineSystems(coupled_solver, solid, state); + auto system = combined.system; + auto cycle_zero_sys = combined.cycle_zero_system; + auto end_steps = combined.end_step_systems; // Material and Evolution - system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); - system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); + setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); + state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { + return StrainNormEvolution{}(t_info, isv, isv_dot, get(u)); + }); // Boundary Conditions // Fix bottom face - system->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); // Pull top face double pull_rate = 0.05; - system->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { + solid->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { tensor u{}; u[2] = pull_rate * t; return u; @@ -148,30 +163,46 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) { + auto solid_system_solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto state_system_solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); auto disp_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto state_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto state_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); // Staggered solver: stage 0 solves displacement (block 0), stage 1 solves state (block 1). // Use relaxation_factor = 0.5 on the displacement stage to exercise the relaxation path. auto staggered_solver = std::make_shared(20); staggered_solver->addSubsystemSolver({0}, disp_solver, 0.5); - staggered_solver->addSubsystemSolver({1}, state_solver, 1.0); + staggered_solver->addSubsystemSolver({1}, state_block_solver, 1.0); auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule state_rule; - registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); - - auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( - field_store, disp_rule, state_rule, staggered_solver, SolidMechanicsWithInternalVarsOptions{}); - - system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); - system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); + auto solid_fields = + registerSolidMechanicsFields(field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); + auto state_fields = + registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); + + auto solid_res = buildSolidMechanicsSystem( + field_store, state_fields, solid_system_solver, SolidMechanicsOptions{}); + auto solid = solid_res.system; + auto [state, state_cycle_zero, state_end_steps] = + buildStateVariableSystem(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}, solid_fields, + state_system_solver, StateVariableOptions{}); + + auto combined = combineSystems(staggered_solver, solid, state); + auto system = combined.system; + auto cycle_zero_sys = combined.cycle_zero_system; + auto end_steps = combined.end_step_systems; + + setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); + state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { + return StrainNormEvolution{}(t_info, isv, isv_dot, get(u)); + }); - system->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); double pull_rate = 0.05; - system->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { + solid->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { tensor u{}; u[2] = pull_rate * t; return u; @@ -186,26 +217,41 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) { + auto solid_solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto state_solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "body_force_test_"); - QuasiStaticSecondOrderTimeIntegrationRule disp_rule; - BackwardEulerFirstOrderTimeIntegrationRule state_rule; - registerSolidMechanicsWithInternalVarsFields(field_store, disp_rule, state_rule); - - auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsWithInternalVarsSystem( - field_store, disp_rule, state_rule, coupled_solver, SolidMechanicsWithInternalVarsOptions{}); - - system->setMaterial(DamageMaterial{}, mesh->entireBodyName()); - system->addStateEvolution(mesh->entireBodyName(), StrainNormEvolution{}); + auto solid_fields = + registerSolidMechanicsFields(field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); + auto state_fields = + registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); + + auto solid_res = buildSolidMechanicsSystem( + field_store, state_fields, solid_solver, SolidMechanicsOptions{}); + auto solid = solid_res.system; + auto [state, state_cycle_zero, state_end_steps] = buildStateVariableSystem( + field_store, BackwardEulerFirstOrderTimeIntegrationRule{}, solid_fields, state_solver, StateVariableOptions{}); + + auto combined = combineSystems(coupled_solver, solid, state); + auto system = combined.system; + auto cycle_zero_sys = combined.cycle_zero_system; + auto end_steps = combined.end_step_systems; + + setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); + state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { + return StrainNormEvolution{}(t_info, isv, isv_dot, get(u)); + }); // Fix bottom face - system->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); // Apply a gravity-like body force in the -z direction double body_force_mag = -0.01; - system->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { + solid->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { tensor f{}; f[2] = body_force_mag; return f; @@ -213,7 +259,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) // Apply a traction on the top face in the +z direction double traction_mag = 0.005; - system->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { + solid->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { tensor t{}; t[2] = traction_mag; return t; diff --git a/src/smith/numerics/equation_solver.cpp b/src/smith/numerics/equation_solver.cpp index b5b217a2c1..f3ab28752a 100644 --- a/src/smith/numerics/equation_solver.cpp +++ b/src/smith/numerics/equation_solver.cpp @@ -30,7 +30,7 @@ namespace { */ class PreconditionerOnlySolver : public mfem::IterativeSolver { public: - PreconditionerOnlySolver(MPI_Comm comm) : mfem::IterativeSolver(comm) {} + PreconditionerOnlySolver(MPI_Comm mpi_comm) : mfem::IterativeSolver(mpi_comm) {} /// @overload void Mult(const mfem::Vector& x, mfem::Vector& y) const override From 00ed11558cf573493768853cc36cae22f0fc5443 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 21 Apr 2026 17:38:27 -0700 Subject: [PATCH 33/67] Fix tests and styles, cleanup duplication. --- .../differentiable_numerics/CMakeLists.txt | 1 + .../multiphysics_time_integrator.cpp | 4 + .../differentiable_numerics/system_solver.cpp | 4 + .../tests/test_combined_thermo_mechanics.cpp | 92 +++---------------- .../test_multiphysics_time_integrator.cpp | 4 +- .../thermo_mechanical_system.hpp | 46 ++++++++-- .../green_saint_venant_thermoelastic.hpp | 84 ++++++++++------- 7 files changed, 115 insertions(+), 120 deletions(-) diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 5905db11ee..06cf57d528 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -41,6 +41,7 @@ set(differentiable_numerics_headers solid_mechanics_with_internal_vars_system.hpp state_variable_system.hpp thermal_system.hpp + thermo_mechanical_system.hpp coupling_params.hpp combined_system.hpp system_base.hpp diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index 7ccac9c665..36e5001292 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -180,6 +180,10 @@ std::pair, std::vector> MultiphysicsTimeI } } + for (size_t i = 0; i < new_states.size(); ++i) { + system_->field_store->setField(i, new_states[i]); + } + return {new_states, reactions}; } diff --git a/src/smith/differentiable_numerics/system_solver.cpp b/src/smith/differentiable_numerics/system_solver.cpp index 8171cb8557..fe20a0fc2a 100644 --- a/src/smith/differentiable_numerics/system_solver.cpp +++ b/src/smith/differentiable_numerics/system_solver.cpp @@ -79,6 +79,10 @@ std::vector SystemSolver::solve(const std::vector& residu stage.block_indices.resize(num_residuals); std::iota(stage.block_indices.begin(), stage.block_indices.end(), 0); } + for (size_t block_index : stage.block_indices) { + SLIC_ERROR_IF(block_index >= num_residuals, + axom::fmt::format("Stage block index {} exceeds residual count {}", block_index, num_residuals)); + } } // Set the inner tolerance factor based on the number of stages. For single-stage // solves, we don't want to reduce the tolerances as that's pointless and diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index a5a9af1119..904c42fd78 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -12,6 +12,7 @@ #include "smith/numerics/solver_config.hpp" #include "smith/physics/state/state_manager.hpp" #include "smith/physics/mesh.hpp" +#include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/system_solver.hpp" @@ -34,73 +35,6 @@ static constexpr int temperature_order = 1; using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; -template -auto greenStrain(const tensor& grad_u) -{ - return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); -} - -// Material with E parameter -struct ParameterizedGreenSaintVenantThermoelasticMaterial { - double density; - double E0; - double nu; - double C_v; - double alpha; - double theta_ref; - double kappa; - using State = Empty; - template - auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta, const T5& E_param) const - { - auto E = E0 + get<0>(E_param); - const auto K = E / (3.0 * (1.0 - 2.0 * nu)); - const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrain(grad_u); - const auto trEg = tr(Eg); - static constexpr auto I = Identity(); - const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; - auto F = grad_u + I; - const auto Piola = dot(F, S); - auto greenStrainRate = - 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate) + 0.0 * E; - const auto q0 = -kappa * grad_theta; - return smith::tuple{Piola, C_v, s0, q0}; - } -}; - -// Material without user parameters -struct ThermoelasticMaterialNoParam { - double density; - double E; - double nu; - double C_v; - double alpha; - double theta_ref; - double kappa; - using State = Empty; - template - auto operator()(double, State&, const tensor& grad_u, const tensor& grad_v, T3 theta, - const tensor& grad_theta) const - { - const auto K = E / (3.0 * (1.0 - 2.0 * nu)); - const auto G = 0.5 * E / (1.0 + nu); - const auto Eg = greenStrain(grad_u); - const auto trEg = tr(Eg); - static constexpr auto I = Identity(); - const auto S = 2.0 * G * dev(Eg) + K * (trEg - dim * alpha * (theta - theta_ref)) * I; - auto F = grad_u + I; - const auto Piola = dot(F, S); - auto greenStrainRate = - 0.5 * (grad_v + transpose(grad_v) + dot(transpose(grad_v), grad_u) + dot(transpose(grad_u), grad_v)); - const auto s0 = -dim * K * alpha * (theta + 273.1) * tr(greenStrainRate); - const auto q0 = -kappa * grad_theta; - return smith::tuple{Piola, C_v, s0, q0}; - } -}; - struct ThermoMechanicsMeshFixture : public testing::Test { void SetUp() { @@ -124,13 +58,14 @@ struct ThermoMechanicsMeshFixture : public testing::Test { std::shared_ptr coupled_cycle_zero, double dt = 1.0) { auto shape_disp = field_store_->getShapeDisp(); - auto states = field_store_->getStateFields(); auto params = field_store_->getParameterFields(); std::vector reactions; - std::tie(states, reactions) = makeAdvancer(coupled, coupled_cycle_zero) - ->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, states, params); + std::tie(std::ignore, reactions) = + makeAdvancer(coupled, coupled_cycle_zero) + ->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, field_store_->getStateFields(), params); - mfem::Vector final_disp(*states[field_store_->getFieldIndex("displacement_solve_state")].get()); + mfem::Vector final_disp( + *field_store_->getStateFields()[field_store_->getFieldIndex("displacement_solve_state")].get()); double deflection = 0.0; for (int i = 1; i < final_disp.Size(); i += dim) { deflection = std::max(deflection, std::abs(final_disp(i))); @@ -172,9 +107,9 @@ static const NonlinearSolverOptions newtonNonlinOpts{ // 1. CreateDifferentiablePhysicsAllocatesReactionInfo TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionInfo) { - FieldType> youngs_modulus("youngs_modulus"); + FieldType> thermal_expansion_scaling("thermal_expansion_scaling"); - auto param_fields = registerParameterFields(youngs_modulus); + auto param_fields = registerParameterFields(thermal_expansion_scaling); auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); @@ -226,14 +161,15 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto coupled = combined.system; auto coupled_cycle_zero = combined.cycle_zero_system; - ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( - [=](smith::tensor) { return 100.0; }); + [=](smith::tensor) { return 1.0; }); solid->setDisplacementBC(mesh_->domain("left")); - thermal->setTemperatureBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { auto traction = 0.0 * X; @@ -296,7 +232,7 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto coupled = combined.system; auto coupled_cycle_zero = combined.cycle_zero_system; - ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); @@ -328,7 +264,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto coupled = combined.system; auto coupled_cycle_zero = combined.cycle_zero_system; - ThermoelasticMaterialNoParam material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index af3fd72c22..bf6a56471e 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -305,8 +305,8 @@ TEST(SystemSolver, AppendsRemappedStagesForCombinedSubsystems) subsystem_b.addSubsystemSolver({0, 1}, second_solver, 1.0); SystemSolver combined(3, false); - combined.appendRemappedStages(subsystem_a, {2}); - combined.appendRemappedStages(subsystem_b, {4, 5}); + combined.appendRemappedStages(subsystem_a, {0}); + combined.appendRemappedStages(subsystem_b, {1, 2}); axom::sidre::DataStore datastore; StateManager::initialize(datastore, "combined_solver_stage_mapping"); diff --git a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp index d88d13d5f7..2316067c8f 100644 --- a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp @@ -16,6 +16,31 @@ namespace smith { +namespace detail { + +/** + * @brief Dispatch to either coupled-rate or established thermoelastic material signatures. + * + * Supports materials that accept `(dt, state, grad_u, grad_v, theta, grad_theta, params...)` + * and materials that accept `(state, grad_u, theta, grad_theta, params...)`. + */ +template +auto evaluateCoupledThermoMechanicsMaterial(const MaterialType& material, DT dt, StateType& state, + const GradUType& grad_u, const GradVType& grad_v, ThetaType theta, + const GradThetaType& grad_theta, ParamTypes&&... params) +{ + if constexpr (requires { + material(dt, state, grad_u, grad_v, theta, grad_theta, std::forward(params)...); + }) { + return material(dt, state, grad_u, grad_v, theta, grad_theta, std::forward(params)...); + } else { + return material(state, grad_u, theta, grad_theta, std::forward(params)...); + } +} + +} // namespace detail + /** * @brief Register a coupled thermo-mechanical material integrand on a SolidMechanicsSystem * and ThermalSystem that were built with mutual coupling fields. @@ -64,9 +89,10 @@ void setCoupledThermoMechanicsMaterial( auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u_current), get(v_current), - get(T), get(T), params...); + typename MaterialType::State state{}; + auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( + material, t_info.dt(), state, get(u_current), get(v_current), get(T), + get(T), params...); return smith::tuple{get(a_current) * material.density, pk}; }); @@ -76,9 +102,10 @@ void setCoupledThermoMechanicsMaterial( domain_name, [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T), - get(T), params...); + typename MaterialType::State state{}; + auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( + material, t_info.dt(), state, get(u), get(v), get(T), get(T), + params...); return smith::tuple{get(a) * material.density, pk}; }); } @@ -89,9 +116,10 @@ void setCoupledThermoMechanicsMaterial( auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - typename MaterialType::State state; - auto [pk, C_v, s0, q0] = material(t_info.dt(), state, get(u), get(v), get(T_current), - get(T_current), params...); + typename MaterialType::State state{}; + auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( + material, t_info.dt(), state, get(u), get(v), get(T_current), + get(T_current), params...); return smith::tuple{C_v * get(T_dot) - s0, -q0}; }); } diff --git a/src/smith/physics/materials/green_saint_venant_thermoelastic.hpp b/src/smith/physics/materials/green_saint_venant_thermoelastic.hpp index b0f88aab27..d9f47763e7 100644 --- a/src/smith/physics/materials/green_saint_venant_thermoelastic.hpp +++ b/src/smith/physics/materials/green_saint_venant_thermoelastic.hpp @@ -20,6 +20,50 @@ auto greenStrain(const tensor& grad_u) return 0.5 * (grad_u + transpose(grad_u) + dot(transpose(grad_u), grad_u)); } +/** + * @brief Compute isotropic bulk modulus from Young's modulus and Poisson ratio. + */ +template +auto bulkModulus(EType E, double nu) +{ + return E / (3.0 * (1.0 - 2.0 * nu)); +} + +/** + * @brief Compute isotropic shear modulus from Young's modulus and Poisson ratio. + */ +template +auto shearModulus(EType E, double nu) +{ + return 0.5 * E / (1.0 + nu); +} + +/** + * @brief Compute first Piola stress for Green-Saint Venant thermoelasticity. + */ +template +auto greenSaintVenantPiola(EType E, double nu, AlphaType alpha, double theta_ref, + const tensor& grad_u, TTheta theta) +{ + const auto K = bulkModulus(E, nu); + const auto G = shearModulus(E, nu); + static constexpr auto I = Identity(); + auto F = grad_u + I; + const auto Eg = greenStrain(grad_u); + const auto trEg = tr(Eg); + const auto S = 2.0 * G * dev(Eg) + K * (trEg - static_cast(dim) * alpha * (theta - theta_ref)) * I; + return dot(F, S); +} + +/** + * @brief Compute referential Fourier heat flux. + */ +template +auto fourierHeatFlux(double kappa, const tensor& grad_theta) +{ + return -kappa * grad_theta; +} + /// @brief Green-Saint Venant isotropic thermoelastic model struct GreenSaintVenantThermoelasticMaterial { double density; ///< density @@ -56,22 +100,11 @@ struct GreenSaintVenantThermoelasticMaterial { template auto operator()(State& state, const tensor& grad_u, T2 theta, const tensor& grad_theta) const { - const double K = E / (3.0 * (1.0 - 2.0 * nu)); - const double G = 0.5 * E / (1.0 + nu); - static constexpr auto I = Identity(); - auto F = grad_u + I; const auto Eg = greenStrain(grad_u); const auto trEg = tr(Eg); - - // stress - const auto S = 2.0 * G * dev(Eg) + K * (trEg - 3.0 * alpha * (theta - theta_ref)) * I; - const auto Piola = dot(F, S); - - // internal heat source - const auto s0 = -3.0 * K * alpha * theta * (trEg - state.strain_trace); - - // heat flux - const auto q0 = -kappa * grad_theta; + const auto Piola = greenSaintVenantPiola(E, nu, alpha, theta_ref, grad_u, theta); + const auto s0 = -3.0 * bulkModulus(E, nu) * alpha * theta * (trEg - state.strain_trace); + const auto q0 = fourierHeatFlux(kappa, grad_theta); state.strain_trace = get_value(trEg); @@ -139,23 +172,12 @@ struct ParameterizedGreenSaintVenantThermoelasticMaterial { T4 thermal_expansion_scaling) const { auto [scale, unused] = thermal_expansion_scaling; - const double K = E / (3.0 * (1.0 - 2.0 * nu)); - const double G = 0.5 * E / (1.0 + nu); - static constexpr auto I = Identity(); - auto F = grad_u + I; const auto Eg = greenStrain(grad_u); const auto trEg = tr(Eg); auto alpha = alpha0 * scale; - - // stress - const auto S = 2.0 * G * dev(Eg) + K * (trEg - 3.0 * alpha * (theta - theta_ref)) * I; - const auto Piola = dot(F, S); - - // internal heat source - const auto s0 = -3.0 * K * alpha * theta * (trEg - state.strain_trace); - - // heat flux - const auto q0 = -kappa * grad_theta; + const auto Piola = greenSaintVenantPiola(E, nu, alpha, theta_ref, grad_u, theta); + const auto s0 = -3.0 * bulkModulus(E, nu) * alpha * theta * (trEg - state.strain_trace); + const auto q0 = fourierHeatFlux(kappa, grad_theta); state.strain_trace = get_value(trEg); @@ -172,11 +194,11 @@ struct ParameterizedGreenSaintVenantThermoelasticMaterial { auto calculateFreeEnergy(const tensor& grad_u, T2 theta, T3 thermal_expansion_scaling) const { auto [scale, unused] = thermal_expansion_scaling; - const double K = E / (3.0 * (1.0 - 2.0 * nu)); - const double G = 0.5 * E / (1.0 + nu); + const double K = bulkModulus(E, nu); + const double G = shearModulus(E, nu); auto strain = greenStrain(grad_u); auto trE = tr(strain); - const double alpha = alpha0 * scale; + const auto alpha = alpha0 * scale; auto psi_1 = G * squared_norm(dev(strain)) + 0.5 * K * trE * trE; using std::log; auto logT = log(theta / theta_ref); From 683ff3aa58bd61e279f73ea6c91cd5d9e2660ff9 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 22 Apr 2026 11:20:51 -0700 Subject: [PATCH 34/67] Working on cleaning up the interface more. --- .../combined_system.hpp | 31 ++++--- .../solid_mechanics_system.hpp | 77 ++++++----------- .../state_variable_system.hpp | 63 ++++++++------ .../differentiable_numerics/system_base.hpp | 16 ---- .../tests/test_combined_thermo_mechanics.cpp | 86 ++++++++----------- .../tests/test_solid_dynamics.cpp | 20 ++--- .../test_solid_static_with_internal_vars.cpp | 42 ++++----- .../tests/test_thermal_static.cpp | 11 +-- .../test_thermo_mechanics_three_system.cpp | 17 ++-- .../thermal_system.hpp | 66 ++++++-------- 10 files changed, 182 insertions(+), 247 deletions(-) diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index 364cb282ab..580114766c 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -21,13 +21,11 @@ * CouplingParams thermal_coupling{FieldType>("displacement_solve_state"), * FieldType>("displacement")}; * - * auto solid_res = buildSolidMechanicsSystem( - * field_store, disp_rule, solid_coupling, solid_solver, solid_opts, params...); - * auto solid = solid_res.system; + * auto solid = buildSolidMechanicsSystem( + * field_store, solid_solver, solid_opts, params..., solid_coupling); * - * auto thermal_res = buildThermalSystem( - * field_store, temp_rule, thermal_coupling, thermal_solver, thermal_opts); - * auto thermal = thermal_res.system; + * auto thermal = buildThermalSystem( + * field_store, thermal_solver, thermal_opts, thermal_coupling); * * auto coupled = combineSystems(solid, thermal); * coupled->setMaterial(thermo_mech_material, domain); // tight coupling @@ -98,7 +96,7 @@ struct CombinedSystem : public SystemBase { * @param subs Two or more sub-systems that share a FieldStore. */ template -SystemBuildResult combineSystems(std::shared_ptr... subs) +auto combineSystems(std::shared_ptr... subs) { static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); @@ -168,7 +166,13 @@ SystemBuildResult combineSystems(std::shared_ptr... combined->cycle_zero_system = cycle_zero_combined; combined->post_solve_systems = post_solve_systems; - return {combined, cycle_zero_combined, post_solve_systems}; + struct CombinedSystemResult { + std::shared_ptr system; + std::shared_ptr cycle_zero_system; + std::vector> end_step_systems; + }; + + return CombinedSystemResult{combined, cycle_zero_combined, post_solve_systems}; } /** @@ -198,8 +202,7 @@ struct MonolithicCombinedSystem : public SystemBase { * @param subs Two or more sub-systems that share a FieldStore. */ template -SystemBuildResult combineSystems(std::shared_ptr solver, - std::shared_ptr... subs) +auto combineSystems(std::shared_ptr solver, std::shared_ptr... subs) { static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); @@ -237,7 +240,13 @@ SystemBuildResult combineSystems(std::shared_ptrcycle_zero_system = cycle_zero_combined; combined->post_solve_systems = post_solve_systems; - return {combined, cycle_zero_combined, post_solve_systems}; + struct MonolithicCombinedSystemResult { + std::shared_ptr system; + std::shared_ptr cycle_zero_system; + std::vector> end_step_systems; + }; + + return MonolithicCombinedSystemResult{combined, cycle_zero_combined, post_solve_systems}; } /** diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index ba7c0fc757..0e4103b0a5 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -69,7 +69,6 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr cycle_zero_solid_weak_form; ///< Typed cycle zero solid mechanics weak form. - std::shared_ptr cycle_zero_system; ///< Cycle-zero system for initial acceleration solve. std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. @@ -372,7 +371,7 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, Displ } /** - * @brief Register all solid mechanics fields (no rule instance — Rule given as explicit template param). + * @brief Register all solid mechanics fields (no rule instance - Rule given as explicit template param). * * Preferred form: Rule is deduced from the PhysicsFields type rather than a runtime instance. * Equivalent to the rule-instance overload but avoids requiring a dummy rule object. @@ -384,20 +383,18 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store) } /** - * @brief Build a SolidMechanicsSystem with coupling, assuming fields are already registered. + * @brief Internal solid mechanics builder after coupling fields are assembled. * * Phase 2 of the two-phase initialization. Pass the same rule instance used in * registerSolidMechanicsFields so the type is deduced; only `` need be specified. * - * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. - * `cycle_zero_system` is nullptr unless the rule requires an initial acceleration solve. - * `end_step_systems` contains the stress output system when `enable_stress_output` is set. */ +namespace detail { + template requires detail::is_coupling_params_v -auto buildSolidMechanicsSystem(std::shared_ptr field_store, DisplacementTimeRule /*rule*/, - const Coupling& coupling, std::shared_ptr solver, - const SolidMechanicsOptions& options) +auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, const Coupling& coupling, + std::shared_ptr solver, const SolidMechanicsOptions& options) { auto disp_time_rule_ptr = std::make_shared(); @@ -427,9 +424,6 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace sys->solid_weak_form = solid_weak_form; sys->output_cauchy_stress = options.output_cauchy_stress; - std::shared_ptr cycle_zero_system; - std::vector> end_step_systems; - if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("solid_reaction"); auto accel_as_unknown = accel_old_type; @@ -461,7 +455,6 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); - cycle_zero_system = sys->cycle_zero_system; } if (options.enable_stress_output) { @@ -496,49 +489,36 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, Displace std::make_shared(buildNonlinearBlockSolver(stress_nonlin, stress_lin, *field_store->getMesh())); sys->stress_output_system = makeSystem(field_store, stress_solver, {sys->stress_weak_form}); - end_step_systems.push_back(sys->stress_output_system); + sys->post_solve_systems.push_back(sys->stress_output_system); } - sys->post_solve_systems = end_step_systems; - - return std::make_tuple(sys, cycle_zero_system, end_step_systems); + return sys; } +} // namespace detail + /** - * @brief Build a SolidMechanicsSystem from a coupling pack and optional parameter fields. + * @brief Build a SolidMechanicsSystem from already-registered field packs. * * Preferred API: Rule is given as explicit template param (no rule instance needed). - * The coupling argument carries the fields borrowed from another physics (or CouplingParams<> for none). - * Additional parameter_types are registered and appended after the coupling fields. - * - * Returns a @c SystemBuildResult so callers can write @c res.system, @c res.cycle_zero_system, etc. + * Additional parameter packs are registered as parameters. Coupling packs are taken from + * the trailing field-pack arguments. * * Usage: * @code - * auto res = buildSolidMechanicsSystem( - * field_store, thermal_fields, solver, opts, youngs_modulus); - * auto solid = res.system; + * auto solid = buildSolidMechanicsSystem( + * field_store, solver, opts, youngs_modulus, thermal_fields); * @endcode */ -template - requires detail::is_coupling_params_v -auto buildSolidMechanicsSystem(std::shared_ptr field_store, const Coupling& coupling, - std::shared_ptr solver, const SolidMechanicsOptions& options, - FieldType... parameter_types) +template + requires(sizeof...(FieldPacks) > 0 && (detail::is_coupling_params_v || ...)) +auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::shared_ptr solver, + const SolidMechanicsOptions& options, const FieldPacks&... field_packs) { - ( - [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }(parameter_types), - ...); - - auto combined = std::apply([&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); - - auto [sys, cycle_zero, ends] = - buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, combined, solver, options); - using SysType = typename decltype(sys)::element_type; - return SystemBuildResult{std::move(sys), std::move(cycle_zero), std::move(ends)}; + (detail::registerParamsIfNeeded(field_store, field_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, field_packs...); + return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, + options); } /** @@ -550,21 +530,22 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, const Co * * Usage: * @code - * auto [solid, cycle_zero, end] = buildSolidMechanicsSystem( + * auto solid = buildSolidMechanicsSystem( * solver, opts, param_fields, solid_fields, thermal_fields); * @endcode */ /** * @brief Build a standalone SolidMechanicsSystem with no coupling and no parameters. * - * Convenience overload for single-physics tests and demos — avoids the `CouplingParams<>{}` + * Convenience overload for single-physics tests and demos - avoids the `CouplingParams<>{}` * placeholder at the call site. */ template auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::shared_ptr solver, const SolidMechanicsOptions& options) { - return buildSolidMechanicsSystem(field_store, CouplingParams<>{}, solver, options); + return detail::buildSolidMechanicsSystemImpl(field_store, CouplingParams<>{}, + solver, options); } /** @@ -581,9 +562,7 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid const FieldPacks&... field_packs) { auto field_store = detail::findFieldStore(field_packs...); - (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, field_packs...); - return buildSolidMechanicsSystem(field_store, DisplacementTimeRule{}, coupling, solver, options); + return buildSolidMechanicsSystem(field_store, solver, options, field_packs...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index d6a6d0e0bd..40db2ec12e 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -12,8 +12,8 @@ * auto state_coupling_fields = registerStateVariableFields( * field_store, state_rule, params...); * - * auto [state_sys, cycle_zero, ends] = buildStateVariableSystem( - * field_store, state_rule, [solid_coupling,] solver, opts, params...); + * auto state_sys = buildStateVariableSystem( + * field_store, solver, opts, params..., solid_coupling); * * The returned CouplingParams from registerStateVariableFields carries field tokens * (state_solve_state, state) that can be injected into another physics system @@ -46,7 +46,7 @@ namespace smith { /** * @brief System for a single internal variable using a two-state first-order rule. * - * Field layout: (state_solve_state, state) — 2 fields. + * Field layout: (state_solve_state, state) - 2 fields. * With a non-empty Coupling, coupling fields appear after the two state fields, * before user parameter fields. * @@ -149,13 +149,15 @@ auto registerStateVariableFields(std::shared_ptr field_store, Intern * Pass the same rule instance used in registerStateVariableFields so its type is deduced; * only `` need be specified. * - * Returns `{system, nullptr, {}}` as a tuple (no cycle-zero or end-step systems). + * Additional parameter packs are registered as parameters. Coupling packs are taken from + * the trailing field-pack arguments. */ +namespace detail { + template requires detail::is_coupling_params_v -auto buildStateVariableSystem(std::shared_ptr field_store, InternalVarTimeRule /*rule*/, - const Coupling& coupling, std::shared_ptr solver, - const StateVariableOptions& /*options*/) +auto buildStateVariableSystemImpl(std::shared_ptr field_store, const Coupling& coupling, + std::shared_ptr solver, const StateVariableOptions& /*options*/) { auto state_time_rule_ptr = std::make_shared(); @@ -180,31 +182,42 @@ auto buildStateVariableSystem(std::shared_ptr field_store, InternalV sys->state_time_rule = state_time_rule_ptr; sys->state_weak_form = state_weak_form; - std::shared_ptr cycle_zero_system; - std::vector> end_step_systems; - return std::make_tuple(sys, cycle_zero_system, end_step_systems); + return sys; } +} // namespace detail + /** * @brief Build a StateVariableSystem without coupling. * - * Overload for the common case of no inter-physics coupling. - * Parameters (if any) are wrapped into a CouplingParams so the system sees them. + * Overload for already-registered field packs. */ -template -auto buildStateVariableSystem(std::shared_ptr field_store, InternalVarTimeRule rule, - std::shared_ptr solver, const StateVariableOptions& options, - FieldType... parameter_types) +template + requires(sizeof...(FieldPacks) > 0 && (detail::is_coupling_params_v || ...)) +auto buildStateVariableSystem(std::shared_ptr field_store, std::shared_ptr solver, + const StateVariableOptions& options, const FieldPacks&... field_packs) { - if constexpr (sizeof...(parameter_space) > 0) { - auto prefix_param = [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }; - (prefix_param(parameter_types), ...); - } - return buildStateVariableSystem(field_store, rule, CouplingParams{parameter_types...}, solver, - options); + (detail::registerParamsIfNeeded(field_store, field_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, field_packs...); + return detail::buildStateVariableSystemImpl(field_store, coupling, solver, + options); +} + +template +auto buildStateVariableSystem(std::shared_ptr field_store, std::shared_ptr solver, + const StateVariableOptions& options) +{ + return detail::buildStateVariableSystemImpl(field_store, CouplingParams<>{}, + solver, options); +} + +template + requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...)) +auto buildStateVariableSystem(std::shared_ptr solver, const StateVariableOptions& options, + const FieldPacks&... field_packs) +{ + auto field_store = detail::findFieldStore(field_packs...); + return buildStateVariableSystem(field_store, solver, options, field_packs...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index ca9a5a6689..6df524ad27 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -101,20 +101,4 @@ inline std::shared_ptr makeSystem(std::shared_ptr field_ return std::make_shared(std::move(field_store), std::move(solver), std::move(weak_forms)); } -/** - * @brief Return type for physics system build functions. - * - * Replaces the previous std::tuple return so callers can write - * @code - * auto res = buildSolidMechanicsSystem(...); - * auto system = res.system; - * @endcode - */ -template -struct SystemBuildResult { - std::shared_ptr system; ///< Main system returned by the factory. - std::shared_ptr cycle_zero_system; ///< Optional startup system. - std::vector> end_step_systems; ///< Optional systems solved after each main step. -}; - } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 904c42fd78..eb47a59947 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -13,6 +13,7 @@ #include "smith/physics/state/state_manager.hpp" #include "smith/physics/mesh.hpp" #include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" +#include "smith/physics/materials/solid_material.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/system_solver.hpp" @@ -113,16 +114,14 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto [solid, solid_cycle_zero, solid_end_steps] = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, param_fields, solid_fields, thermal_fields); + auto solid = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); - auto [thermal, thermal_cycle_zero, thermal_end_steps] = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, param_fields, thermal_fields, solid_fields); + auto thermal = buildThermalSystem( + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto combined = combineSystems(solid, thermal); auto coupled = combined.system; - auto coupled_cycle_zero = combined.cycle_zero_system; - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); const auto& solid_dual_space = physics->dual("reactions").space(); const auto& solid_state_space = physics->state("displacement_solve_state").space(); @@ -151,16 +150,14 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto [solid, solid_cycle_zero, solid_end_steps] = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, param_fields, solid_fields, thermal_fields); + auto solid = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); - auto [thermal, thermal_cycle_zero, thermal_end_steps] = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, param_fields, thermal_fields, solid_fields); + auto thermal = buildThermalSystem( + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto combined = combineSystems(solid, thermal); auto coupled = combined.system; - auto coupled_cycle_zero = combined.cycle_zero_system; - thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -223,21 +220,19 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( - field_store_, thermal_fields, makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}); - auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( - field_store_, solid_fields, makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}); + auto solid = buildSolidMechanicsSystem( + field_store_, makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, thermal_fields); + auto thermal = buildThermalSystem( + field_store_, makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, solid_fields); auto combined = combineSystems(solid, thermal); auto coupled = combined.system; - auto coupled_cycle_zero = combined.cycle_zero_system; - thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - double deflection = advanceOneStepAndGetLateralDeflection(coupled, coupled_cycle_zero); + double deflection = advanceOneStepAndGetLateralDeflection(coupled, combined.cycle_zero_system); EXPECT_GT(deflection, 1e-5); } @@ -255,51 +250,38 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( - field_store_, thermal_fields, nullptr, SolidMechanicsOptions{}); - auto [thermal, thermal_cycle_zero, thermal_end] = - buildThermalSystem(field_store_, solid_fields, nullptr, ThermalOptions{}); + auto solid = buildSolidMechanicsSystem(field_store_, nullptr, + SolidMechanicsOptions{}, thermal_fields); + auto thermal = + buildThermalSystem(field_store_, nullptr, ThermalOptions{}, solid_fields); auto combined = combineSystems(solver_ptr, solid, thermal); auto coupled = combined.system; - auto coupled_cycle_zero = combined.cycle_zero_system; - thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - double deflection = advanceOneStepAndGetLateralDeflection(coupled, coupled_cycle_zero); + double deflection = advanceOneStepAndGetLateralDeflection(coupled, combined.cycle_zero_system); EXPECT_GT(deflection, 1e-5); } -// Simple linear elastic material for stress output test. -struct StressOutputLinearElastic { - double density = 1.0; - using State = Empty; - template - auto operator()(State&, DerivType grad_u) const - { - double E = 100.0, nu = 0.25; - double lam = E * nu / ((1 + nu) * (1 - 2 * nu)); - double mu = E / (2 * (1 + nu)); - auto eps = sym(grad_u); - static constexpr auto I = Identity(); - return lam * tr(eps) * I + 2.0 * mu * eps; - } -}; - // 5. CauchyStressOutput TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) { SolidMechanicsOptions solid_opts{.enable_stress_output = true, .output_cauchy_stress = true}; registerSolidMechanicsFields(field_store_); - auto [sys, cycle_zero, end_steps] = buildSolidMechanicsSystem( + auto sys = buildSolidMechanicsSystem( field_store_, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts); - sys->setMaterial(StressOutputLinearElastic{}, mesh_->entireBodyName()); + constexpr double E = 100.0; + constexpr double nu = 0.25; + constexpr double G = E / (2.0 * (1.0 + nu)); + constexpr double K = E / (3.0 * (1.0 - 2.0 * nu)); + + sys->setMaterial(solid_mechanics::NeoHookean{.density = 1.0, .K = K, .G = G}, mesh_->entireBodyName()); sys->setDisplacementBC(mesh_->domain("left")); sys->addTraction("right", [](double, auto X, auto, auto, auto, auto) { @@ -308,10 +290,10 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) return t; }); - auto physics = makeDifferentiablePhysics(sys, "cauchy_physics", cycle_zero, end_steps); + auto physics = makeDifferentiablePhysics(sys, "cauchy_physics"); physics->advanceTimestep(1.0); - ASSERT_FALSE(end_steps.empty()) << "Stress output system should be present"; + ASSERT_FALSE(sys->post_solve_systems.empty()) << "Stress output system should be present"; auto states = physics->getFieldStates(); size_t stress_idx = field_store_->getFieldIndex("stress_solve_state"); double stress_norm = norm(*states[stress_idx].get()); @@ -325,15 +307,15 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( - field_store_, thermal_fields, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts); - auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( - field_store_, solid_fields, makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}); + auto solid = buildSolidMechanicsSystem( + field_store_, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, thermal_fields); + auto thermal = buildThermalSystem( + field_store_, makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, solid_fields); auto combined = combineSystems(solid, thermal); - ASSERT_EQ(combined.end_step_systems.size(), solid_end.size()); - ASSERT_EQ(combined.system->post_solve_systems.size(), solid_end.size()); + ASSERT_EQ(combined.end_step_systems.size(), solid->post_solve_systems.size()); + ASSERT_EQ(combined.system->post_solve_systems.size(), solid->post_solve_systems.size()); EXPECT_FALSE(combined.end_step_systems.empty()); } diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 93e289f14b..8da21585fa 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -107,17 +107,15 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) auto field_store = std::make_shared(mesh, 100, "impl"); using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; registerSolidMechanicsFields(field_store); - auto [sys, cycle_zero, ends] = - buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); - EXPECT_NE(cycle_zero, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; + auto sys = buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); + EXPECT_NE(sys->cycle_zero_system, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { auto field_store = std::make_shared(mesh, 100, "qs"); using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; registerSolidMechanicsFields(field_store); - auto [sys, cycle_zero, ends] = - buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); - EXPECT_EQ(cycle_zero, nullptr) << "QuasiStatic has no initial acceleration solve"; + auto sys = buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); + EXPECT_EQ(sys->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; } } @@ -135,7 +133,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) registerParameterFields(FieldType("bulk"), FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); - auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsSystem( + auto system = buildSolidMechanicsSystem( coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, param_fields, solid_fields); static constexpr double gravity = -9.0; @@ -178,7 +176,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) size_t cycle = 0; std::vector reactions; - auto advancer = makeAdvancer(system, cycle_zero_sys, end_steps); + auto advancer = makeAdvancer(system); for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); @@ -242,10 +240,10 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr("bulk"), FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); - auto [system, cycle_zero_sys, end_steps] = buildSolidMechanicsSystem( - coupled_solver, SolidMechanicsOptions{}, param_fields, solid_fields); + auto system = buildSolidMechanicsSystem(coupled_solver, SolidMechanicsOptions{}, param_fields, + solid_fields); - auto physics = makeDifferentiablePhysics(system, physics_name, cycle_zero_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, physics_name); auto bcs = system->disp_bc; bcs->setFixedVectorBCs(mesh->domain("right")); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 0e7bdd59ca..9ca21c1930 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -96,8 +96,6 @@ struct StrainNormEvolution { using std::sqrt; auto epsilon = sym(deriv_u); auto eps_norm = sqrt(inner(epsilon, epsilon) + 1e-16); - - // ODE: isv_dot = (1 - isv) * eps_norm return isv_dot - (1.0 - isv) * eps_norm; } }; @@ -118,16 +116,13 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto state_fields = registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - auto solid_res = buildSolidMechanicsSystem( - field_store, state_fields, solid_solver, SolidMechanicsOptions{}); - auto solid = solid_res.system; - auto [state, state_cycle_zero, state_end_steps] = buildStateVariableSystem( - field_store, BackwardEulerFirstOrderTimeIntegrationRule{}, solid_fields, state_solver, StateVariableOptions{}); + auto solid = buildSolidMechanicsSystem( + field_store, solid_solver, SolidMechanicsOptions{}, state_fields); + auto state = buildStateVariableSystem( + field_store, state_solver, StateVariableOptions{}, solid_fields); auto combined = combineSystems(coupled_solver, solid, state); auto system = combined.system; - auto cycle_zero_sys = combined.cycle_zero_system; - auto end_steps = combined.end_step_systems; // Material and Evolution setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); @@ -148,7 +143,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) return u; }); - auto physics = makeDifferentiablePhysics(system, "physics", cycle_zero_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, "physics"); // Create ParaView writer auto writer = createParaviewWriter(*mesh, system->field_store->getOutputFieldStates(), "solid_state_output"); @@ -183,17 +178,13 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto state_fields = registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - auto solid_res = buildSolidMechanicsSystem( - field_store, state_fields, solid_system_solver, SolidMechanicsOptions{}); - auto solid = solid_res.system; - auto [state, state_cycle_zero, state_end_steps] = - buildStateVariableSystem(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}, solid_fields, - state_system_solver, StateVariableOptions{}); + auto solid = buildSolidMechanicsSystem( + field_store, solid_system_solver, SolidMechanicsOptions{}, state_fields); + auto state = buildStateVariableSystem( + field_store, state_system_solver, StateVariableOptions{}, solid_fields); auto combined = combineSystems(staggered_solver, solid, state); auto system = combined.system; - auto cycle_zero_sys = combined.cycle_zero_system; - auto end_steps = combined.end_step_systems; setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { @@ -208,7 +199,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) return u; }); - auto physics = makeDifferentiablePhysics(system, "physics_relaxed", cycle_zero_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, "physics_relaxed"); for (int step = 1; step <= 3; ++step) { physics->advanceTimestep(1.0); SLIC_INFO("Staggered relaxation step " << step << " completed"); @@ -230,16 +221,13 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto state_fields = registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - auto solid_res = buildSolidMechanicsSystem( - field_store, state_fields, solid_solver, SolidMechanicsOptions{}); - auto solid = solid_res.system; - auto [state, state_cycle_zero, state_end_steps] = buildStateVariableSystem( - field_store, BackwardEulerFirstOrderTimeIntegrationRule{}, solid_fields, state_solver, StateVariableOptions{}); + auto solid = buildSolidMechanicsSystem( + field_store, solid_solver, SolidMechanicsOptions{}, state_fields); + auto state = buildStateVariableSystem( + field_store, state_solver, StateVariableOptions{}, solid_fields); auto combined = combineSystems(coupled_solver, solid, state); auto system = combined.system; - auto cycle_zero_sys = combined.cycle_zero_system; - auto end_steps = combined.end_step_systems; setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { @@ -265,7 +253,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) return t; }); - auto physics = makeDifferentiablePhysics(system, "physics_bf", cycle_zero_sys, end_steps); + auto physics = makeDifferentiablePhysics(system, "physics_bf"); physics->advanceTimestep(1.0); // Check that the displacement field is non-zero (the body force + traction produced deformation) diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index e19d1095b2..7981d6fe4e 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -56,10 +56,7 @@ struct ThermalStaticFixture : public testing::Test { using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; registerThermalFields<2, temp_order, TempRule>(field_store); - auto thermal_res = buildThermalSystem<2, temp_order, TempRule>(field_store, coupled_solver, ThermalOptions{}); - auto thermal_system = thermal_res.system; - auto cycle_zero_sys = thermal_res.cycle_zero_system; - auto end_steps = thermal_res.end_step_systems; + auto thermal_system = buildThermalSystem<2, temp_order, TempRule>(field_store, coupled_solver, ThermalOptions{}); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -79,7 +76,7 @@ struct ThermalStaticFixture : public testing::Test { [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system, cycle_zero_sys, end_steps) + auto [new_states, reactions] = makeAdvancer(thermal_system) ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); @@ -155,7 +152,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto param_fields = registerParameterFields(conductivity_param); auto thermal_fields = registerThermalFields<2, 1, TempRule>(field_store); - auto [thermal_system, cycle_zero_sys, end_steps] = + auto thermal_system = buildThermalSystem<2, 1, TempRule>(coupled_solver, ThermalOptions{}, param_fields, thermal_fields); // Set the conductivity parameter field to k=1.0 @@ -182,7 +179,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); - auto [new_states, reactions] = makeAdvancer(thermal_system, cycle_zero_sys, end_steps) + auto [new_states, reactions] = makeAdvancer(thermal_system) ->advanceState(t_info, thermal_system->field_store->getShapeDisp(), thermal_system->field_store->getAllFields(), thermal_system->field_store->getParameterFields()); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index cb7bf3302c..b80ebb5e00 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -40,7 +40,7 @@ static constexpr int dim3 = 3; static constexpr int disp_ord = 1; static constexpr int temp_ord = 1; static constexpr int state_ord = 0; -using ThreeSystemStateSpace = L2; +using StateSpace = L2; struct ThreeSystemMeshFixture : public testing::Test { void SetUp() override @@ -91,22 +91,21 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) // the displacement field tokens available for thermal coupling. auto solid_exported = registerSolidMechanicsFields(field_store); auto thermal_exported = registerThermalFields(field_store, temp_rule); - registerStateVariableFields(field_store, state_rule); + registerStateVariableFields(field_store, state_rule); // Phase 2: build each system. // Solid receives thermal coupling (temperature_solve_state, temperature). - auto [solid, solid_cycle_zero, solid_end] = buildSolidMechanicsSystem( - field_store, thermal_exported, std::make_shared(solid_block_solver), SolidMechanicsOptions{}); + auto solid = buildSolidMechanicsSystem( + field_store, std::make_shared(solid_block_solver), SolidMechanicsOptions{}, thermal_exported); // Thermal is standalone (no coupling back from solid for this test). - auto [thermal, thermal_cycle_zero, thermal_end] = buildThermalSystem( - field_store, CouplingParams<>{}, std::make_shared(thermal_block_solver), ThermalOptions{}); + auto thermal = buildThermalSystem( + field_store, std::make_shared(thermal_block_solver), ThermalOptions{}); // StateVariable receives solid displacement coupling (4 fields: disp_ss, displacement, velocity, acceleration). - auto [state_sys, state_cycle_zero, state_end] = buildStateVariableSystem( - field_store, state_rule, solid_exported, std::make_shared(state_block_solver), - StateVariableOptions{}); + auto state_sys = buildStateVariableSystem( + field_store, std::make_shared(state_block_solver), StateVariableOptions{}, solid_exported); // Phase 3: register material integrands. diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 7f759049dd..12479aa38d 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -194,7 +194,7 @@ auto registerThermalFields(std::shared_ptr field_store, TemperatureT } /** - * @brief Register thermal fields (no rule instance — Rule given as explicit template param). + * @brief Register thermal fields (no rule instance - Rule given as explicit template param). */ template auto registerThermalFields(std::shared_ptr field_store) @@ -203,19 +203,18 @@ auto registerThermalFields(std::shared_ptr field_store) } /** - * @brief Build a ThermalSystem with coupling, assuming fields are already registered. + * @brief Internal thermal builder after coupling fields are assembled. * * Phase 2 of the two-phase initialization. Pass the same rule instance used in * registerThermalFields so the type is deduced; only `` need be specified. * - * Returns `{system, cycle_zero_system, end_step_systems}` as a tuple. - * `cycle_zero_system` is always nullptr (thermal has no cycle-zero solve). - * `end_step_systems` is always empty. */ +namespace detail { + template requires detail::is_coupling_params_v -auto buildThermalSystem(std::shared_ptr field_store, TemperatureTimeRule /*rule*/, const Coupling& coupling, - std::shared_ptr solver, const ThermalOptions& /*options*/) +auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupling& coupling, + std::shared_ptr solver, const ThermalOptions& /*options*/) { auto temperature_time_rule_ptr = std::make_shared(); @@ -243,44 +242,32 @@ auto buildThermalSystem(std::shared_ptr field_store, TemperatureTime sys->temperature_time_rule = temperature_time_rule_ptr; sys->thermal_weak_form = thermal_weak_form; - return std::make_tuple(sys, std::shared_ptr{}, std::vector>{}); + return sys; } +} // namespace detail + /** - * @brief Build a ThermalSystem from a coupling pack and optional parameter fields. + * @brief Build a ThermalSystem from already-registered field packs. * * Preferred API: Rule is given as explicit template param (no rule instance needed). - * The coupling argument carries the fields borrowed from another physics (or CouplingParams<> for none). - * Additional parameter_types are registered and appended after the coupling fields. - * - * Returns a @c SystemBuildResult so callers can write @c res.system, @c res.cycle_zero_system, etc. + * Additional parameter packs are registered as parameters. Coupling packs are taken from + * the trailing field-pack arguments. * * Usage: * @code - * auto res = buildThermalSystem( - * field_store, solid_fields, solver, opts); - * auto thermal = res.system; + * auto thermal = buildThermalSystem( + * field_store, solver, opts, solid_fields); * @endcode */ -template - requires detail::is_coupling_params_v -auto buildThermalSystem(std::shared_ptr field_store, const Coupling& coupling, - std::shared_ptr solver, const ThermalOptions& options, - FieldType... parameter_types) +template + requires(sizeof...(FieldPacks) > 0 && (detail::is_coupling_params_v || ...)) +auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr solver, + const ThermalOptions& options, const FieldPacks&... field_packs) { - ( - [&](auto& pt) { - pt.name = "param_" + pt.name; - field_store->addParameter(pt); - }(parameter_types), - ...); - - auto combined = std::apply([&](auto... cfs) { return CouplingParams{cfs..., parameter_types...}; }, coupling.fields); - - auto [sys, cycle_zero, ends] = - buildThermalSystem(field_store, TemperatureTimeRule{}, combined, solver, options); - using SysType = typename decltype(sys)::element_type; - return SystemBuildResult{std::move(sys), std::move(cycle_zero), std::move(ends)}; + (detail::registerParamsIfNeeded(field_store, field_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, field_packs...); + return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } /** @@ -292,21 +279,22 @@ auto buildThermalSystem(std::shared_ptr field_store, const Coupling& * * Usage: * @code - * auto [thermal, cycle_zero, end] = buildThermalSystem( + * auto thermal = buildThermalSystem( * solver, opts, param_fields, thermal_fields, solid_fields); * @endcode */ /** * @brief Build a standalone ThermalSystem with no coupling and no parameters. * - * Convenience overload for single-physics tests and demos — avoids the `CouplingParams<>{}` + * Convenience overload for single-physics tests and demos - avoids the `CouplingParams<>{}` * placeholder at the call site. */ template auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr solver, const ThermalOptions& options) { - return buildThermalSystem(field_store, CouplingParams<>{}, solver, options); + return detail::buildThermalSystemImpl(field_store, CouplingParams<>{}, solver, + options); } /** @@ -323,9 +311,7 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio const FieldPacks&... field_packs) { auto field_store = detail::findFieldStore(field_packs...); - (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, field_packs...); - return buildThermalSystem(field_store, TemperatureTimeRule{}, coupling, solver, options); + return buildThermalSystem(field_store, solver, options, field_packs...); } } // namespace smith From f1b4327deafb42adedb48020bddf922885f2ce2f Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 22 Apr 2026 14:52:04 -0700 Subject: [PATCH 35/67] Trying to unify and simplify the interface more. --- .../combined_system.hpp | 11 +- .../solid_mechanics_system.hpp | 186 +++++++++--------- ...id_mechanics_with_internal_vars_system.hpp | 70 ++++++- .../state_variable_system.hpp | 162 ++++++++------- .../tests/test_combined_thermo_mechanics.cpp | 23 +-- .../tests/test_solid_dynamics.cpp | 21 +- .../test_solid_static_with_internal_vars.cpp | 154 +++++++-------- .../tests/test_thermal_static.cpp | 6 +- .../test_thermo_mechanics_three_system.cpp | 129 ++++++------ .../thermal_system.hpp | 73 ++----- .../thermo_mechanical_system.hpp | 2 +- .../time_integration_rule.hpp | 47 +++++ 12 files changed, 465 insertions(+), 419 deletions(-) diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index 580114766c..d8b734340e 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -15,17 +15,14 @@ * Usage: * @code * auto field_store = std::make_shared(mesh, 100, "coupled"); - * - * CouplingParams solid_coupling{FieldType>("temperature"), - * FieldType>("temperature_old")}; - * CouplingParams thermal_coupling{FieldType>("displacement_solve_state"), - * FieldType>("displacement")}; + * auto solid_fields = registerSolidMechanicsFields(field_store); + * auto thermal_fields = registerThermalFields(field_store); * * auto solid = buildSolidMechanicsSystem( - * field_store, solid_solver, solid_opts, params..., solid_coupling); + * solid_solver, solid_opts, solid_fields, params..., thermal_fields); * * auto thermal = buildThermalSystem( - * field_store, thermal_solver, thermal_opts, thermal_coupling); + * thermal_solver, thermal_opts, thermal_fields, solid_fields); * * auto coupled = combineSystems(solid, thermal); * coupled->setMaterial(thermo_mech_material, domain); // tight coupling diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 0e4103b0a5..54410fce76 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -25,6 +25,22 @@ namespace smith { +namespace detail { + +template +auto scaleCycleZeroTerm(const TimeInfo& t_info, ValueType value) +{ + return (t_info.dt() * t_info.dt()) * value; +} + +template +auto makeScaledCycleZeroResidual(const TimeInfo& t_info, InertiaType inertia, StressType stress) +{ + return smith::tuple{scaleCycleZeroTerm(t_info, inertia), scaleCycleZeroTerm(t_info, stress)}; +} + +} // namespace detail + /** * @brief System struct for solid dynamics with configurable time integration. * @@ -52,8 +68,8 @@ struct SolidMechanicsSystem : public SystemBase { dim, H1, typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; - /// Cycle-zero form: (u, v_old, a, coupling_fields..., params...) - /// 3-state form: u, v, a (no u_old needed; at cycle 0 u and v are given, solve for a) + /// Cycle-zero startup form reusing the main solid fields: (u, v_old, a, coupling_fields..., params...) + /// No extra fields are registered for cycle zero; acceleration is the unknown for this internal solve. using CycleZeroSolidWeakFormType = TimeDiscretizedWeakForm, typename detail::AppendCouplingToParams< @@ -99,11 +115,11 @@ struct SolidMechanicsSystem : public SystemBase { // Add to cycle-zero weak form (at cycle 0, u and v are given, solve for a) if (cycle_zero_solid_weak_form) { cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v_old*/, auto a, auto... params) { + domain_name, [=](auto t_info, auto /*X*/, auto u, auto /*v_old*/, auto a, auto... params) { typename MaterialType::State state; auto pk_stress = material(state, get(u), params...); - return smith::tuple{get(a) * material.density, pk_stress}; + return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk_stress); }); } @@ -159,7 +175,7 @@ struct SolidMechanicsSystem : public SystemBase { addCycleZeroBodySourceImpl( domain_name, [=](auto t_info, auto X, auto u, auto v_old, auto a, auto... params) { - return force_function(t_info.time(), X, u, v_old, a, params...); + return detail::scaleCycleZeroTerm(t_info, force_function(t_info.time(), X, u, v_old, a, params...)); }, std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); } @@ -199,7 +215,7 @@ struct SolidMechanicsSystem : public SystemBase { addCycleZeroBoundaryFluxImpl( domain_name, [=](auto t_info, auto X, auto n, auto u, auto v_old, auto a, auto... params) { - return traction_function(t_info.time(), X, n, u, v_old, a, params...); + return detail::scaleCycleZeroTerm(t_info, traction_function(t_info.time(), X, n, u, v_old, a, params...)); }, std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); } @@ -253,7 +269,7 @@ struct SolidMechanicsSystem : public SystemBase { auto pressure = pressure_function(t_info.time(), get(X), get(params)...); - return pressure * n_deformed * (1.0 / n_shape_norm); + return detail::scaleCycleZeroTerm(t_info, pressure * n_deformed * (1.0 / n_shape_norm)); }, std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); } @@ -335,20 +351,20 @@ struct SolidMechanicsSystem : public SystemBase { * @brief Optional auxiliary systems and outputs for solid mechanics. */ struct SolidMechanicsOptions { - bool enable_stress_output = false; ///< Build post-solve stress projection system. + bool enable_stress_output = false; ///< Register stress output fields during phase 1. bool output_cauchy_stress = false; ///< When true, project Cauchy stress (sigma) instead of PK1 (P). }; /** * @brief Register all solid mechanics fields into a FieldStore. * - * Phase 1 of the two-phase initialization. Pass an instance of the desired time integration rule - * so its type is deduced; only `` need be specified explicitly. + * Phase 1 of the two-phase initialization. * * @return PhysicsFields carrying the exported field tokens and time rule type. */ template -auto registerSolidMechanicsFields(std::shared_ptr field_store, DisplacementTimeRule /*rule*/) +auto registerSolidMechanicsFields(std::shared_ptr field_store, + const SolidMechanicsOptions& options = SolidMechanicsOptions{}) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { @@ -363,38 +379,61 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, Displ field_store->addDependent(disp_type, FieldStore::TimeDerivative::DOT, "velocity"); field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); - return PhysicsFields, H1, H1, H1>{ + auto physics_fields = + PhysicsFields, H1, H1, H1>{ field_store, FieldType>(field_store->prefix("displacement_solve_state")), FieldType>(field_store->prefix("displacement")), FieldType>(field_store->prefix("velocity")), FieldType>(field_store->prefix("acceleration"))}; -} -/** - * @brief Register all solid mechanics fields (no rule instance - Rule given as explicit template param). - * - * Preferred form: Rule is deduced from the PhysicsFields type rather than a runtime instance. - * Equivalent to the rule-instance overload but avoids requiring a dummy rule object. - */ -template -auto registerSolidMechanicsFields(std::shared_ptr field_store) -{ - return registerSolidMechanicsFields(field_store, DisplacementTimeRule{}); -} + if (options.enable_stress_output) { + auto stress_time_rule = std::make_shared(); + FieldType> stress_type("stress_solve_state"); + field_store->addIndependent(stress_type, stress_time_rule); + field_store->addDependent(stress_type, FieldStore::TimeDerivative::VAL, "stress"); + } + return physics_fields; +} /** * @brief Internal solid mechanics builder after coupling fields are assembled. * - * Phase 2 of the two-phase initialization. Pass the same rule instance used in - * registerSolidMechanicsFields so the type is deduced; only `` need be specified. - * + * Phase 2 of the two-phase initialization. */ namespace detail { +inline bool hasRegisteredStressOutput(const std::shared_ptr& field_store) +{ + return field_store->hasField(field_store->prefix("stress_solve_state")); +} + +inline std::shared_ptr makeCycleZeroSolver(std::shared_ptr solver, const Mesh& mesh) +{ + if (solver) { + if (auto derived_solver = solver->singleBlockSolver(0)) { + return derived_solver; + } + } + + NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 2, + .print_level = 0}; + LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-14, + .absolute_tol = 1e-14, + .max_iterations = 1000, + .print_level = 0}; + return std::make_shared(buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, mesh)); +} + template requires detail::is_coupling_params_v auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, const Coupling& coupling, - std::shared_ptr solver, const SolidMechanicsOptions& options) + std::shared_ptr solver, const SolidMechanicsOptions& options, + bool has_stress_output) { auto disp_time_rule_ptr = std::make_shared(); @@ -410,11 +449,11 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons std::string force_name = field_store->prefix("reactions"); auto solid_weak_form = std::apply( - [&](auto&... cfs) { + [&](auto&... coupling_fields) { return std::make_shared( force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, cfs...)); + accel_old_type, coupling_fields...)); }, coupling.fields); @@ -425,52 +464,34 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons sys->output_cauchy_stress = options.output_cauchy_stress; if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { - std::string cycle_zero_name = field_store->prefix("solid_reaction"); + std::string cycle_zero_name = field_store->prefix("cycle_zero_acceleration_reaction"); auto accel_as_unknown = accel_old_type; accel_as_unknown.is_unknown = true; FieldType> disp_cz_input(disp_type.name); sys->cycle_zero_solid_weak_form = std::apply( - [&](auto&... cfs) { + [&](auto&... coupling_fields) { return std::make_shared( cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, cfs...)); + accel_as_unknown, coupling_fields...)); }, coupling.fields); field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); - - NonlinearSolverOptions cycle_zero_nonlin{.nonlin_solver = NonlinearSolver::Newton, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 2, - .print_level = 0}; - LinearSolverOptions cycle_zero_lin{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-14, - .absolute_tol = 1e-14, - .max_iterations = 1000, - .print_level = 0}; - auto cycle_zero_solver = std::make_shared( - buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, *field_store->getMesh())); - + auto cycle_zero_solver = detail::makeCycleZeroSolver(solver, *field_store->getMesh()); sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); } - if (options.enable_stress_output) { - auto stress_time_rule = std::make_shared(); - FieldType> stress_type("stress_solve_state"); - field_store->addIndependent(stress_type, stress_time_rule); - field_store->addDependent(stress_type, FieldStore::TimeDerivative::VAL, "stress"); - + if (has_stress_output) { + FieldType> stress_type(field_store->prefix("stress_solve_state"), true); FieldType> disp_as_input(disp_type.name); std::string stress_name = field_store->prefix("stress_projection"); sys->stress_weak_form = std::apply( - [&](auto&... cfs) { + [&](auto&... coupling_fields) { return std::make_shared( stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, - velo_old_type, accel_old_type, cfs...)); + velo_old_type, accel_old_type, coupling_fields...)); }, coupling.fields); @@ -507,18 +528,22 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons * Usage: * @code * auto solid = buildSolidMechanicsSystem( - * field_store, solver, opts, youngs_modulus, thermal_fields); + * solver, opts, solid_fields, youngs_modulus, thermal_fields); * @endcode */ -template - requires(sizeof...(FieldPacks) > 0 && (detail::is_coupling_params_v || ...)) -auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::shared_ptr solver, - const SolidMechanicsOptions& options, const FieldPacks&... field_packs) +template + requires(detail::is_physics_fields_v && + std::is_same_v::time_rule_type, DisplacementTimeRule> && + (detail::is_coupling_params_v && ...)) +auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) { - (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, field_packs...); + auto field_store = self_fields.field_store; + (detail::registerParamsIfNeeded(field_store, other_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + bool has_stress_output = detail::hasRegisteredStressOutput(field_store); return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, - options); + options, has_stress_output); } /** @@ -531,38 +556,7 @@ auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::sha * Usage: * @code * auto solid = buildSolidMechanicsSystem( - * solver, opts, param_fields, solid_fields, thermal_fields); + * solver, opts, solid_fields, param_fields, thermal_fields); * @endcode */ -/** - * @brief Build a standalone SolidMechanicsSystem with no coupling and no parameters. - * - * Convenience overload for single-physics tests and demos - avoids the `CouplingParams<>{}` - * placeholder at the call site. - */ -template -auto buildSolidMechanicsSystem(std::shared_ptr field_store, std::shared_ptr solver, - const SolidMechanicsOptions& options) -{ - return detail::buildSolidMechanicsSystemImpl(field_store, CouplingParams<>{}, - solver, options); -} - -/** - * @brief Build a solid mechanics system from registered field packs. - * - * One `PhysicsFields` pack must come from the solid registration. Other `PhysicsFields` packs are - * treated as coupling inputs, while non-physics `CouplingParams` packs are registered as parameter - * fields. - */ -template - requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && - !(std::is_same_v, SolidMechanicsOptions> || ...)) -auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, - const FieldPacks&... field_packs) -{ - auto field_store = detail::findFieldStore(field_packs...); - return buildSolidMechanicsSystem(field_store, solver, options, field_packs...); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index ab546076ce..7989765a44 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -6,7 +6,7 @@ /** * @file solid_mechanics_with_internal_vars_system.hpp - * @brief Helper to wire an internal-variable material to composed solid and state systems. + * @brief Helpers to wire internal-variable materials to composed solid and internal-variable systems. */ #pragma once @@ -16,9 +16,26 @@ namespace smith { +namespace detail { + +template +auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const TimeInfoType& t_info, + AlphaType alpha, AlphaDotType alpha_dot, DerivType deriv_u, + ParamTypes&&... params) +{ + if constexpr (requires { material(t_info, alpha, alpha_dot, deriv_u, std::forward(params)...); }) { + return material(t_info, alpha, alpha_dot, deriv_u, std::forward(params)...); + } else { + return material(alpha, alpha_dot, deriv_u, std::forward(params)...); + } +} + +} // namespace detail + /** * @brief Register a solid material integrand on a SolidMechanicsSystem that is coupled to - * a StateVariableSystem carrying internal variables. + * an InternalVariableSystem carrying internal variables. * * Assumes: * - solid was built with state fields as leading coupling fields @@ -31,18 +48,18 @@ namespace smith { */ template -void setCoupledSolidMechanicsInternalVarsMaterial( +void setCoupledSolidMechanicsInternalVariableMaterial( std::shared_ptr> solid, - std::shared_ptr> state, const MaterialType& material, - const std::string& domain_name) + std::shared_ptr> internal_variables, + const MaterialType& material, const std::string& domain_name) { auto captured_disp_rule = solid->disp_time_rule; - auto captured_state_rule = state->state_time_rule; + auto captured_internal_variable_rule = internal_variables->internal_variable_time_rule; solid->solid_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto alpha, auto alpha_old, auto... params) { auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto alpha_current = captured_state_rule->value(t_info, alpha, alpha_old); + auto alpha_current = captured_internal_variable_rule->value(t_info, alpha, alpha_old); typename MaterialType::State material_state; auto pk_stress = material(material_state, get(u_current), get(alpha_current), params...); @@ -52,12 +69,47 @@ void setCoupledSolidMechanicsInternalVarsMaterial( if (solid->cycle_zero_solid_weak_form) { solid->cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { + domain_name, [=](auto t_info, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { typename MaterialType::State material_state; auto pk_stress = material(material_state, get(u), get(alpha), params...); - return smith::tuple{get(a) * material.density, pk_stress}; + return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk_stress); }); } } +/** + * @brief Register an internal-variable evolution law using solid displacement coupling. + * + * Evolution callable may satisfy either: + * material(t_info, alpha, alpha_dot, grad_u, params...) -> residual + * or + * material(alpha, alpha_dot, grad_u, params...) -> residual + */ +template +void setCoupledInternalVariableMaterial( + std::shared_ptr> internal_variables, + std::shared_ptr> solid, const MaterialType& material, + const std::string& domain_name) +{ + auto captured_disp_rule = solid->disp_time_rule; + internal_variables->setMaterial( + [=](auto t_info, auto alpha, auto alpha_dot, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + return detail::evaluateCoupledInternalVariableMaterial(material, t_info, alpha, alpha_dot, + get(u_current), params...); + }, + domain_name); +} + +template +void setCoupledSolidMechanicsInternalVarsMaterial( + std::shared_ptr> solid, + std::shared_ptr> state, const MaterialType& material, + const std::string& domain_name) +{ + setCoupledSolidMechanicsInternalVariableMaterial(solid, state, material, domain_name); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 40db2ec12e..7d6f8c5efe 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -9,17 +9,17 @@ * @brief Standalone composable system for a first-order internal variable (damage, plasticity, etc.). * * Two-phase factory: - * auto state_coupling_fields = registerStateVariableFields( - * field_store, state_rule, params...); + * auto internal_variable_fields = registerInternalVariableFields( + * field_store, params...); * - * auto state_sys = buildStateVariableSystem( - * field_store, solver, opts, params..., solid_coupling); + * auto internal_variables = buildInternalVariableSystem( + * solver, opts, internal_variable_fields, params..., solid_fields); * - * The returned CouplingParams from registerStateVariableFields carries field tokens + * The returned PhysicsFields from registerInternalVariableFields carries field tokens * (state_solve_state, state) that can be injected into another physics system * (e.g. SolidMechanicsSystem) as coupling input. * - * The system's addStateEvolution registers an ODE residual of the form: + * The system's setMaterial registers an ODE residual of the form: * evolution_law(t_info, alpha_val, alpha_dot, coupling_fields..., params...) == 0 * * With Coupling = CouplingParams, H1, H1, H1> @@ -57,18 +57,18 @@ namespace smith { */ template > -struct StateVariableSystem : public SystemBase { +struct InternalVariableSystem : public SystemBase { using SystemBase::SystemBase; - static_assert(InternalVarTimeRule::num_states == 2, "StateVariableSystem requires a 2-state time integration rule"); + static_assert(InternalVarTimeRule::num_states == 2, "InternalVariableSystem requires a 2-state time integration rule"); /// State weak form: (alpha, alpha_old, coupling_fields..., params...) - using StateWeakFormType = TimeDiscretizedWeakForm< + using InternalVariableWeakFormType = TimeDiscretizedWeakForm< dim, StateSpace, typename detail::TimeRuleParamsWithCoupling::type>; - std::shared_ptr state_weak_form; ///< Internal variable weak form. - std::shared_ptr state_bc; ///< Internal variable boundary conditions. - std::shared_ptr state_time_rule; ///< Time integration rule. + std::shared_ptr internal_variable_weak_form; ///< Internal variable weak form. + std::shared_ptr internal_variable_bc; ///< Internal variable BCs. + std::shared_ptr internal_variable_time_rule; ///< Time integration rule. /** * @brief Register an ODE evolution law for the internal variable. @@ -84,47 +84,58 @@ struct StateVariableSystem : public SystemBase { * @param evolution_law Callable returning the ODE residual. */ template - void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) + void addEvolution(const std::string& domain_name, EvolutionType evolution_law) { - auto captured_state_rule = state_time_rule; - state_weak_form->addBodyIntegral( + auto captured_time_rule = internal_variable_time_rule; + internal_variable_weak_form->addBodyIntegral( domain_name, [=](auto t_info, auto /*X*/, auto alpha, auto alpha_old, auto... coupling_and_params) { - auto [alpha_current, alpha_dot] = captured_state_rule->interpolate(t_info, alpha, alpha_old); + auto [alpha_current, alpha_dot] = captured_time_rule->interpolate(t_info, alpha, alpha_old); auto residual_val = evolution_law(t_info, get(alpha_current), get(alpha_dot), coupling_and_params...); tensor flux{}; return smith::tuple{residual_val, flux}; }); } + + template + void setMaterial(const MaterialType& material, const std::string& domain_name) + { + addEvolution(domain_name, material); + } + + template + void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) + { + addEvolution(domain_name, evolution_law); + } }; // --------------------------------------------------------------------------- // Options // --------------------------------------------------------------------------- -struct StateVariableOptions {}; +struct InternalVariableOptions {}; +using StateVariableOptions = InternalVariableOptions; // --------------------------------------------------------------------------- -// Phase 1: registerStateVariableFields +// Phase 1: registerInternalVariableFields // --------------------------------------------------------------------------- /** * @brief Register state variable fields into a FieldStore. * * Adds a 2-field layout: (state_solve_state, state). - * Pass an instance of the desired time integration rule so its type is deduced; - * only `` needs to be specified explicitly. * - * @return CouplingParams carrying (state_solve_state, state[, params...]) field tokens + * @return PhysicsFields carrying (state_solve_state, state) field tokens * suitable for injection into another physics system. */ template -auto registerStateVariableFields(std::shared_ptr field_store, InternalVarTimeRule /*rule*/, - FieldType... parameter_types) +auto registerInternalVariableFields(std::shared_ptr field_store, + FieldType... parameter_types) { - auto state_time_rule_ptr = std::make_shared(); + auto internal_variable_time_rule = std::make_shared(); FieldType state_type("state_solve_state"); - field_store->addIndependent(state_type, state_time_rule_ptr); + field_store->addIndependent(state_type, internal_variable_time_rule); field_store->addDependent(state_type, FieldStore::TimeDerivative::VAL, "state"); if constexpr (sizeof...(parameter_space) > 0) { @@ -135,52 +146,56 @@ auto registerStateVariableFields(std::shared_ptr field_store, Intern (prefix_param(parameter_types), ...); } - return CouplingParams{FieldType(field_store->prefix("state_solve_state")), - FieldType(field_store->prefix("state")), parameter_types...}; + return PhysicsFields{ + field_store, FieldType(field_store->prefix("state_solve_state")), + FieldType(field_store->prefix("state"))}; +} + +template +auto registerStateVariableFields(std::shared_ptr field_store, FieldType... parameter_types) +{ + return registerInternalVariableFields(field_store, parameter_types...); } // --------------------------------------------------------------------------- -// Phase 2: buildStateVariableSystem +// Phase 2: buildInternalVariableSystem // --------------------------------------------------------------------------- /** - * @brief Build a StateVariableSystem with coupling, assuming fields are already registered. - * - * Pass the same rule instance used in registerStateVariableFields so its type is deduced; - * only `` need be specified. - * - * Additional parameter packs are registered as parameters. Coupling packs are taken from - * the trailing field-pack arguments. + * @brief Build an InternalVariableSystem with coupling, assuming fields are already registered. */ namespace detail { template requires detail::is_coupling_params_v -auto buildStateVariableSystemImpl(std::shared_ptr field_store, const Coupling& coupling, - std::shared_ptr solver, const StateVariableOptions& /*options*/) +auto buildInternalVariableSystemImpl(std::shared_ptr field_store, const Coupling& coupling, + std::shared_ptr solver, + const InternalVariableOptions& /*options*/) { - auto state_time_rule_ptr = std::make_shared(); + auto internal_variable_time_rule = std::make_shared(); FieldType state_type(field_store->prefix("state_solve_state"), true); FieldType state_old_type(field_store->prefix("state")); - auto state_bc = field_store->getBoundaryConditions(state_type.name); + auto internal_variable_bc = field_store->getBoundaryConditions(state_type.name); - using SystemType = StateVariableSystem; + using SystemType = InternalVariableSystem; - std::string state_res_name = field_store->prefix("state_residual"); - auto state_weak_form = std::apply( - [&](auto&... cfs) { - return std::make_shared( - state_res_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), - field_store->createSpaces(state_res_name, state_type.name, state_type, state_old_type, cfs...)); + std::string internal_variable_residual_name = field_store->prefix("state_residual"); + auto internal_variable_weak_form = std::apply( + [&](auto&... coupling_fields) { + return std::make_shared( + internal_variable_residual_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), + field_store->createSpaces(internal_variable_residual_name, state_type.name, state_type, state_old_type, + coupling_fields...)); }, coupling.fields); - auto sys = std::make_shared(field_store, solver, std::vector>{state_weak_form}); - sys->state_bc = state_bc; - sys->state_time_rule = state_time_rule_ptr; - sys->state_weak_form = state_weak_form; + auto sys = + std::make_shared(field_store, solver, std::vector>{internal_variable_weak_form}); + sys->internal_variable_bc = internal_variable_bc; + sys->internal_variable_time_rule = internal_variable_time_rule; + sys->internal_variable_weak_form = internal_variable_weak_form; return sys; } @@ -188,36 +203,35 @@ auto buildStateVariableSystemImpl(std::shared_ptr field_store, const } // namespace detail /** - * @brief Build a StateVariableSystem without coupling. - * - * Overload for already-registered field packs. + * @brief Build an internal-variable system from registered field packs. */ -template - requires(sizeof...(FieldPacks) > 0 && (detail::is_coupling_params_v || ...)) -auto buildStateVariableSystem(std::shared_ptr field_store, std::shared_ptr solver, - const StateVariableOptions& options, const FieldPacks&... field_packs) +template + requires(detail::is_physics_fields_v && + std::is_same_v::time_rule_type, InternalVarTimeRule> && + (detail::is_coupling_params_v && ...)) +auto buildInternalVariableSystem(std::shared_ptr solver, const InternalVariableOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) { - (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, field_packs...); - return detail::buildStateVariableSystemImpl(field_store, coupling, solver, - options); + auto field_store = self_fields.field_store; + (detail::registerParamsIfNeeded(field_store, other_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + return detail::buildInternalVariableSystemImpl(field_store, coupling, solver, + options); } -template -auto buildStateVariableSystem(std::shared_ptr field_store, std::shared_ptr solver, - const StateVariableOptions& options) -{ - return detail::buildStateVariableSystemImpl(field_store, CouplingParams<>{}, - solver, options); -} - -template - requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...)) +template + requires(detail::is_physics_fields_v && + std::is_same_v::time_rule_type, InternalVarTimeRule> && + (detail::is_coupling_params_v && ...)) auto buildStateVariableSystem(std::shared_ptr solver, const StateVariableOptions& options, - const FieldPacks&... field_packs) + const SelfFields& self_fields, const OtherPacks&... other_packs) { - auto field_store = detail::findFieldStore(field_packs...); - return buildStateVariableSystem(field_store, solver, options, field_packs...); + return buildInternalVariableSystem(solver, options, self_fields, + other_packs...); } +template > +using StateVariableSystem = InternalVariableSystem; + } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index eb47a59947..fda269f9d0 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -221,9 +221,9 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto thermal_fields = registerThermalFields(field_store_); auto solid = buildSolidMechanicsSystem( - field_store_, makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, thermal_fields); + makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, thermal_fields); auto thermal = buildThermalSystem( - field_store_, makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, solid_fields); + makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, thermal_fields, solid_fields); auto combined = combineSystems(solid, thermal); auto coupled = combined.system; @@ -250,10 +250,11 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem(field_store_, nullptr, - SolidMechanicsOptions{}, thermal_fields); - auto thermal = - buildThermalSystem(field_store_, nullptr, ThermalOptions{}, solid_fields); + auto solid = + buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + thermal_fields); + auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, + solid_fields); auto combined = combineSystems(solver_ptr, solid, thermal); auto coupled = combined.system; @@ -272,9 +273,9 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) { SolidMechanicsOptions solid_opts{.enable_stress_output = true, .output_cauchy_stress = true}; - registerSolidMechanicsFields(field_store_); + auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto sys = buildSolidMechanicsSystem( - field_store_, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts); + makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields); constexpr double E = 100.0; constexpr double nu = 0.25; @@ -304,13 +305,13 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) { SolidMechanicsOptions solid_opts{.enable_stress_output = true}; - auto solid_fields = registerSolidMechanicsFields(field_store_); + auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto thermal_fields = registerThermalFields(field_store_); auto solid = buildSolidMechanicsSystem( - field_store_, makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, thermal_fields); + makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields, thermal_fields); auto thermal = buildThermalSystem( - field_store_, makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, solid_fields); + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, solid_fields); auto combined = combineSystems(solid, thermal); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 8da21585fa..2edd462988 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -106,15 +106,15 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { auto field_store = std::make_shared(mesh, 100, "impl"); using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - registerSolidMechanicsFields(field_store); - auto sys = buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); EXPECT_NE(sys->cycle_zero_system, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { auto field_store = std::make_shared(mesh, 100, "qs"); using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; - registerSolidMechanicsFields(field_store); - auto sys = buildSolidMechanicsSystem(field_store, solver, SolidMechanicsOptions{}); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); EXPECT_EQ(sys->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; } } @@ -131,10 +131,11 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto solid_fields = registerSolidMechanicsFields(field_store); + auto solid_fields = registerSolidMechanicsFields( + field_store, SolidMechanicsOptions{.enable_stress_output = true}); auto system = buildSolidMechanicsSystem( - coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, param_fields, solid_fields); + coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, solid_fields, param_fields); static constexpr double gravity = -9.0; @@ -202,7 +203,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) return da0 * da0 + da1 * da1; }); double a_err = accel_error.evaluate(endTimeInfo, shape_disp.get().get(), getConstFieldPointers({states[3]})); - EXPECT_NEAR(0.0, a_err, 1e-14); + EXPECT_NEAR(0.0, a_err, 1e-12); // Test velocity (states[2] is velocity) FunctionalObjective> velo_error("velo_error", mesh, spaces({states[2]})); @@ -213,7 +214,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) return dv0 * dv0 + dv1 * dv1; }); double v_err = velo_error.evaluate(TimeInfo(0.0, 1.0, 0), shape_disp.get().get(), getConstFieldPointers({states[2]})); - EXPECT_NEAR(0.0, v_err, 1e-14); + EXPECT_NEAR(0.0, v_err, 1e-12); // Test displacement (states[1] is displacement) FunctionalObjective> disp_error("disp_error", mesh, spaces({states[1]})); @@ -240,8 +241,8 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr("bulk"), FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); - auto system = buildSolidMechanicsSystem(coupled_solver, SolidMechanicsOptions{}, param_fields, - solid_fields); + auto system = buildSolidMechanicsSystem(coupled_solver, SolidMechanicsOptions{}, solid_fields, + param_fields); auto physics = makeDifferentiablePhysics(system, physics_name); auto bcs = system->disp_bc; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 9ca21c1930..90dd1d5678 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -36,6 +36,8 @@ static constexpr int disp_order = 1; static constexpr int state_order = 0; using StateSpace = L2; +using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; +using InternalVariableRule = BackwardEulerFirstOrderTimeIntegrationRule; struct SolidStaticWithInternalVarsFixture : public testing::Test { void SetUp() override @@ -100,48 +102,67 @@ struct StrainNormEvolution { } }; -TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) +std::shared_ptr makeSystemSolver(const Mesh& mesh) { - auto solid_solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - auto state_solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); - - auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); - - auto solid_fields = - registerSolidMechanicsFields(field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); - auto state_fields = - registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - - auto solid = buildSolidMechanicsSystem( - field_store, solid_solver, SolidMechanicsOptions{}, state_fields); - auto state = buildStateVariableSystem( - field_store, state_solver, StateVariableOptions{}, solid_fields); - - auto combined = combineSystems(coupled_solver, solid, state); - auto system = combined.system; + return std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, mesh)); +} - // Material and Evolution - setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); - state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { - return StrainNormEvolution{}(t_info, isv, isv_dot, get(u)); - }); +auto registerFields(const std::shared_ptr& field_store) +{ + return std::tuple{registerSolidMechanicsFields(field_store), + registerInternalVariableFields(field_store)}; +} - // Boundary Conditions +template +auto buildSystems(const std::shared_ptr& solid_solver, + const std::shared_ptr& internal_variable_solver, const SolidFields& solid_fields, + const InternalVariableFields& internal_variable_fields) +{ + return std::tuple{ + buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + internal_variable_fields), + buildInternalVariableSystem(internal_variable_solver, + InternalVariableOptions{}, + internal_variable_fields, solid_fields)}; +} - // Fix bottom face - solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); +template +void setDamageCoupling(const std::shared_ptr& solid, + const std::shared_ptr& internal_variables, + const std::shared_ptr& mesh) +{ + setCoupledSolidMechanicsInternalVariableMaterial(solid, internal_variables, DamageMaterial{}, mesh->entireBodyName()); + setCoupledInternalVariableMaterial(internal_variables, solid, StrainNormEvolution{}, mesh->entireBodyName()); +} - // Pull top face - double pull_rate = 0.05; - solid->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { +template +void setPullBoundaryConditions(const std::shared_ptr& solid, const std::shared_ptr& mesh, + double pull_rate) +{ + solid->disp_bc->template setFixedVectorBCs(mesh->domain("bottom")); + solid->disp_bc->template setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { tensor u{}; u[2] = pull_rate * t; return u; }); +} + +TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) +{ + auto solid_solver = makeSystemSolver(*mesh); + auto internal_variable_solver = makeSystemSolver(*mesh); + auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto coupled_solver = std::make_shared(nonlinear_block_solver); + auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); + auto [solid_fields, internal_variable_fields] = registerFields(field_store); + auto [solid, internal_variables] = + buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); + + auto combined = combineSystems(coupled_solver, solid, internal_variables); + auto system = combined.system; + + setDamageCoupling(solid, internal_variables, mesh); + setPullBoundaryConditions(solid, mesh, 0.05); auto physics = makeDifferentiablePhysics(system, "physics"); @@ -158,46 +179,27 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) { - auto solid_system_solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - auto state_system_solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solid_solver = makeSystemSolver(*mesh); + auto internal_variable_solver = makeSystemSolver(*mesh); auto disp_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - auto state_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto internal_variable_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - // Staggered solver: stage 0 solves displacement (block 0), stage 1 solves state (block 1). + // Staggered solver: stage 0 solves displacement, stage 1 solves internal variable. // Use relaxation_factor = 0.5 on the displacement stage to exercise the relaxation path. auto staggered_solver = std::make_shared(20); staggered_solver->addSubsystemSolver({0}, disp_solver, 0.5); - staggered_solver->addSubsystemSolver({1}, state_block_solver, 1.0); + staggered_solver->addSubsystemSolver({1}, internal_variable_block_solver, 1.0); auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); + auto [solid_fields, internal_variable_fields] = registerFields(field_store); + auto [solid, internal_variables] = + buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); - auto solid_fields = - registerSolidMechanicsFields(field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); - auto state_fields = - registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - - auto solid = buildSolidMechanicsSystem( - field_store, solid_system_solver, SolidMechanicsOptions{}, state_fields); - auto state = buildStateVariableSystem( - field_store, state_system_solver, StateVariableOptions{}, solid_fields); - - auto combined = combineSystems(staggered_solver, solid, state); + auto combined = combineSystems(staggered_solver, solid, internal_variables); auto system = combined.system; - setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); - state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { - return StrainNormEvolution{}(t_info, isv, isv_dot, get(u)); - }); - - solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); - double pull_rate = 0.05; - solid->disp_bc->setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { - tensor u{}; - u[2] = pull_rate * t; - return u; - }); + setDamageCoupling(solid, internal_variables, mesh); + setPullBoundaryConditions(solid, mesh, 0.05); auto physics = makeDifferentiablePhysics(system, "physics_relaxed"); for (int step = 1; step <= 3; ++step) { @@ -208,31 +210,19 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) { - auto solid_solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - auto state_solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solid_solver = makeSystemSolver(*mesh); + auto internal_variable_solver = makeSystemSolver(*mesh); auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "body_force_test_"); + auto [solid_fields, internal_variable_fields] = registerFields(field_store); + auto [solid, internal_variables] = + buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); - auto solid_fields = - registerSolidMechanicsFields(field_store, QuasiStaticSecondOrderTimeIntegrationRule{}); - auto state_fields = - registerStateVariableFields(field_store, BackwardEulerFirstOrderTimeIntegrationRule{}); - - auto solid = buildSolidMechanicsSystem( - field_store, solid_solver, SolidMechanicsOptions{}, state_fields); - auto state = buildStateVariableSystem( - field_store, state_solver, StateVariableOptions{}, solid_fields); - - auto combined = combineSystems(coupled_solver, solid, state); + auto combined = combineSystems(coupled_solver, solid, internal_variables); auto system = combined.system; - setCoupledSolidMechanicsInternalVarsMaterial(solid, state, DamageMaterial{}, mesh->entireBodyName()); - state->addStateEvolution(mesh->entireBodyName(), [](auto t_info, auto isv, auto isv_dot, auto u, auto... /*unused*/) { - return StrainNormEvolution{}(t_info, isv, isv_dot, get(u)); - }); + setDamageCoupling(solid, internal_variables, mesh); // Fix bottom face solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 7981d6fe4e..7edc2ca2ee 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -54,9 +54,9 @@ struct ThermalStaticFixture : public testing::Test { auto field_store = std::make_shared(mesh, 100, ""); using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; - registerThermalFields<2, temp_order, TempRule>(field_store); + auto thermal_fields = registerThermalFields<2, temp_order, TempRule>(field_store); - auto thermal_system = buildThermalSystem<2, temp_order, TempRule>(field_store, coupled_solver, ThermalOptions{}); + auto thermal_system = buildThermalSystem<2, temp_order, TempRule>(coupled_solver, ThermalOptions{}, thermal_fields); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -153,7 +153,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto thermal_fields = registerThermalFields<2, 1, TempRule>(field_store); auto thermal_system = - buildThermalSystem<2, 1, TempRule>(coupled_solver, ThermalOptions{}, param_fields, thermal_fields); + buildThermalSystem<2, 1, TempRule>(coupled_solver, ThermalOptions{}, thermal_fields, param_fields); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index b80ebb5e00..9523cc1904 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -6,14 +6,14 @@ /** * @file test_thermo_mechanics_three_system.cpp - * @brief Tests 3-system coupling: SolidMechanicsSystem + ThermalSystem + StateVariableSystem. + * @brief Tests 3-system coupling: SolidMechanicsSystem + ThermalSystem + InternalVariableSystem. * * Validates N>2 system coupling end-to-end via combineSystems. * * Layout: * - Solid: receives temperature coupling (1-way: temperature affects elastic modulus via expansion). * - Thermal: standalone heat diffusion with a heat source. - * - State (damage): receives solid displacement coupling; damage evolves with strain norm. + * - Internal variable (damage): receives solid displacement coupling; damage evolves with strain norm. */ #include @@ -28,7 +28,9 @@ #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" #include "smith/differentiable_numerics/state_variable_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" @@ -42,6 +44,44 @@ static constexpr int temp_ord = 1; static constexpr int state_ord = 0; using StateSpace = L2; +struct SimpleThermoelasticMaterial { + using State = smith::QOI; + + double density = 1.0; + double E = 100.0; + double nu = 0.25; + double alpha_th = 0.001; + double kappa = 0.05; + double heat_capacity = 1.0; + double heat_source = 0.5; + + template + SMITH_HOST_DEVICE auto operator()(DT /*dt*/, StateType /*state*/, GradUType grad_u, GradVType /*grad_v*/, + ThetaType theta, GradThetaType grad_theta, Params... /*params*/) const + { + double lam = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)); + double mu = E / (2.0 * (1.0 + nu)); + static constexpr auto I = Identity(); + auto eps = sym(grad_u); + auto pk = lam * (tr(eps) - 3.0 * alpha_th * theta) * I + 2.0 * mu * eps; + auto q0 = kappa * grad_theta; + return smith::tuple{pk, heat_capacity, heat_source, q0}; + } +}; + +struct StrainDrivenInternalVariableMaterial { + template + SMITH_HOST_DEVICE auto operator()(TimeInfo /*t_info*/, AlphaType alpha, AlphaDotType alpha_dot, DerivType grad_u, + Params... /*params*/) const + { + using std::sqrt; + auto eps = sym(grad_u); + auto eps_norm = sqrt(inner(eps, eps) + 1e-16); + return alpha_dot - (1.0 - alpha) * eps_norm; + } +}; + struct ThreeSystemMeshFixture : public testing::Test { void SetUp() override { @@ -57,7 +97,7 @@ struct ThreeSystemMeshFixture : public testing::Test { std::shared_ptr mesh_; }; -// 3-system staggered: solid (with thermal coupling) + thermal + state (with displacement coupling). +// 3-system staggered: solid + thermal + internal variable. TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) { smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, @@ -74,89 +114,38 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) auto solid_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto state_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); + auto internal_variable_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); auto field_store = std::make_shared(mesh_, 100, "three"); using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; - using StateRule = BackwardEulerFirstOrderTimeIntegrationRule; - - DispRule disp_rule; - TempRule temp_rule; - StateRule state_rule; + using InternalVariableRule = BackwardEulerFirstOrderTimeIntegrationRule; // Phase 1: register all fields. // registerSolidMechanicsFields must come before registerThermalFields to get // the displacement field tokens available for thermal coupling. - auto solid_exported = registerSolidMechanicsFields(field_store); - auto thermal_exported = registerThermalFields(field_store, temp_rule); - registerStateVariableFields(field_store, state_rule); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto thermal_fields = registerThermalFields(field_store); + auto internal_variable_fields = registerInternalVariableFields(field_store); // Phase 2: build each system. // Solid receives thermal coupling (temperature_solve_state, temperature). auto solid = buildSolidMechanicsSystem( - field_store, std::make_shared(solid_block_solver), SolidMechanicsOptions{}, thermal_exported); + std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, thermal_fields); - // Thermal is standalone (no coupling back from solid for this test). auto thermal = buildThermalSystem( - field_store, std::make_shared(thermal_block_solver), ThermalOptions{}); + std::make_shared(thermal_block_solver), ThermalOptions{}, thermal_fields, solid_fields); - // StateVariable receives solid displacement coupling (4 fields: disp_ss, displacement, velocity, acceleration). - auto state_sys = buildStateVariableSystem( - field_store, std::make_shared(state_block_solver), StateVariableOptions{}, solid_exported); + auto internal_variables = buildInternalVariableSystem( + std::make_shared(internal_variable_block_solver), InternalVariableOptions{}, internal_variable_fields, + solid_fields); // Phase 3: register material integrands. - - // Solid: thermoelastic — temperature drives isotropic expansion. - // Lambda args: (t_info, X, u, u_old, v_old, a_old, temperature_ss, temperature_old) - { - auto captured_disp_rule = solid->disp_time_rule; - auto captured_temp_rule = thermal->temperature_time_rule; - solid->solid_weak_form->addBodyIntegral( - mesh_->entireBodyName(), [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, - auto temperature_ss, auto temperature_old) { - auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto T = captured_temp_rule->value(t_info, temperature_ss, temperature_old); - - double E = 100.0, nu = 0.25, alpha_th = 0.001, density = 1.0; - double lam = E * nu / ((1 + nu) * (1 - 2 * nu)); - double mu = E / (2 * (1 + nu)); - static constexpr auto I = Identity(); - auto eps = sym(get(u_c)); - auto T_val = get(T); - auto sigma = lam * (tr(eps) - 3.0 * alpha_th * T_val) * I + 2.0 * mu * eps; - return smith::tuple{get(a_c) * density, sigma}; - }); - } - - // Thermal: simple conduction with constant heat source. - // Lambda args: (t_info, X, T, T_old) - { - auto captured_temp_rule = thermal->temperature_time_rule; - const double kappa = 0.05, C_v = 1.0, heat_source = 0.5; - thermal->thermal_weak_form->addBodyIntegral(mesh_->entireBodyName(), - [=](auto t_info, auto /*X*/, auto T, auto T_old) { - auto [T_c, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); - auto q = kappa * get(T_c); - return smith::tuple{C_v * get(T_dot) - heat_source, -q}; - }); - } - - // State (damage): evolves as d_dot = (1 - d) * eps_norm. - // addStateEvolution lambda args: (t_info, alpha_val, alpha_dot, disp_ss, displacement, velocity, acceleration) - { - auto captured_disp_rule = solid->disp_time_rule; - state_sys->addStateEvolution(mesh_->entireBodyName(), - [=](auto t_info, auto alpha_val, auto alpha_dot, auto u_ss, auto u, auto v, auto a) { - auto [u_c, v_c, a_c] = captured_disp_rule->interpolate(t_info, u_ss, u, v, a); - auto eps = sym(get(u_c)); - using std::sqrt; - auto eps_norm = sqrt(inner(eps, eps) + 1e-16); - return alpha_dot - (1.0 - alpha_val) * eps_norm; - }); - } + setCoupledThermoMechanicsMaterial(solid, thermal, SimpleThermoelasticMaterial{}, mesh_->entireBodyName()); + setCoupledInternalVariableMaterial(internal_variables, solid, StrainDrivenInternalVariableMaterial{}, + mesh_->entireBodyName()); // Phase 4: boundary conditions. solid->setDisplacementBC(mesh_->domain("left")); @@ -173,7 +162,7 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) }); // Phase 5: combine and solve. - auto combined_res = combineSystems(solid, thermal, state_sys); + auto combined_res = combineSystems(solid, thermal, internal_variables); auto combined = combined_res.system; auto combined_cycle_zero = combined_res.cycle_zero_system; @@ -194,8 +183,8 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) << "Solid solver did not converge"; EXPECT_TRUE(thermal_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged()) << "Thermal solver did not converge"; - EXPECT_TRUE(state_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged()) - << "State solver did not converge"; + EXPECT_TRUE(internal_variable_block_solver->nonlinear_solver_->nonlinearSolver().GetConverged()) + << "Internal-variable solver did not converge"; // Displacement should be non-zero (compressive traction). mfem::Vector final_disp(*states[field_store->getFieldIndex("three_displacement_solve_state")].get()); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 12479aa38d..2f4e6cca20 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -170,13 +170,13 @@ struct ThermalOptions {}; /** * @brief Register all thermal fields into a FieldStore. * - * Phase 1 of the two-phase initialization. Pass an instance of the desired time integration rule - * so its type is deduced; only `` need be specified explicitly. + * Phase 1 of the two-phase initialization. * * @return PhysicsFields carrying the exported field tokens and time rule type. */ template -auto registerThermalFields(std::shared_ptr field_store, TemperatureTimeRule /*rule*/) +auto registerThermalFields(std::shared_ptr field_store, + const ThermalOptions& /*options*/ = ThermalOptions{}) { FieldType> shape_disp_type("shape_displacement"); if (!field_store->hasField(field_store->prefix(shape_disp_type.name))) { @@ -193,21 +193,10 @@ auto registerThermalFields(std::shared_ptr field_store, TemperatureT FieldType>(field_store->prefix("temperature"))}; } -/** - * @brief Register thermal fields (no rule instance - Rule given as explicit template param). - */ -template -auto registerThermalFields(std::shared_ptr field_store) -{ - return registerThermalFields(field_store, TemperatureTimeRule{}); -} - /** * @brief Internal thermal builder after coupling fields are assembled. * - * Phase 2 of the two-phase initialization. Pass the same rule instance used in - * registerThermalFields so the type is deduced; only `` need be specified. - * + * Phase 2 of the two-phase initialization. */ namespace detail { @@ -228,11 +217,11 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl std::string thermal_flux_name = field_store->prefix("thermal_flux"); auto thermal_weak_form = std::apply( - [&](auto&... cfs) { + [&](auto&... coupling_fields) { return std::make_shared( thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, - cfs...)); + coupling_fields...)); }, coupling.fields); @@ -257,16 +246,19 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl * Usage: * @code * auto thermal = buildThermalSystem( - * field_store, solver, opts, solid_fields); + * solver, opts, thermal_fields, solid_fields); * @endcode */ -template - requires(sizeof...(FieldPacks) > 0 && (detail::is_coupling_params_v || ...)) -auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr solver, - const ThermalOptions& options, const FieldPacks&... field_packs) +template + requires(detail::is_physics_fields_v && + std::is_same_v::time_rule_type, TemperatureTimeRule> && + (detail::is_coupling_params_v && ...)) +auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) { - (detail::registerParamsIfNeeded(field_store, field_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, field_packs...); + auto field_store = self_fields.field_store; + (detail::registerParamsIfNeeded(field_store, other_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } @@ -280,38 +272,7 @@ auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr * Usage: * @code * auto thermal = buildThermalSystem( - * solver, opts, param_fields, thermal_fields, solid_fields); + * solver, opts, thermal_fields, param_fields, solid_fields); * @endcode */ -/** - * @brief Build a standalone ThermalSystem with no coupling and no parameters. - * - * Convenience overload for single-physics tests and demos - avoids the `CouplingParams<>{}` - * placeholder at the call site. - */ -template -auto buildThermalSystem(std::shared_ptr field_store, std::shared_ptr solver, - const ThermalOptions& options) -{ - return detail::buildThermalSystemImpl(field_store, CouplingParams<>{}, solver, - options); -} - -/** - * @brief Build a thermal system from registered field packs. - * - * One `PhysicsFields` pack must come from the thermal registration. Other `PhysicsFields` packs are - * treated as coupling inputs, while non-physics `CouplingParams` packs are registered as parameter - * fields. - */ -template - requires(sizeof...(FieldPacks) > 0 && (detail::is_physics_fields_v || ...) && - !(std::is_same_v, ThermalOptions> || ...)) -auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, - const FieldPacks&... field_packs) -{ - auto field_store = detail::findFieldStore(field_packs...); - return buildThermalSystem(field_store, solver, options, field_packs...); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp index 2316067c8f..70280681e6 100644 --- a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp @@ -106,7 +106,7 @@ void setCoupledThermoMechanicsMaterial( auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( material, t_info.dt(), state, get(u), get(v), get(T), get(T), params...); - return smith::tuple{get(a) * material.density, pk}; + return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk); }); } diff --git a/src/smith/differentiable_numerics/time_integration_rule.hpp b/src/smith/differentiable_numerics/time_integration_rule.hpp index 34eab564ad..33b4e00c81 100644 --- a/src/smith/differentiable_numerics/time_integration_rule.hpp +++ b/src/smith/differentiable_numerics/time_integration_rule.hpp @@ -153,6 +153,53 @@ class QuasiStaticRule : public TimeIntegrationRule { } }; +/// @brief encodes rules for static postprocessing fields with zero time derivatives. +class StaticTimeIntegrationRule : public TimeIntegrationRule { + public: + static constexpr int num_states = 1; ///< number of states required by this rule (compile-time) + + int num_args() const override { return num_states; } + + template + SMITH_HOST_DEVICE auto value(const TimeInfo& /*t*/, const T1& field_new) const + { + return field_new; + } + + template + SMITH_HOST_DEVICE auto dot(const TimeInfo& /*t*/, const T1& /*field_new*/) const + { + return zero{}; + } + + template + SMITH_HOST_DEVICE auto ddot(const TimeInfo& /*t*/, const T1& /*field_new*/) const + { + return zero{}; + } + + template + SMITH_HOST_DEVICE auto interpolate(const TimeInfo& t, const T1& field_new) const + { + return std::make_tuple(value(t, field_new), dot(t, field_new), ddot(t, field_new)); + } + + FieldState corrected_value(const TimeInfo& t, const std::vector& states) const override + { + return value(t, states[0]); + } + + FieldState corrected_dot(const TimeInfo& /*t*/, const std::vector& states) const override + { + return zeroCopy(states[0]); + } + + FieldState corrected_ddot(const TimeInfo& /*t*/, const std::vector& states) const override + { + return zeroCopy(states[0]); + } +}; + /// @brief Alias for BackwardEulerFirstOrderTimeIntegrationRule for convenience. Quasi-static still should compute /// velocities (viscosities) using backward Euler. using QuasiStaticFirstOrderTimeIntegrationRule = BackwardEulerFirstOrderTimeIntegrationRule; From 73ebfb4f852603d81fd74e666911f3ffa45d0d7b Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 22 Apr 2026 15:03:10 -0700 Subject: [PATCH 36/67] Finsih last parts of this increment of design change. --- .../combined_system.hpp | 24 +---------- .../tests/test_combined_thermo_mechanics.cpp | 41 +++++++++++-------- .../tests/test_solid_dynamics.cpp | 30 ++++++++++++++ .../test_solid_static_with_internal_vars.cpp | 9 ++-- .../test_thermo_mechanics_three_system.cpp | 6 +-- 5 files changed, 62 insertions(+), 48 deletions(-) diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index d8b734340e..70e8aa7d20 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -86,10 +86,6 @@ struct CombinedSystem : public SystemBase { * - All sub-systems share the same FieldStore (built via registerXxxFields + buildXxxFromStore). * - Sub-system weak_forms are already populated (registerXxx was called before buildXxx). * - * The returned pair contains: - * - first: The combined main system (staggered). - * - second: The combined cycle-zero system (staggered, max_stagger_iters=1), or nullptr if none. - * * @param subs Two or more sub-systems that share a FieldStore. */ template @@ -163,13 +159,7 @@ auto combineSystems(std::shared_ptr... subs) combined->cycle_zero_system = cycle_zero_combined; combined->post_solve_systems = post_solve_systems; - struct CombinedSystemResult { - std::shared_ptr system; - std::shared_ptr cycle_zero_system; - std::vector> end_step_systems; - }; - - return CombinedSystemResult{combined, cycle_zero_combined, post_solve_systems}; + return combined; } /** @@ -190,10 +180,6 @@ struct MonolithicCombinedSystem : public SystemBase { * - All sub-systems share the same FieldStore. * - Sub-system weak_forms are already populated. * - * The returned pair contains: - * - first: The combined main system (monolithic). - * - second: The combined cycle-zero system (monolithic), or nullptr if none. - * * @param solver The monolithic SystemSolver that will solve the combined block system, * including the aggregated cycle-zero system if any sub-systems have one. * @param subs Two or more sub-systems that share a FieldStore. @@ -237,13 +223,7 @@ auto combineSystems(std::shared_ptr solver, std::shared_ptrcycle_zero_system = cycle_zero_combined; combined->post_solve_systems = post_solve_systems; - struct MonolithicCombinedSystemResult { - std::shared_ptr system; - std::shared_ptr cycle_zero_system; - std::vector> end_step_systems; - }; - - return MonolithicCombinedSystemResult{combined, cycle_zero_combined, post_solve_systems}; + return combined; } /** diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index fda269f9d0..6bd21905f2 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -56,14 +56,14 @@ struct ThermoMechanicsMeshFixture : public testing::Test { // Advance one step, return final states + lateral deflection. template double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, - std::shared_ptr coupled_cycle_zero, double dt = 1.0) + double dt = 1.0) { auto shape_disp = field_store_->getShapeDisp(); auto params = field_store_->getParameterFields(); std::vector reactions; std::tie(std::ignore, reactions) = - makeAdvancer(coupled, coupled_cycle_zero) - ->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, field_store_->getStateFields(), params); + makeAdvancer(coupled)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, field_store_->getStateFields(), + params); mfem::Vector final_disp( *field_store_->getStateFields()[field_store_->getFieldIndex("displacement_solve_state")].get()); @@ -120,8 +120,7 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto thermal = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); - auto combined = combineSystems(solid, thermal); - auto coupled = combined.system; + auto coupled = combineSystems(solid, thermal); auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); const auto& solid_dual_space = physics->dual("reactions").space(); const auto& solid_state_space = physics->state("displacement_solve_state").space(); @@ -156,8 +155,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto thermal = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); - auto combined = combineSystems(solid, thermal); - auto coupled = combined.system; + auto coupled = combineSystems(solid, thermal); thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); @@ -225,14 +223,13 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto thermal = buildThermalSystem( makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, thermal_fields, solid_fields); - auto combined = combineSystems(solid, thermal); - auto coupled = combined.system; + auto coupled = combineSystems(solid, thermal); thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - double deflection = advanceOneStepAndGetLateralDeflection(coupled, combined.cycle_zero_system); + double deflection = advanceOneStepAndGetLateralDeflection(coupled); EXPECT_GT(deflection, 1e-5); } @@ -256,14 +253,13 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); - auto combined = combineSystems(solver_ptr, solid, thermal); - auto coupled = combined.system; + auto coupled = combineSystems(solver_ptr, solid, thermal); thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - double deflection = advanceOneStepAndGetLateralDeflection(coupled, combined.cycle_zero_system); + double deflection = advanceOneStepAndGetLateralDeflection(coupled); EXPECT_GT(deflection, 1e-5); } @@ -274,8 +270,11 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) SolidMechanicsOptions solid_opts{.enable_stress_output = true, .output_cauchy_stress = true}; auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); + EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); + EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress"))); auto sys = buildSolidMechanicsSystem( makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields); + ASSERT_EQ(sys->post_solve_systems.size(), 1u); constexpr double E = 100.0; constexpr double nu = 0.25; @@ -301,6 +300,17 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) EXPECT_GT(stress_norm, 1e-8) << "Cauchy stress field should be non-zero after deformation"; } +TEST_F(ThermoMechanicsMeshFixture, StressOutputRegistrationDisabledByDefault) +{ + auto solid_fields = registerSolidMechanicsFields(field_store_); + EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); + EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress"))); + + auto sys = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields); + EXPECT_TRUE(sys->post_solve_systems.empty()); +} + TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) { SolidMechanicsOptions solid_opts{.enable_stress_output = true}; @@ -315,9 +325,8 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto combined = combineSystems(solid, thermal); - ASSERT_EQ(combined.end_step_systems.size(), solid->post_solve_systems.size()); - ASSERT_EQ(combined.system->post_solve_systems.size(), solid->post_solve_systems.size()); - EXPECT_FALSE(combined.end_step_systems.empty()); + ASSERT_EQ(combined->post_solve_systems.size(), solid->post_solve_systems.size()); + EXPECT_FALSE(combined->post_solve_systems.empty()); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 2edd462988..517fea4755 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -119,6 +119,36 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) } } +TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverUsesOwnedSingleStepPolicy) +{ + auto main_stage_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto solver = std::make_shared(7, false); + solver->addSubsystemSolver({0}, main_stage_solver, 0.8); + + auto field_store = std::make_shared(mesh, 100, "cycle_zero_policy"); + using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + auto solid_fields = registerSolidMechanicsFields(field_store); + auto system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + + ASSERT_NE(system->cycle_zero_system, nullptr); + ASSERT_NE(system->cycle_zero_system->solver, nullptr); + EXPECT_EQ(system->cycle_zero_system->solver->maxStaggeredIterations(), 1); + EXPECT_TRUE(system->cycle_zero_system->solver->exactStaggeredSteps()); +} + +TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver) +{ + auto field_store = std::make_shared(mesh, 100, "cycle_zero_fallback"); + using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + auto solid_fields = registerSolidMechanicsFields(field_store); + auto system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); + + ASSERT_NE(system->cycle_zero_system, nullptr); + ASSERT_NE(system->cycle_zero_system->solver, nullptr); + EXPECT_EQ(system->cycle_zero_system->solver->maxStaggeredIterations(), 1); + EXPECT_FALSE(system->cycle_zero_system->solver->exactStaggeredSteps()); +} + TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 90dd1d5678..299b337b7b 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -158,8 +158,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto [solid, internal_variables] = buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); - auto combined = combineSystems(coupled_solver, solid, internal_variables); - auto system = combined.system; + auto system = combineSystems(coupled_solver, solid, internal_variables); setDamageCoupling(solid, internal_variables, mesh); setPullBoundaryConditions(solid, mesh, 0.05); @@ -195,8 +194,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto [solid, internal_variables] = buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); - auto combined = combineSystems(staggered_solver, solid, internal_variables); - auto system = combined.system; + auto system = combineSystems(staggered_solver, solid, internal_variables); setDamageCoupling(solid, internal_variables, mesh); setPullBoundaryConditions(solid, mesh, 0.05); @@ -219,8 +217,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto [solid, internal_variables] = buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); - auto combined = combineSystems(coupled_solver, solid, internal_variables); - auto system = combined.system; + auto system = combineSystems(coupled_solver, solid, internal_variables); setDamageCoupling(solid, internal_variables, mesh); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index 9523cc1904..bd33cbbee0 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -162,9 +162,7 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) }); // Phase 5: combine and solve. - auto combined_res = combineSystems(solid, thermal, internal_variables); - auto combined = combined_res.system; - auto combined_cycle_zero = combined_res.cycle_zero_system; + auto combined = combineSystems(solid, thermal, internal_variables); double dt = 1.0, time = 0.0; auto shape_disp = field_store->getShapeDisp(); @@ -173,7 +171,7 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) std::vector reactions; for (size_t step = 0; step < 2; ++step) { - std::tie(states, reactions) = makeAdvancer(combined, combined_cycle_zero) + std::tie(states, reactions) = makeAdvancer(combined) ->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } From 976661076ec134c99b72546a4a3b3933ad7bdf67 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 22 Apr 2026 20:12:16 -0700 Subject: [PATCH 37/67] Fix style. --- examples/CMakeLists.txt | 3 + examples/thermo_mechanics/CMakeLists.txt | 24 ++++ .../composable_thermo_mechanics.cpp | 106 ++++++++++++++++++ .../composable_thermo_mechanics_tutorial.rst | 81 +++++++++++++ src/docs/sphinx/user_guide/index.rst | 1 + .../solid_mechanics_system.hpp | 19 +++- ...id_mechanics_with_internal_vars_system.hpp | 13 ++- .../state_variable_system.hpp | 27 +++-- .../tests/test_combined_thermo_mechanics.cpp | 37 +++--- .../tests/test_solid_dynamics.cpp | 60 ++++++++++ .../test_solid_static_with_internal_vars.cpp | 10 +- .../test_thermo_mechanics_three_system.cpp | 12 +- .../time_integration_rule.hpp | 14 +++ 13 files changed, 359 insertions(+), 48 deletions(-) create mode 100644 examples/thermo_mechanics/CMakeLists.txt create mode 100644 examples/thermo_mechanics/composable_thermo_mechanics.cpp create mode 100644 src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index f78aa70141..09bd191b34 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,9 @@ install( # Add the conduction examples add_subdirectory(conduction) +# Add the thermo-mechanics examples +add_subdirectory(thermo_mechanics) + # Add the contact examples add_subdirectory(contact) diff --git a/examples/thermo_mechanics/CMakeLists.txt b/examples/thermo_mechanics/CMakeLists.txt new file mode 100644 index 0000000000..07cf24f7cb --- /dev/null +++ b/examples/thermo_mechanics/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) Lawrence Livermore National Security, LLC and +# other Smith Project Developers. See the top-level LICENSE file for +# details. +# +# SPDX-License-Identifier: (BSD-3-Clause) + +smith_add_executable( NAME composable_thermo_mechanics + SOURCES composable_thermo_mechanics.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON smith + ) + +install( + FILES + composable_thermo_mechanics.cpp + DESTINATION + examples/smith/thermo_mechanics +) + +if(SMITH_ENABLE_TESTS) + blt_add_test(NAME composable_thermo_mechanics + COMMAND composable_thermo_mechanics + NUM_MPI_TASKS 1 ) +endif() diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp new file mode 100644 index 0000000000..a7b0faae9f --- /dev/null +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -0,0 +1,106 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file composable_thermo_mechanics.cpp + * @brief Minimal composable thermo-mechanics example using differentiable numerics. + */ + +#include + +// _includes_start +#include "smith/infrastructure/application_manager.hpp" +#include "smith/physics/state/state_manager.hpp" +#include "smith/physics/mesh.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" +#include "smith/differentiable_numerics/differentiable_physics.hpp" +#include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" +// _includes_end + +int main(int argc, char* argv[]) +{ + // _init_start + smith::ApplicationManager application_manager(argc, argv); + axom::sidre::DataStore datastore; + smith::StateManager::initialize(datastore, "composable_thermo_mechanics"); + // _init_end + + // _mesh_start + constexpr int dim = 3; + auto mesh = std::make_shared( + mfem::Mesh::MakeCartesian3D(8, 2, 2, mfem::Element::HEXAHEDRON, 1.0, 0.1, 0.1), "mesh", 0, 0); + mesh->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh->addDomainOfBoundaryElements("right", smith::by_attr(5)); + // _mesh_end + + // _solver_start + smith::LinearSolverOptions linear_options{.linear_solver = smith::LinearSolver::SuperLU, + .relative_tol = 1e-8, + .absolute_tol = 1e-10, + .max_iterations = 200, + .print_level = 0}; + smith::NonlinearSolverOptions nonlinear_options{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-7, + .absolute_tol = 1e-8, + .max_iterations = 20, + .max_line_search_iterations = 6, + .print_level = 0}; + + auto field_store = std::make_shared(mesh, 100, "tutorial_"); + + using DispRule = smith::QuasiStaticSecondOrderTimeIntegrationRule; + using TempRule = smith::BackwardEulerFirstOrderTimeIntegrationRule; + + auto solid_fields = smith::registerSolidMechanicsFields(field_store); + auto thermal_fields = smith::registerThermalFields(field_store); + // _solver_end + + // _build_start + auto solid_solver = + std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); + auto thermal_solver = + std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); + + auto solid = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, + solid_fields, thermal_fields); + auto thermal = smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, + solid_fields); + + smith::thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + smith::setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh->entireBodyName()); + + auto coupled = smith::combineSystems(solid, thermal); + // _build_end + + // _bc_start + solid->setDisplacementBC(mesh->domain("left")); + thermal->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); + thermal->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); + + solid->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto... /*unused*/) { + auto traction = 0.0 * X; + traction[0] = -0.01; + return traction; + }); + + thermal->addHeatSource(mesh->entireBodyName(), [](auto, auto, auto, auto... /*unused*/) { return 0.5; }); + // _bc_end + + // _run_start + auto physics = smith::makeDifferentiablePhysics(coupled, "composable_thermo_mechanics"); + for (int step = 0; step < 2; ++step) { + physics->advanceTimestep(1.0); + } + // _run_end + + return 0; +} diff --git a/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst b/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst new file mode 100644 index 0000000000..dbc741b926 --- /dev/null +++ b/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst @@ -0,0 +1,81 @@ +.. ## Copyright (c) Lawrence Livermore National Security, LLC and +.. ## other Smith Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +#################################### +Composable Thermo-Mechanics Tutorial +#################################### + +This tutorial shows a minimal thermo-mechanical setup built from the composable +differentiable numerics systems. It uses separate solid and thermal systems, +couples them with a thermoelastic material, and advances the combined system. + +The full source code lives in ``examples/thermo_mechanics/composable_thermo_mechanics.cpp``. + +Includes and Initialization +--------------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _includes_start + :end-before: _includes_end + :language: C++ + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _init_start + :end-before: _init_end + :language: C++ + +Mesh Construction +----------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _mesh_start + :end-before: _mesh_end + :language: C++ + +Field Registration +------------------ + +Registration is phase 1. It declares the solid and thermal fields up front in a +shared ``FieldStore``. + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _solver_start + :end-before: _solver_end + :language: C++ + +System Build and Coupling +------------------------- + +Build is phase 2. Each system consumes its own registered field pack first, then +the other system's field pack for coupling. ``combineSystems(...)`` returns the +final combined system directly, and ``makeDifferentiablePhysics(...)`` later uses +the system-owned cycle-zero and post-solve systems automatically. + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _build_start + :end-before: _build_end + :language: C++ + +Boundary Conditions and Loads +----------------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _bc_start + :end-before: _bc_end + :language: C++ + +Advance the Coupled System +-------------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp + :start-after: _run_start + :end-before: _run_end + :language: C++ + +This example intentionally stays small. Use it as a template for: + +- adding parameter fields, +- enabling stress output on the solid system, +- or extending the model to thermo-mechanics plus internal variables. diff --git a/src/docs/sphinx/user_guide/index.rst b/src/docs/sphinx/user_guide/index.rst index 5acf7927f3..32cfb8454d 100644 --- a/src/docs/sphinx/user_guide/index.rst +++ b/src/docs/sphinx/user_guide/index.rst @@ -12,6 +12,7 @@ User Guide :maxdepth: 2 simple_conduction_tutorial + composable_thermo_mechanics_tutorial command_line_options input_schema diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 54410fce76..c68f102e63 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -28,12 +28,14 @@ namespace smith { namespace detail { template +/// @brief Scale a cycle-zero residual contribution by `dt*dt`. auto scaleCycleZeroTerm(const TimeInfo& t_info, ValueType value) { return (t_info.dt() * t_info.dt()) * value; } template +/// @brief Package scaled inertia and stress terms for the cycle-zero weak form. auto makeScaledCycleZeroResidual(const TimeInfo& t_info, InertiaType inertia, StressType stress) { return smith::tuple{scaleCycleZeroTerm(t_info, inertia), scaleCycleZeroTerm(t_info, stress)}; @@ -381,10 +383,10 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, auto physics_fields = PhysicsFields, H1, H1, H1>{ - field_store, FieldType>(field_store->prefix("displacement_solve_state")), - FieldType>(field_store->prefix("displacement")), - FieldType>(field_store->prefix("velocity")), - FieldType>(field_store->prefix("acceleration"))}; + field_store, FieldType>(field_store->prefix("displacement_solve_state")), + FieldType>(field_store->prefix("displacement")), + FieldType>(field_store->prefix("velocity")), + FieldType>(field_store->prefix("acceleration"))}; if (options.enable_stress_output) { auto stress_time_rule = std::make_shared(); @@ -402,11 +404,13 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, */ namespace detail { +/// @brief Return true when stress output fields were registered during phase 1. inline bool hasRegisteredStressOutput(const std::shared_ptr& field_store) { return field_store->hasField(field_store->prefix("stress_solve_state")); } +/// @brief Build a cycle-zero solver from the main solver when possible, else use fallback defaults. inline std::shared_ptr makeCycleZeroSolver(std::shared_ptr solver, const Mesh& mesh) { if (solver) { @@ -429,6 +433,9 @@ inline std::shared_ptr makeCycleZeroSolver(std::shared_ptr(buildNonlinearBlockSolver(cycle_zero_nonlin, cycle_zero_lin, mesh)); } +/** + * @brief Internal solid builder after public registration and coupling collection. + */ template requires detail::is_coupling_params_v auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, const Coupling& coupling, @@ -542,8 +549,8 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid (detail::registerParamsIfNeeded(field_store, other_packs), ...); auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); bool has_stress_output = detail::hasRegisteredStressOutput(field_store); - return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, - options, has_stress_output); + return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, options, + has_stress_output); } /** diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 7989765a44..976dfe3b92 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -20,9 +20,9 @@ namespace detail { template -auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const TimeInfoType& t_info, - AlphaType alpha, AlphaDotType alpha_dot, DerivType deriv_u, - ParamTypes&&... params) +/// @brief Dispatch internal-variable material calls with or without explicit `TimeInfo`. +auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const TimeInfoType& t_info, AlphaType alpha, + AlphaDotType alpha_dot, DerivType deriv_u, ParamTypes&&... params) { if constexpr (requires { material(t_info, alpha, alpha_dot, deriv_u, std::forward(params)...); }) { return material(t_info, alpha, alpha_dot, deriv_u, std::forward(params)...); @@ -104,6 +104,13 @@ void setCoupledInternalVariableMaterial( template +/** + * @brief Backward-compatible alias for `setCoupledSolidMechanicsInternalVariableMaterial`. + * @param solid Solid system receiving internal-variable coupling. + * @param state Backward-compatible internal-variable system alias. + * @param material Material model. + * @param domain_name Domain on which to apply the material. + */ void setCoupledSolidMechanicsInternalVarsMaterial( std::shared_ptr> solid, std::shared_ptr> state, const MaterialType& material, diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 7d6f8c5efe..6b01401bdd 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -60,7 +60,8 @@ template + /// @brief Register an internal-variable material or evolution law on a domain. void setMaterial(const MaterialType& material, const std::string& domain_name) { addEvolution(domain_name, material); } template + /// @brief Backward-compatible alias for `addEvolution`. void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) { addEvolution(domain_name, evolution_law); @@ -114,7 +117,9 @@ struct InternalVariableSystem : public SystemBase { // Options // --------------------------------------------------------------------------- +/// @brief Options for building an `InternalVariableSystem`. struct InternalVariableOptions {}; +/// @brief Backward-compatible alias for `InternalVariableOptions`. using StateVariableOptions = InternalVariableOptions; // --------------------------------------------------------------------------- @@ -152,6 +157,7 @@ auto registerInternalVariableFields(std::shared_ptr field_store, } template +/// @brief Backward-compatible alias for `registerInternalVariableFields`. auto registerStateVariableFields(std::shared_ptr field_store, FieldType... parameter_types) { return registerInternalVariableFields(field_store, parameter_types...); @@ -166,11 +172,13 @@ auto registerStateVariableFields(std::shared_ptr field_store, FieldT */ namespace detail { +/** + * @brief Internal builder for an internal-variable system after public registration and coupling collection. + */ template requires detail::is_coupling_params_v auto buildInternalVariableSystemImpl(std::shared_ptr field_store, const Coupling& coupling, - std::shared_ptr solver, - const InternalVariableOptions& /*options*/) + std::shared_ptr solver, const InternalVariableOptions& /*options*/) { auto internal_variable_time_rule = std::make_shared(); @@ -185,14 +193,15 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co auto internal_variable_weak_form = std::apply( [&](auto&... coupling_fields) { return std::make_shared( - internal_variable_residual_name, field_store->getMesh(), field_store->getField(state_type.name).get()->space(), + internal_variable_residual_name, field_store->getMesh(), + field_store->getField(state_type.name).get()->space(), field_store->createSpaces(internal_variable_residual_name, state_type.name, state_type, state_old_type, coupling_fields...)); }, coupling.fields); - auto sys = - std::make_shared(field_store, solver, std::vector>{internal_variable_weak_form}); + auto sys = std::make_shared(field_store, solver, + std::vector>{internal_variable_weak_form}); sys->internal_variable_bc = internal_variable_bc; sys->internal_variable_time_rule = internal_variable_time_rule; sys->internal_variable_weak_form = internal_variable_weak_form; @@ -219,6 +228,9 @@ auto buildInternalVariableSystem(std::shared_ptr solver, const Int options); } +/** + * @brief Backward-compatible alias for `buildInternalVariableSystem`. + */ template requires(detail::is_physics_fields_v && std::is_same_v::time_rule_type, InternalVarTimeRule> && @@ -227,11 +239,12 @@ auto buildStateVariableSystem(std::shared_ptr solver, const StateV const SelfFields& self_fields, const OtherPacks&... other_packs) { return buildInternalVariableSystem(solver, options, self_fields, - other_packs...); + other_packs...); } template > +/// @brief Backward-compatible alias for `InternalVariableSystem`. using StateVariableSystem = InternalVariableSystem; } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 6bd21905f2..5a09866435 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -55,15 +55,13 @@ struct ThermoMechanicsMeshFixture : public testing::Test { // Advance one step, return final states + lateral deflection. template - double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, - double dt = 1.0) + double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, double dt = 1.0) { auto shape_disp = field_store_->getShapeDisp(); auto params = field_store_->getParameterFields(); std::vector reactions; - std::tie(std::ignore, reactions) = - makeAdvancer(coupled)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, field_store_->getStateFields(), - params); + std::tie(std::ignore, reactions) = makeAdvancer(coupled)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, + field_store_->getStateFields(), params); mfem::Vector final_disp( *field_store_->getStateFields()[field_store_->getFieldIndex("displacement_solve_state")].get()); @@ -220,8 +218,8 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto solid = buildSolidMechanicsSystem( makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, thermal_fields); - auto thermal = buildThermalSystem( - makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, thermal_fields, solid_fields); + auto thermal = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), + ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solid, thermal); thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -247,11 +245,10 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = - buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - thermal_fields); - auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, - solid_fields); + auto solid = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, + solid_fields, thermal_fields); + auto thermal = + buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solver_ptr, solid, thermal); thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -272,8 +269,8 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress"))); - auto sys = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields); + auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + solid_opts, solid_fields); ASSERT_EQ(sys->post_solve_systems.size(), 1u); constexpr double E = 100.0; @@ -306,8 +303,8 @@ TEST_F(ThermoMechanicsMeshFixture, StressOutputRegistrationDisabledByDefault) EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress"))); - auto sys = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields); + auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields); EXPECT_TRUE(sys->post_solve_systems.empty()); } @@ -318,10 +315,10 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields, thermal_fields); - auto thermal = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, solid_fields); + auto solid = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + solid_opts, solid_fields, thermal_fields); + auto thermal = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), + ThermalOptions{}, thermal_fields, solid_fields); auto combined = combineSystems(solid, thermal); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 517fea4755..1242ad0ea3 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -258,6 +258,66 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) EXPECT_NEAR(0.0, u_err, 1e-14); } +TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditions) +{ + auto solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto field_store = std::make_shared(mesh, 100, "freefall_"); + + using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + SolidMechanicsOptions solid_options{.enable_stress_output = true}; + auto solid_fields = registerSolidMechanicsFields(field_store, solid_options); + auto system = buildSolidMechanicsSystem(solver, solid_options, solid_fields); + + static constexpr double gravity = -9.0; + double E = 100.0; + double nu = 0.25; + auto K = E / (3.0 * (1.0 - 2.0 * nu)); + auto G = E / (2.0 * (1.0 + nu)); + system->setMaterial(solid_mechanics::NeoHookean{.density = 1.0, .K = K, .G = G}, mesh->entireBodyName()); + + system->addBodyForce(mesh->entireBodyName(), [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/) { + tensor b{}; + b[1] = gravity; + return b; + }); + + system->disp_bc->setFixedVectorBCs(mesh->entireBoundary(), std::vector{0, 2}); + + auto physics = makeDifferentiablePhysics(system, "freefall"); + for (size_t step = 0; step < num_steps_; ++step) { + physics->advanceTimestep(dt_); + } + + auto states = physics->getFieldStates(); + auto shape_disp = physics->getShapeDispFieldState(); + double time = num_steps_ * dt_; + double a_exact = gravity; + double v_exact = gravity * time; + double u_exact = 0.5 * gravity * time * time; + + auto vector_error = [&](const std::string& name, size_t state_index, double y_exact) { + FunctionalObjective> error(name, mesh, spaces({states[state_index]})); + error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [y_exact](auto /*t*/, auto /*X*/, auto U) { + auto u = get(U); + return u[0] * u[0] + (u[1] - y_exact) * (u[1] - y_exact) + u[2] * u[2]; + }); + return error.evaluate(TimeInfo(0.0, dt_, num_steps_), shape_disp.get().get(), + getConstFieldPointers({states[state_index]})); + }; + + EXPECT_NEAR(0.0, vector_error("freefall_acceleration_error", 3, a_exact), 1e-12); + EXPECT_NEAR(0.0, vector_error("freefall_velocity_error", 2, v_exact), 1e-12); + EXPECT_NEAR(0.0, vector_error("freefall_displacement_error", 0, u_exact), 1e-12); + + auto stress_it = std::find_if(states.begin(), states.end(), [](const auto& state) { + return state.get()->name().find("stress_solve_state") != std::string::npos; + }); + ASSERT_NE(stress_it, states.end()); + double stress_norm = norm(*stress_it->get().get()); + EXPECT_LT(stress_norm, 1e-8); +} + auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr mesh) { std::shared_ptr solid_block_solver = diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 299b337b7b..795a0a8ae0 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -118,12 +118,10 @@ auto buildSystems(const std::shared_ptr& solid_solver, const std::shared_ptr& internal_variable_solver, const SolidFields& solid_fields, const InternalVariableFields& internal_variable_fields) { - return std::tuple{ - buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - internal_variable_fields), - buildInternalVariableSystem(internal_variable_solver, - InternalVariableOptions{}, - internal_variable_fields, solid_fields)}; + return std::tuple{buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, + solid_fields, internal_variable_fields), + buildInternalVariableSystem( + internal_variable_solver, InternalVariableOptions{}, internal_variable_fields, solid_fields)}; } template diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp index bd33cbbee0..465a99b97f 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp @@ -135,12 +135,12 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) auto solid = buildSolidMechanicsSystem( std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, thermal_fields); - auto thermal = buildThermalSystem( - std::make_shared(thermal_block_solver), ThermalOptions{}, thermal_fields, solid_fields); + auto thermal = buildThermalSystem(std::make_shared(thermal_block_solver), + ThermalOptions{}, thermal_fields, solid_fields); auto internal_variables = buildInternalVariableSystem( - std::make_shared(internal_variable_block_solver), InternalVariableOptions{}, internal_variable_fields, - solid_fields); + std::make_shared(internal_variable_block_solver), InternalVariableOptions{}, + internal_variable_fields, solid_fields); // Phase 3: register material integrands. setCoupledThermoMechanicsMaterial(solid, thermal, SimpleThermoelasticMaterial{}, mesh_->entireBodyName()); @@ -171,8 +171,8 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) std::vector reactions; for (size_t step = 0; step < 2; ++step) { - std::tie(states, reactions) = makeAdvancer(combined) - ->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + std::tie(states, reactions) = + makeAdvancer(combined)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } diff --git a/src/smith/differentiable_numerics/time_integration_rule.hpp b/src/smith/differentiable_numerics/time_integration_rule.hpp index 33b4e00c81..8262d31068 100644 --- a/src/smith/differentiable_numerics/time_integration_rule.hpp +++ b/src/smith/differentiable_numerics/time_integration_rule.hpp @@ -114,6 +114,7 @@ class QuasiStaticRule : public TimeIntegrationRule { /// @brief evaluate value of the ode state as used by the integration rule template + /// @brief Return the static field value. SMITH_HOST_DEVICE auto value(const TimeInfo& /*t*/, const T1& field_new) const { return field_new; @@ -121,6 +122,7 @@ class QuasiStaticRule : public TimeIntegrationRule { /// @brief evaluate time derivative discretization of the ode state as used by the integration rule template + /// @brief Return zero first derivative for a static field. SMITH_HOST_DEVICE auto dot(const TimeInfo& /*t*/, const T1& /*field_new*/) const { return zero{}; @@ -160,24 +162,36 @@ class StaticTimeIntegrationRule : public TimeIntegrationRule { int num_args() const override { return num_states; } + /** + * @brief Return the static field value. + */ template SMITH_HOST_DEVICE auto value(const TimeInfo& /*t*/, const T1& field_new) const { return field_new; } + /** + * @brief Return zero first derivative for a static field. + */ template SMITH_HOST_DEVICE auto dot(const TimeInfo& /*t*/, const T1& /*field_new*/) const { return zero{}; } + /** + * @brief Return zero second derivative for a static field. + */ template SMITH_HOST_DEVICE auto ddot(const TimeInfo& /*t*/, const T1& /*field_new*/) const { return zero{}; } + /** + * @brief Return value, first derivative, and second derivative for a static field. + */ template SMITH_HOST_DEVICE auto interpolate(const TimeInfo& t, const T1& field_new) const { From 7186714cdbf61522103a301dc9bb6e86b4dade38 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 22 Apr 2026 20:47:52 -0700 Subject: [PATCH 38/67] Still trying to simplify the interface. --- .../solid_mechanics_system.hpp | 22 +++- .../state_variable_system.hpp | 29 ++++- .../tests/CMakeLists.txt | 2 +- .../tests/test_combined_thermo_mechanics.cpp | 37 +++--- .../tests/test_solid_dynamics.cpp | 16 +-- .../test_solid_static_with_internal_vars.cpp | 6 +- .../tests/test_thermal_static.cpp | 5 +- ...t_thermo_mechanics_with_internal_vars.cpp} | 123 +++++++++++++----- .../thermal_system.hpp | 22 +++- 9 files changed, 187 insertions(+), 75 deletions(-) rename src/smith/differentiable_numerics/tests/{test_thermo_mechanics_three_system.cpp => test_thermo_mechanics_with_internal_vars.cpp} (60%) diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index c68f102e63..7e2a1fff33 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -528,7 +528,7 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons /** * @brief Build a SolidMechanicsSystem from already-registered field packs. * - * Preferred API: Rule is given as explicit template param (no rule instance needed). + * Explicit-rule API: rule is given as template param. * Additional parameter packs are registered as parameters. Coupling packs are taken from * the trailing field-pack arguments. * @@ -553,6 +553,26 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid has_stress_output); } +/** + * @brief Build a SolidMechanicsSystem from already-registered field packs. + * + * Preferred API: deduce rule from `self_fields`. + * + * Usage: + * @code + * auto solid = buildSolidMechanicsSystem( + * solver, opts, solid_fields, youngs_modulus, thermal_fields); + * @endcode + */ +template + requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) +{ + using DisplacementTimeRule = typename std::decay_t::time_rule_type; + return buildSolidMechanicsSystem(solver, options, self_fields, other_packs...); +} + /** * @brief Build a SolidMechanicsSystem from variadic field packs. * diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 6b01401bdd..9c032b664a 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -211,9 +211,6 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co } // namespace detail -/** - * @brief Build an internal-variable system from registered field packs. - */ template requires(detail::is_physics_fields_v && std::is_same_v::time_rule_type, InternalVarTimeRule> && @@ -242,6 +239,32 @@ auto buildStateVariableSystem(std::shared_ptr solver, const StateV other_packs...); } +/** + * @brief Build an internal-variable system from registered field packs. + */ +template + requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +auto buildInternalVariableSystem(std::shared_ptr solver, const InternalVariableOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) +{ + using InternalVarTimeRule = typename std::decay_t::time_rule_type; + return buildInternalVariableSystem(solver, options, self_fields, + other_packs...); +} + +/** + * @brief Backward-compatible alias for `buildInternalVariableSystem`. + */ +template + requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +auto buildStateVariableSystem(std::shared_ptr solver, const StateVariableOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) +{ + using InternalVarTimeRule = typename std::decay_t::time_rule_type; + return buildInternalVariableSystem(solver, options, self_fields, + other_packs...); +} + template > /// @brief Backward-compatible alias for `InternalVariableSystem`. diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index 8848d097a4..135e0771c8 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -16,7 +16,7 @@ set(differentiable_numerics_test_source test_thermal_static.cpp test_solid_static_with_internal_vars.cpp test_multiphysics_time_integrator.cpp - test_thermo_mechanics_three_system.cpp + test_thermo_mechanics_with_internal_vars.cpp ) smith_add_tests( SOURCES ${differentiable_numerics_test_source} diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 5a09866435..46b262ac29 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -112,10 +112,10 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( + auto solid = buildSolidMechanicsSystem( makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); - auto thermal = buildThermalSystem( + auto thermal = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto coupled = combineSystems(solid, thermal); @@ -147,10 +147,10 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( + auto solid = buildSolidMechanicsSystem( makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); - auto thermal = buildThermalSystem( + auto thermal = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto coupled = combineSystems(solid, thermal); @@ -216,10 +216,10 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( + auto solid = buildSolidMechanicsSystem( makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, thermal_fields); - auto thermal = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), - ThermalOptions{}, thermal_fields, solid_fields); + auto thermal = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), + ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solid, thermal); thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -245,10 +245,9 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, - solid_fields, thermal_fields); - auto thermal = - buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); + auto solid = + buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, thermal_fields); + auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solver_ptr, solid, thermal); thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -269,8 +268,8 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress"))); - auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - solid_opts, solid_fields); + auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, + solid_fields); ASSERT_EQ(sys->post_solve_systems.size(), 1u); constexpr double E = 100.0; @@ -303,8 +302,8 @@ TEST_F(ThermoMechanicsMeshFixture, StressOutputRegistrationDisabledByDefault) EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress"))); - auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields); + auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields); EXPECT_TRUE(sys->post_solve_systems.empty()); } @@ -315,10 +314,10 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - solid_opts, solid_fields, thermal_fields); - auto thermal = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), - ThermalOptions{}, thermal_fields, solid_fields); + auto solid = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + solid_opts, solid_fields, thermal_fields); + auto thermal = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), + ThermalOptions{}, thermal_fields, solid_fields); auto combined = combineSystems(solid, thermal); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 1242ad0ea3..144783211c 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -107,14 +107,14 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) auto field_store = std::make_shared(mesh, 100, "impl"); using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); EXPECT_NE(sys->cycle_zero_system, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { auto field_store = std::make_shared(mesh, 100, "qs"); using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); EXPECT_EQ(sys->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; } } @@ -128,7 +128,7 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverUsesOwnedSingleStepPolicy) auto field_store = std::make_shared(mesh, 100, "cycle_zero_policy"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); ASSERT_NE(system->cycle_zero_system, nullptr); ASSERT_NE(system->cycle_zero_system->solver, nullptr); @@ -141,7 +141,7 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver auto field_store = std::make_shared(mesh, 100, "cycle_zero_fallback"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); + auto system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); ASSERT_NE(system->cycle_zero_system, nullptr); ASSERT_NE(system->cycle_zero_system->solver, nullptr); @@ -164,7 +164,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto solid_fields = registerSolidMechanicsFields( field_store, SolidMechanicsOptions{.enable_stress_output = true}); - auto system = buildSolidMechanicsSystem( + auto system = buildSolidMechanicsSystem( coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, solid_fields, param_fields); static constexpr double gravity = -9.0; @@ -267,7 +267,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; SolidMechanicsOptions solid_options{.enable_stress_output = true}; auto solid_fields = registerSolidMechanicsFields(field_store, solid_options); - auto system = buildSolidMechanicsSystem(solver, solid_options, solid_fields); + auto system = buildSolidMechanicsSystem(solver, solid_options, solid_fields); static constexpr double gravity = -9.0; double E = 100.0; @@ -331,8 +331,8 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr("bulk"), FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); - auto system = buildSolidMechanicsSystem(coupled_solver, SolidMechanicsOptions{}, solid_fields, - param_fields); + auto system = + buildSolidMechanicsSystem(coupled_solver, SolidMechanicsOptions{}, solid_fields, param_fields); auto physics = makeDifferentiablePhysics(system, physics_name); auto bcs = system->disp_bc; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 795a0a8ae0..219e52ad7d 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -118,9 +118,9 @@ auto buildSystems(const std::shared_ptr& solid_solver, const std::shared_ptr& internal_variable_solver, const SolidFields& solid_fields, const InternalVariableFields& internal_variable_fields) { - return std::tuple{buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, - solid_fields, internal_variable_fields), - buildInternalVariableSystem( + return std::tuple{buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + internal_variable_fields), + buildInternalVariableSystem( internal_variable_solver, InternalVariableOptions{}, internal_variable_fields, solid_fields)}; } diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 7edc2ca2ee..636ff0485f 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -56,7 +56,7 @@ struct ThermalStaticFixture : public testing::Test { using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; auto thermal_fields = registerThermalFields<2, temp_order, TempRule>(field_store); - auto thermal_system = buildThermalSystem<2, temp_order, TempRule>(coupled_solver, ThermalOptions{}, thermal_fields); + auto thermal_system = buildThermalSystem<2, temp_order>(coupled_solver, ThermalOptions{}, thermal_fields); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -152,8 +152,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto param_fields = registerParameterFields(conductivity_param); auto thermal_fields = registerThermalFields<2, 1, TempRule>(field_store); - auto thermal_system = - buildThermalSystem<2, 1, TempRule>(coupled_solver, ThermalOptions{}, thermal_fields, param_fields); + auto thermal_system = buildThermalSystem<2, 1>(coupled_solver, ThermalOptions{}, thermal_fields, param_fields); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp similarity index 60% rename from src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp rename to src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 465a99b97f..a89689c815 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_three_system.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -5,13 +5,13 @@ // SPDX-License-Identifier: (BSD-3-Clause) /** - * @file test_thermo_mechanics_three_system.cpp - * @brief Tests 3-system coupling: SolidMechanicsSystem + ThermalSystem + InternalVariableSystem. + * @file test_thermo_mechanics_with_internal_vars.cpp + * @brief Tests thermo-mechanics with internal variables using three composed systems. * * Validates N>2 system coupling end-to-end via combineSystems. * * Layout: - * - Solid: receives temperature coupling (1-way: temperature affects elastic modulus via expansion). + * - Solid: receives temperature and alpha coupling. * - Thermal: standalone heat diffusion with a heat source. * - Internal variable (damage): receives solid displacement coupling; damage evolves with strain norm. */ @@ -38,7 +38,7 @@ namespace smith { -static constexpr int dim3 = 3; +static constexpr int dim = 3; static constexpr int disp_ord = 1; static constexpr int temp_ord = 1; static constexpr int state_ord = 0; @@ -55,19 +55,35 @@ struct SimpleThermoelasticMaterial { double heat_capacity = 1.0; double heat_source = 0.5; + template + SMITH_HOST_DEVICE auto effectiveYoungsModulus(AlphaType alpha) const + { + return E * (1.0 - 0.8 * alpha); + } + template + typename GradThetaType, typename AlphaType, typename... Params> SMITH_HOST_DEVICE auto operator()(DT /*dt*/, StateType /*state*/, GradUType grad_u, GradVType /*grad_v*/, - ThetaType theta, GradThetaType grad_theta, Params... /*params*/) const + ThetaType theta, GradThetaType grad_theta, AlphaType alpha, + Params... /*params*/) const { - double lam = E * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)); - double mu = E / (2.0 * (1.0 + nu)); - static constexpr auto I = Identity(); + auto E_eff = effectiveYoungsModulus(alpha); + auto lam = E_eff * nu / ((1.0 + nu) * (1.0 - 2.0 * nu)); + auto mu = E_eff / (2.0 * (1.0 + nu)); + static constexpr auto I = Identity(); auto eps = sym(grad_u); auto pk = lam * (tr(eps) - 3.0 * alpha_th * theta) * I + 2.0 * mu * eps; auto q0 = kappa * grad_theta; return smith::tuple{pk, heat_capacity, heat_source, q0}; } + + template + SMITH_HOST_DEVICE auto operator()(DT dt, StateType state, GradUType grad_u, GradVType grad_v, ThetaType theta, + GradThetaType grad_theta, Params... params) const + { + return (*this)(dt, state, grad_u, grad_v, theta, grad_theta, 0.0, params...); + } }; struct StrainDrivenInternalVariableMaterial { @@ -86,19 +102,18 @@ struct ThreeSystemMeshFixture : public testing::Test { void SetUp() override { datastore_ = std::make_unique(); - smith::StateManager::initialize(*datastore_, "three_system"); + smith::StateManager::initialize(*datastore_, "thermo_mechanics_with_internal_vars"); mesh_ = std::make_shared( mfem::Mesh::MakeCartesian3D(4, 2, 2, mfem::Element::HEXAHEDRON, 1.0, 0.1, 0.1), "mesh", 0, 0); - mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); - mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); + mesh_->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh_->addDomainOfBoundaryElements("right", smith::by_attr(5)); } std::unique_ptr datastore_; std::shared_ptr mesh_; }; -// 3-system staggered: solid + thermal + internal variable. -TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) +TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) { smith::LinearSolverOptions lin_opts{.linear_solver = smith::LinearSolver::SuperLU, .relative_tol = 1e-8, @@ -116,7 +131,7 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) auto thermal_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); auto internal_variable_block_solver = buildNonlinearBlockSolver(nonlin_opts, lin_opts, *mesh_); - auto field_store = std::make_shared(mesh_, 100, "three"); + auto field_store = std::make_shared(mesh_, 100); using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; @@ -125,25 +140,63 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) // Phase 1: register all fields. // registerSolidMechanicsFields must come before registerThermalFields to get // the displacement field tokens available for thermal coupling. - auto solid_fields = registerSolidMechanicsFields(field_store); - auto thermal_fields = registerThermalFields(field_store); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto thermal_fields = registerThermalFields(field_store); auto internal_variable_fields = registerInternalVariableFields(field_store); // Phase 2: build each system. - // Solid receives thermal coupling (temperature_solve_state, temperature). - auto solid = buildSolidMechanicsSystem( - std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, thermal_fields); + // Solid receives thermal and alpha coupling. + auto solid = buildSolidMechanicsSystem( + std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, thermal_fields, + internal_variable_fields); - auto thermal = buildThermalSystem(std::make_shared(thermal_block_solver), - ThermalOptions{}, thermal_fields, solid_fields); + auto thermal = buildThermalSystem(std::make_shared(thermal_block_solver), + ThermalOptions{}, thermal_fields, solid_fields); - auto internal_variables = buildInternalVariableSystem( + auto internal_variables = buildInternalVariableSystem( std::make_shared(internal_variable_block_solver), InternalVariableOptions{}, internal_variable_fields, solid_fields); // Phase 3: register material integrands. - setCoupledThermoMechanicsMaterial(solid, thermal, SimpleThermoelasticMaterial{}, mesh_->entireBodyName()); + auto material = SimpleThermoelasticMaterial{}; + auto disp_rule = solid->disp_time_rule; + auto temp_rule = thermal->temperature_time_rule; + auto alpha_rule = internal_variables->internal_variable_time_rule; + + solid->solid_weak_form->addBodyIntegral( + mesh_->entireBodyName(), + [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, auto temperature_old, + auto alpha, auto alpha_old) { + auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto T = temp_rule->value(t_info, temperature, temperature_old); + auto alpha_current = alpha_rule->value(t_info, alpha, alpha_old); + + SimpleThermoelasticMaterial::State state{}; + auto response = + material(t_info.dt(), state, get(u_current), get(v_current), get(T), + get(T), get(alpha_current)); + auto pk = get<0>(response); + return smith::tuple{get(a_current) * material.density, pk}; + }); + + thermal->thermal_weak_form->addBodyIntegral( + mesh_->entireBodyName(), + [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { + auto [T_current, T_dot] = temp_rule->interpolate(t_info, T, T_old); + auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + (void)a_current; + + SimpleThermoelasticMaterial::State state{}; + auto response = + material(t_info.dt(), state, get(u_current), get(v_current), get(T_current), + get(T_current)); + auto C_v = get<1>(response); + auto s0 = get<2>(response); + auto q0 = get<3>(response); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); + setCoupledInternalVariableMaterial(internal_variables, solid, StrainDrivenInternalVariableMaterial{}, mesh_->entireBodyName()); @@ -152,10 +205,11 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) thermal->setTemperatureBC(mesh_->domain("left")); // Compressive traction on right face. - // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old) - // — 6 self fields + 2 thermal coupling fields forwarded as trailing args. + // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old, alpha_ss, alpha_old) solid->addTraction( - "right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, auto /*temp_old*/) { + "right", + [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, auto /*temp_old*/, + auto /*alpha_ss*/, auto /*alpha_old*/) { auto t = 0.0 * X; t[0] = -0.005; return t; @@ -185,19 +239,16 @@ TEST_F(ThreeSystemMeshFixture, StaggeredThreeSystems) << "Internal-variable solver did not converge"; // Displacement should be non-zero (compressive traction). - mfem::Vector final_disp(*states[field_store->getFieldIndex("three_displacement_solve_state")].get()); - double max_disp = final_disp.Normlinf(); - EXPECT_GT(max_disp, 1e-8) << "Displacement should be non-zero under compressive traction"; + auto final_disp = states[field_store->getFieldIndex("displacement_solve_state")].get(); + EXPECT_GT(final_disp->Normlinf(), 1e-8) << "Displacement should be non-zero under compressive traction"; // Temperature should be non-zero (heat source applied). - mfem::Vector final_temp(*states[field_store->getFieldIndex("three_temperature_solve_state")].get()); - double max_temp = final_temp.Normlinf(); - EXPECT_GT(max_temp, 1e-10) << "Temperature should be non-zero under heat source"; + auto final_temp = states[field_store->getFieldIndex("temperature")].get(); + EXPECT_GT(final_temp->Normlinf(), 1e-10) << "Temperature should be non-zero under heat source"; // Damage should be non-zero (driven by strain norm). - mfem::Vector final_state(*states[field_store->getFieldIndex("three_state_solve_state")].get()); - double max_state = final_state.Normlinf(); - EXPECT_GT(max_state, 1e-10) << "Damage state should grow under deformation"; + auto final_state = states[field_store->getFieldIndex("state")].get(); + EXPECT_GT(final_state->Normlinf(), 1e-10) << "Damage state should grow under deformation"; } } // namespace smith diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 2f4e6cca20..c74deda900 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -239,7 +239,7 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl /** * @brief Build a ThermalSystem from already-registered field packs. * - * Preferred API: Rule is given as explicit template param (no rule instance needed). + * Explicit-rule API: rule is given as template param. * Additional parameter packs are registered as parameters. Coupling packs are taken from * the trailing field-pack arguments. * @@ -262,6 +262,26 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } +/** + * @brief Build a ThermalSystem from already-registered field packs. + * + * Preferred API: deduce rule from `self_fields`. + * + * Usage: + * @code + * auto thermal = buildThermalSystem( + * solver, opts, thermal_fields, solid_fields); + * @endcode + */ +template + requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, + const SelfFields& self_fields, const OtherPacks&... other_packs) +{ + using TemperatureTimeRule = typename std::decay_t::time_rule_type; + return buildThermalSystem(solver, options, self_fields, other_packs...); +} + /** * @brief Build a ThermalSystem from variadic field packs. * From 13c504a5d6bb0471673ef1ccb3efd217ea4bbdb9 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 23 Apr 2026 15:34:17 -0700 Subject: [PATCH 39/67] Add more examples, and slight clarification of interface issues. --- examples/CMakeLists.txt | 3 + examples/solid_mechanics/CMakeLists.txt | 24 ++ .../composable_solid_mechanics.cpp | 223 ++++++++++++++++++ examples/thermo_mechanics/CMakeLists.txt | 10 + .../composable_thermo_mechanics.cpp | 36 +-- .../composable_thermo_mechanics_advanced.cpp | 199 ++++++++++++++++ .../composable_solid_mechanics_tutorial.rst | 90 +++++++ ...ble_thermo_mechanics_advanced_tutorial.rst | 92 ++++++++ src/docs/sphinx/user_guide/index.rst | 2 + .../differentiable_physics.hpp | 4 +- .../differentiable_numerics/field_store.cpp | 8 +- .../multiphysics_time_integrator.cpp | 2 +- .../solid_mechanics_system.hpp | 76 +++--- ...id_mechanics_with_internal_vars_system.hpp | 63 +++-- .../state_variable_system.hpp | 3 + .../differentiable_numerics/system_solver.cpp | 11 +- .../tests/test_combined_thermo_mechanics.cpp | 103 ++++++-- .../tests/test_solid_dynamics.cpp | 11 +- .../test_solid_static_with_internal_vars.cpp | 9 +- .../tests/test_thermal_static.cpp | 13 +- ...st_thermo_mechanics_with_internal_vars.cpp | 65 +++-- .../thermal_system.hpp | 45 ++-- .../thermo_mechanical_system.hpp | 103 ++++---- .../time_info_solid_materials.hpp | 52 ++++ .../time_info_thermo_mechanical_materials.hpp | 61 +++++ 25 files changed, 1080 insertions(+), 228 deletions(-) create mode 100644 examples/solid_mechanics/CMakeLists.txt create mode 100644 examples/solid_mechanics/composable_solid_mechanics.cpp create mode 100644 examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp create mode 100644 src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst create mode 100644 src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst create mode 100644 src/smith/differentiable_numerics/time_info_solid_materials.hpp create mode 100644 src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt index 09bd191b34..1f87d42ab9 100644 --- a/examples/CMakeLists.txt +++ b/examples/CMakeLists.txt @@ -23,6 +23,9 @@ install( # Add the conduction examples add_subdirectory(conduction) +# Add the solid mechanics examples +add_subdirectory(solid_mechanics) + # Add the thermo-mechanics examples add_subdirectory(thermo_mechanics) diff --git a/examples/solid_mechanics/CMakeLists.txt b/examples/solid_mechanics/CMakeLists.txt new file mode 100644 index 0000000000..16040fb520 --- /dev/null +++ b/examples/solid_mechanics/CMakeLists.txt @@ -0,0 +1,24 @@ +# Copyright (c) Lawrence Livermore National Security, LLC and +# other Smith Project Developers. See the top-level LICENSE file for +# details. +# +# SPDX-License-Identifier: (BSD-3-Clause) + +smith_add_executable( NAME composable_solid_mechanics + SOURCES composable_solid_mechanics.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON smith + ) + +install( + FILES + composable_solid_mechanics.cpp + DESTINATION + examples/smith/solid_mechanics +) + +if(SMITH_ENABLE_TESTS) + blt_add_test(NAME composable_solid_mechanics + COMMAND composable_solid_mechanics + NUM_MPI_TASKS 1 ) +endif() diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp new file mode 100644 index 0000000000..6bb945d316 --- /dev/null +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -0,0 +1,223 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file composable_solid_mechanics.cpp + * @brief Dynamic solid-mechanics example using composable differentiable numerics systems. + */ + +#include +#include +#include +#include + +// _includes_start +#include "smith/infrastructure/application_manager.hpp" +#include "smith/physics/state/state_manager.hpp" +#include "smith/physics/mesh.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/physics/functional_objective.hpp" +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/time_info_solid_materials.hpp" +#include "smith/differentiable_numerics/differentiable_physics.hpp" +#include "smith/differentiable_numerics/evaluate_objective.hpp" +#include "smith/differentiable_numerics/differentiable_test_utils.hpp" +#include "smith/differentiable_numerics/paraview_writer.hpp" +// _includes_end + +namespace { + +constexpr size_t displacement_state_index = 0; +constexpr size_t initial_displacement_state_index = 1; +constexpr size_t velocity_state_index = 2; + +struct TimeInfoYoungsModulusNeoHookean { + using State = smith::solid_mechanics::NeoHookean::State; + + double density; + double nu; + + template + auto operator()(const smith::TimeInfo&, [[maybe_unused]] State& state, const smith::tensor& grad_u, + const GradVType&, const YoungsType& youngs_modulus) const + { + using std::log1p; + constexpr auto I = smith::Identity(); + auto E = smith::get<0>(youngs_modulus); + auto G = E / (2.0 * (1.0 + nu)); + auto K = E / (3.0 * (1.0 - 2.0 * nu)); + auto lambda = K - (2.0 / dim) * G; + auto B_minus_I = grad_u * transpose(grad_u) + transpose(grad_u) + grad_u; + auto logJ = log1p(detApIm1(grad_u)); + auto TK = lambda * logJ * I + G * B_minus_I; + auto F = grad_u + I; + return dot(TK, inv(transpose(F))); + } +}; + +std::vector outputFields(const smith::FieldStore& field_store) +{ + return {field_store.getField(field_store.prefix("displacement")), + field_store.getField(field_store.prefix("velocity")), + field_store.getField(field_store.prefix("acceleration")), field_store.getField(field_store.prefix("stress"))}; +} + +std::vector qoiFields(const std::vector& states) +{ + if (states.size() <= velocity_state_index) { + throw std::runtime_error("Unexpected solid state layout."); + } + return {states[displacement_state_index], states[velocity_state_index]}; +} + +} // namespace + +int main(int argc, char* argv[]) +{ + // _init_start + smith::ApplicationManager application_manager(argc, argv); + axom::sidre::DataStore datastore; + smith::StateManager::initialize(datastore, "composable_solid_mechanics"); + // _init_end + + // _mesh_start + constexpr int dim = 3; + constexpr int order = 1; + + auto mesh = std::make_shared( + mfem::Mesh::MakeCartesian3D(8, 2, 2, mfem::Element::HEXAHEDRON, 1.0, 0.1, 0.1), "mesh", 0, 0); + mesh->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh->addDomainOfBoundaryElements("right", smith::by_attr(5)); + // _mesh_end + + // _solver_start + smith::LinearSolverOptions linear_options{.linear_solver = smith::LinearSolver::CG, + .preconditioner = smith::Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80, + .print_level = 0}; + smith::NonlinearSolverOptions nonlinear_options{.nonlin_solver = smith::NonlinearSolver::TrustRegion, + .relative_tol = 1e-7, + .absolute_tol = 1e-8, + .max_iterations = 15, + .print_level = 0}; + + auto field_store = std::make_shared(mesh, 100); + using DispRule = smith::ImplicitNewmarkSecondOrderTimeIntegrationRule; + using DispSpace = smith::H1; + + smith::SolidMechanicsOptions solid_options{.enable_stress_output = true, .output_cauchy_stress = true}; + auto param_fields = smith::registerParameterFields(smith::FieldType>("youngs_modulus")); + auto solid_fields = smith::registerSolidMechanicsFields(field_store, solid_options); + // _solver_end + + // _build_start + auto solid_solver = + std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); + + auto solid_system = + smith::buildSolidMechanicsSystem(solid_solver, solid_options, solid_fields, param_fields); + + constexpr double E = 100.0; + constexpr double nu = 0.25; + solid_system->setMaterial(TimeInfoYoungsModulusNeoHookean{.density = 1.0, .nu = nu}, mesh->entireBodyName()); + field_store->getParameterFields()[0].get()->setFromFieldFunction([=](smith::tensor) { return E; }); + + auto initial_displacement = [](smith::tensor X) { + auto displacement = 0.0 * X; + displacement[0] = 1.0e-3 * X[0]; + return displacement; + }; + auto initial_velocity = [](smith::tensor X) { + auto velocity = 0.0 * X; + velocity[1] = 2.0e-2 * X[0]; + return velocity; + }; + + field_store->getField(field_store->prefix("displacement_solve_state")) + .get() + ->setFromFieldFunction(initial_displacement); + field_store->getField(field_store->prefix("displacement")).get()->setFromFieldFunction(initial_displacement); + field_store->getField(field_store->prefix("velocity")).get()->setFromFieldFunction(initial_velocity); + field_store->getField(field_store->prefix("acceleration")) + .get() + ->setFromFieldFunction([](smith::tensor) { return smith::tensor{}; }); + // _build_end + + // _bc_start + solid_system->setDisplacementBC(mesh->domain("left"), std::vector{0, 2}); + solid_system->addBodyForce(smith::DependsOn<>{}, mesh->entireBodyName(), [](double, auto X, auto, auto, auto) { + auto body_force = 0.0 * X; + body_force[1] = -0.02; + return body_force; + }); + solid_system->addTraction(smith::DependsOn<>{}, "right", [](double, auto X, auto, auto, auto, auto) { + auto traction = 0.0 * X; + traction[0] = -0.01; + return traction; + }); + // _bc_end + + // _run_start + if (solid_system->cycle_zero_system == nullptr) { + throw std::runtime_error("Expected cycle-zero solve for implicit dynamics."); + } + + auto physics = smith::makeDifferentiablePhysics(solid_system, "composable_solid_mechanics"); + auto initial_states = physics->getInitialFieldStates(); + smith::FunctionalObjective> qoi( + "solid_dynamic_energy_proxy", mesh, smith::spaces(qoiFields(initial_states))); + qoi.addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [](double, auto, auto U, auto V) { + auto u = smith::get(U); + auto v = smith::get(V); + return 0.5 * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + 0.05 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + }); + + constexpr double dt = 0.25; + constexpr int num_steps = 3; + auto qoi_state = + 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(initial_states), + smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); + for (int step = 0; step < num_steps; ++step) { + physics->advanceTimestep(dt); + qoi_state = qoi_state + + smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(physics->getFieldStates()), + smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); + } + + std::cout << "reaction norm: " << physics->getReactionStates().front().get()->Norml2() << '\n'; + gretl::set_as_objective(qoi_state); + std::cout << "QoI value: " << qoi_state.get() << '\n'; + qoi_state.data_store().back_prop(); + auto shape_displacement = physics->getShapeDispFieldState(); + auto initial_displacement_state = initial_states[initial_displacement_state_index]; + auto initial_velocity_state = initial_states[velocity_state_index]; + auto youngs_modulus_state = field_store->getParameterFields()[0]; + std::cout << "dQoI/d(shape) norm: " << shape_displacement.get_dual()->Norml2() << '\n'; + std::cout << "dQoI/d(youngs_modulus) norm: " << youngs_modulus_state.get_dual()->Norml2() << '\n'; + std::cout << "dQoI/d(initial displacement) norm: " << initial_displacement_state.get_dual()->Norml2() << '\n'; + std::cout << "dQoI/d(initial velocity) norm: " << initial_velocity_state.get_dual()->Norml2() << '\n'; + std::cout << "shape FD rate: " << smith::checkGradWrt(qoi_state, shape_displacement, 1.0e-2, 4, false) << '\n'; + std::cout << "youngs_modulus FD rate: " << smith::checkGradWrt(qoi_state, youngs_modulus_state, 5.0e-2, 4, false) + << '\n'; + std::cout << "initial displacement FD rate: " + << smith::checkGradWrt(qoi_state, initial_displacement_state, 5.0e-3, 4, false) << '\n'; + std::cout << "initial velocity FD rate: " << smith::checkGradWrt(qoi_state, initial_velocity_state, 5.0e-3, 4, false) + << '\n'; + // _run_end + + // _output_start + auto writer = smith::createParaviewWriter(*mesh, outputFields(*field_store), "paraview_composable_solid_mechanics", + smith::ParaviewWriter::Options{.write_duals = false}); + writer.write(physics->cycle(), physics->time(), outputFields(*field_store)); + std::cout << "ParaView output: paraview_composable_solid_mechanics\n"; + // _output_end + + return 0; +} diff --git a/examples/thermo_mechanics/CMakeLists.txt b/examples/thermo_mechanics/CMakeLists.txt index 07cf24f7cb..fe93ead407 100644 --- a/examples/thermo_mechanics/CMakeLists.txt +++ b/examples/thermo_mechanics/CMakeLists.txt @@ -10,9 +10,16 @@ smith_add_executable( NAME composable_thermo_mechanics DEPENDS_ON smith ) +smith_add_executable( NAME composable_thermo_mechanics_advanced + SOURCES composable_thermo_mechanics_advanced.cpp + OUTPUT_DIR ${EXAMPLE_OUTPUT_DIRECTORY} + DEPENDS_ON smith + ) + install( FILES composable_thermo_mechanics.cpp + composable_thermo_mechanics_advanced.cpp DESTINATION examples/smith/thermo_mechanics ) @@ -21,4 +28,7 @@ if(SMITH_ENABLE_TESTS) blt_add_test(NAME composable_thermo_mechanics COMMAND composable_thermo_mechanics NUM_MPI_TASKS 1 ) + blt_add_test(NAME composable_thermo_mechanics_advanced + COMMAND composable_thermo_mechanics_advanced + NUM_MPI_TASKS 1 ) endif() diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index a7b0faae9f..21bd3c95a7 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -21,6 +21,7 @@ #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" #include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" @@ -36,6 +37,8 @@ int main(int argc, char* argv[]) // _mesh_start constexpr int dim = 3; + constexpr int order = 1; + auto mesh = std::make_shared( mfem::Mesh::MakeCartesian3D(8, 2, 2, mfem::Element::HEXAHEDRON, 1.0, 0.1, 0.1), "mesh", 0, 0); mesh->addDomainOfBoundaryElements("left", smith::by_attr(3)); @@ -55,13 +58,13 @@ int main(int argc, char* argv[]) .max_line_search_iterations = 6, .print_level = 0}; - auto field_store = std::make_shared(mesh, 100, "tutorial_"); + auto field_store = std::make_shared(mesh, 100); using DispRule = smith::QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = smith::BackwardEulerFirstOrderTimeIntegrationRule; - auto solid_fields = smith::registerSolidMechanicsFields(field_store); - auto thermal_fields = smith::registerThermalFields(field_store); + auto solid_fields = smith::registerSolidMechanicsFields(field_store); + auto thermal_fields = smith::registerThermalFields(field_store); // _solver_end // _build_start @@ -70,33 +73,34 @@ int main(int argc, char* argv[]) auto thermal_solver = std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); - auto solid = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, - solid_fields, thermal_fields); - auto thermal = smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, - solid_fields); + auto solid_system = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, + solid_fields, thermal_fields); + auto thermal_system = + smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, solid_fields); - smith::thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - smith::setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh->entireBodyName()); + smith::thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; + smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); - auto coupled = smith::combineSystems(solid, thermal); + auto coupled_system = smith::combineSystems(solid_system, thermal_system); // _build_end // _bc_start - solid->setDisplacementBC(mesh->domain("left")); - thermal->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); - thermal->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); + solid_system->setDisplacementBC(mesh->domain("left")); + thermal_system->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); + thermal_system->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); - solid->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto... /*unused*/) { + solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto... /*unused*/) { auto traction = 0.0 * X; traction[0] = -0.01; return traction; }); - thermal->addHeatSource(mesh->entireBodyName(), [](auto, auto, auto, auto... /*unused*/) { return 0.5; }); + thermal_system->addHeatSource(mesh->entireBodyName(), [](auto, auto, auto, auto... /*unused*/) { return 0.5; }); // _bc_end // _run_start - auto physics = smith::makeDifferentiablePhysics(coupled, "composable_thermo_mechanics"); + auto physics = smith::makeDifferentiablePhysics(coupled_system, "composable_thermo_mechanics"); for (int step = 0; step < 2; ++step) { physics->advanceTimestep(1.0); } diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp new file mode 100644 index 0000000000..8989e8f65d --- /dev/null +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -0,0 +1,199 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +/** + * @file composable_thermo_mechanics_advanced.cpp + * @brief Advanced composable thermo-mechanics example with staged solves, a differentiable QoI, + * finite-difference verification, and ParaView output including Cauchy stress. + */ + +#include +#include +#include + +// _includes_start +#include "smith/infrastructure/application_manager.hpp" +#include "smith/physics/state/state_manager.hpp" +#include "smith/physics/mesh.hpp" +#include "smith/numerics/solver_config.hpp" +#include "smith/physics/functional_objective.hpp" + +#include "smith/differentiable_numerics/nonlinear_block_solver.hpp" +#include "smith/differentiable_numerics/system_solver.hpp" +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/combined_system.hpp" +#include "smith/differentiable_numerics/differentiable_physics.hpp" +#include "smith/differentiable_numerics/paraview_writer.hpp" +#include "smith/differentiable_numerics/evaluate_objective.hpp" +#include "smith/differentiable_numerics/differentiable_test_utils.hpp" +#include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" +#include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" +// _includes_end + +namespace { + +std::vector outputFields(const smith::FieldStore& field_store) +{ + return {field_store.getField(field_store.prefix("displacement")), + field_store.getField(field_store.prefix("temperature")), field_store.getField(field_store.prefix("stress"))}; +} + +std::vector qoiFields(const smith::FieldStore& field_store) +{ + return {field_store.getField(field_store.prefix("displacement")), + field_store.getField(field_store.prefix("temperature"))}; +} + +} // namespace + +int main(int argc, char* argv[]) +{ + // _init_start + smith::ApplicationManager application_manager(argc, argv); + axom::sidre::DataStore datastore; + smith::StateManager::initialize(datastore, "composable_thermo_mechanics_advanced"); + // _init_end + + // _mesh_start + constexpr int dim = 3; + constexpr int order = 1; + using DispSpace = smith::H1; + using TempSpace = smith::H1; + + auto mesh = std::make_shared( + mfem::Mesh::MakeCartesian3D(8, 2, 2, mfem::Element::HEXAHEDRON, 1.0, 0.1, 0.1), "mesh", 0, 0); + mesh->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh->addDomainOfBoundaryElements("right", smith::by_attr(5)); + // _mesh_end + + // _solver_start + auto field_store = std::make_shared(mesh, 200); + + smith::SolidMechanicsOptions solid_options{.enable_stress_output = true, .output_cauchy_stress = true}; + auto solid_fields = smith::registerSolidMechanicsFields( + field_store, solid_options); + auto thermal_fields = + smith::registerThermalFields(field_store); + auto param_fields = smith::registerParameterFields(smith::FieldType>("thermal_expansion_scaling")); + + auto solid_system = + smith::buildSolidMechanicsSystem(nullptr, solid_options, solid_fields, param_fields, thermal_fields); + auto thermal_system = smith::buildThermalSystem(nullptr, smith::ThermalOptions{}, thermal_fields, + param_fields, solid_fields); + + smith::thermomechanics::TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; + smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); + + field_store->getParameterFields()[0].get()->setFromFieldFunction([](smith::tensor) { return 1.0; }); + + // _bc_start + solid_system->setDisplacementBC(mesh->domain("left")); + thermal_system->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); + thermal_system->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); + + solid_system->addTraction(smith::DependsOn<>{}, "right", + [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/) { + auto traction = 0.0 * X; + traction[0] = -0.005; + return traction; + }); + + thermal_system->addHeatSource(mesh->entireBodyName(), [](auto, auto... /*unused*/) { return 0.1; }); + // _bc_end + + smith::LinearSolverOptions trust_region_linear{.linear_solver = smith::LinearSolver::CG, + .preconditioner = smith::Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 80, + .print_level = 0}; + smith::NonlinearSolverOptions trust_region_nonlin{.nonlin_solver = smith::NonlinearSolver::TrustRegion, + .relative_tol = 1e-7, + .absolute_tol = 1e-8, + .max_iterations = 15, + .print_level = 0}; + + smith::LinearSolverOptions coupled_linear{.linear_solver = smith::LinearSolver::SuperLU, + .relative_tol = 1e-8, + .absolute_tol = 1e-10, + .max_iterations = 200, + .print_level = 0}; + smith::NonlinearSolverOptions coupled_nonlin{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-8, + .absolute_tol = 1e-8, + .max_iterations = 12, + .max_line_search_iterations = 6, + .print_level = 0}; + // _solver_end + + // _build_start + size_t max_staggered_iterations = 10; + auto custom_solver = std::make_shared(max_staggered_iterations); + custom_solver->addSubsystemSolver( + {0}, smith::buildNonlinearBlockSolver(trust_region_nonlin, trust_region_linear, *mesh), 1.0); + custom_solver->addSubsystemSolver({1}, smith::buildNonlinearBlockSolver(coupled_nonlin, coupled_linear, *mesh), 1.0); + + auto coupled_system = smith::combineSystems(custom_solver, solid_system, thermal_system); + std::string physics_name = "composable_thermo_mechanics_advanced"; + auto physics = smith::makeDifferentiablePhysics(coupled_system, physics_name, {}); + // _build_end + + // _qoi_start + smith::FunctionalObjective> qoi("thermo_mechanical_energy_proxy", mesh, + smith::spaces(qoiFields(*field_store))); + qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](double, auto /*X*/, auto U, auto Theta) { + auto u = smith::get(U); + auto theta = smith::get(Theta); + return 0.5 * u[0] * u[0] + 0.05 * theta * theta; + }); + + auto qoi_state = + 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(*field_store), + smith::TimeInfo(physics->time(), 1.0, static_cast(physics->cycle()))); + // _qoi_end + + // _run_start + constexpr double dt = 0.5; + constexpr int qoi_steps = 1; + for (int step = 0; step < qoi_steps; ++step) { + physics->advanceTimestep(dt); + qoi_state = qoi_state + + smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(*field_store), + smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); + } + // _run_end + + // _output_start + auto output_writer = + smith::createParaviewWriter(*mesh, outputFields(*field_store), "paraview_composable_thermo_mechanics_advanced", + smith::ParaviewWriter::Options{.write_duals = false}); + output_writer.write(physics->cycle(), physics->time(), outputFields(*field_store)); + + std::cout << "ParaView output: paraview_composable_thermo_mechanics_advanced\n"; + // _output_end + + // _sensitivity_start + gretl::set_as_objective(qoi_state); + auto qoi_value = qoi_state.get(); + std::cout << "QoI value: " << qoi_value << '\n'; + qoi_state.data_store().back_prop(); + + auto parameter_state = field_store->getParameterFields()[0]; + auto parameter_sensitivity = parameter_state.get_dual()->Norml2(); + + std::cout << "dQoI/d(thermal_expansion_scaling) norm: " << parameter_sensitivity << '\n'; + SLIC_ERROR_ROOT_IF(parameter_sensitivity <= 0.0, "Expected non-zero QoI sensitivity."); + + auto fd_order = smith::checkGradWrt(qoi_state, parameter_state, 5.0e-2, 4, true); + std::cout << "finite-difference convergence rate: " << fd_order << '\n'; + SLIC_ERROR_ROOT_IF(fd_order < 0.7, "Finite-difference check did not converge."); + // _sensitivity_end + + return 0; +} diff --git a/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst b/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst new file mode 100644 index 0000000000..22508f5fd3 --- /dev/null +++ b/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst @@ -0,0 +1,90 @@ +.. ## Copyright (c) Lawrence Livermore National Security, LLC and +.. ## other Smith Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +############################### +Composable Solid Mechanics Demo +############################### + +This example shows a solid-only setup using the composable differentiable +numerics interface. It registers fields, builds a solid system, applies a +Neo-Hookean material with a differentiable Young's modulus field, seeds dynamic +initial conditions, runs a cycle-zero startup solve plus several implicit +Newmark steps, checks shape, parameter, and initial-condition sensitivities, +and writes displacement, velocity, acceleration, and stress to ParaView. + +The full source code lives in ``examples/solid_mechanics/composable_solid_mechanics.cpp``. + +Includes and Initialization +--------------------------- + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _includes_start + :end-before: _includes_end + :language: C++ + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _init_start + :end-before: _init_end + :language: C++ + +Mesh Construction +----------------- + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _mesh_start + :end-before: _mesh_end + :language: C++ + +Solver and Field Registration +----------------------------- + +The field registration phase declares the dynamic displacement state pack, +Young's modulus parameter field, and optional stress output field on a shared +``FieldStore``. + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _solver_start + :end-before: _solver_end + :language: C++ + +System Build and Material Setup +------------------------------- + +The solid system is built from the registered field pack. The material wrapper +adapts the standard Neo-Hookean material to the ``TimeInfo``-based interface, +pulling bulk and shear response from the Young's modulus parameter field. The +example also seeds non-zero initial displacement and velocity fields. + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _build_start + :end-before: _build_end + :language: C++ + +Boundary Conditions and Loads +----------------------------- + +The left boundary uses a component-wise Dirichlet condition, fixing only the +``x`` and ``z`` displacement components. + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _bc_start + :end-before: _bc_end + :language: C++ + +Advance, Sensitivities, and Reactions +------------------------------------- + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _run_start + :end-before: _run_end + :language: C++ + +Write ParaView Output +--------------------- + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _output_start + :end-before: _output_end + :language: C++ diff --git a/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst b/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst new file mode 100644 index 0000000000..3962e0edd5 --- /dev/null +++ b/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst @@ -0,0 +1,92 @@ +.. ## Copyright (c) Lawrence Livermore National Security, LLC and +.. ## other Smith Project Developers. See the top-level COPYRIGHT file for details. +.. ## +.. ## SPDX-License-Identifier: (BSD-3-Clause) + +############################################# +Composable Thermo-Mechanics Advanced Example +############################################# + +This example extends the basic thermo-mechanics tutorial with a staged solver, +a parameter field, a differentiable quantity of interest, finite-difference +verification, and ParaView output. + +The full source code lives in ``examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp``. + +Includes and Initialization +--------------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _includes_start + :end-before: _includes_end + :language: C++ + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _init_start + :end-before: _init_end + :language: C++ + +Mesh and Field Setup +-------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _mesh_start + :end-before: _mesh_end + :language: C++ + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _solver_start + :end-before: _solver_end + :language: C++ + +Staged Solver and Coupled Build +------------------------------- + +This example uses a custom staggered ``SystemSolver``. The solid subsystem uses +trust-region iterations, while the thermal subsystem uses Newton line search. + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _build_start + :end-before: _build_end + :language: C++ + +Boundary Conditions and Loads +----------------------------- + +The traction call uses ``DependsOn<>{}``, so the user callback receives only the +state arguments it actually needs and none of the trailing coupling or parameter +fields. + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _bc_start + :end-before: _bc_end + :language: C++ + +QoI Definition and Timestep Advance +----------------------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _qoi_start + :end-before: _qoi_end + :language: C++ + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _run_start + :end-before: _run_end + :language: C++ + +ParaView Output +--------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _output_start + :end-before: _output_end + :language: C++ + +Sensitivity and Finite-Difference Check +--------------------------------------- + +.. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp + :start-after: _sensitivity_start + :end-before: _sensitivity_end + :language: C++ diff --git a/src/docs/sphinx/user_guide/index.rst b/src/docs/sphinx/user_guide/index.rst index 32cfb8454d..ffd6ecb6d9 100644 --- a/src/docs/sphinx/user_guide/index.rst +++ b/src/docs/sphinx/user_guide/index.rst @@ -12,7 +12,9 @@ User Guide :maxdepth: 2 simple_conduction_tutorial + composable_solid_mechanics_tutorial composable_thermo_mechanics_tutorial + composable_thermo_mechanics_advanced_tutorial command_line_options input_schema diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index 59070cc956..4148ead019 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -224,16 +224,14 @@ template * * @param system Main system to wrap. * @param physics_name Name exposed through the `BasePhysics` interface. - * @param cycle_zero_system Optional startup solve system. * @param post_solve_systems Optional systems solved after each main step. */ std::unique_ptr makeDifferentiablePhysics( std::shared_ptr system, const std::string& physics_name, - std::shared_ptr cycle_zero_system = nullptr, std::vector> post_solve_systems = {}) { return makeDifferentiablePhysics( - system, makeAdvancer(system, std::move(cycle_zero_system), std::move(post_solve_systems)), physics_name); + system, makeAdvancer(system, system->cycle_zero_system, std::move(post_solve_systems)), physics_name); } } // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 45ba4a4332..f9b18a69aa 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -335,8 +335,14 @@ const std::vector& FieldStore::getStateFields() const { return state std::vector FieldStore::getOutputFieldStates() const { std::vector output; + std::set public_static_fields; + for (const auto& [rule, mapping] : time_integration_rules_) { + if (mapping.history_name.empty() && mapping.dot_name.empty() && mapping.ddot_name.empty()) { + public_static_fields.insert(mapping.primary_name); + } + } for (size_t i = 0; i < states_.size(); ++i) { - if (!is_solve_state_[i]) { + if (!is_solve_state_[i] || public_static_fields.count(states_[i].get()->name()) > 0) { output.push_back(states_[i]); } } diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index 36e5001292..fd5f32da81 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -166,7 +166,7 @@ std::pair, std::vector> MultiphysicsTimeI } } - // Copy solve-state → history for post-solve fields (e.g. stress_solve_state → stress). + // Copy solve-state → history for post-solve fields when a public history field exists. // The main loop skipped these rules; their primary fields are already correct in new_states // (populated from all_current_states above), so only the history field needs updating. for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 7e2a1fff33..c24a8b7115 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -95,7 +95,7 @@ struct SolidMechanicsSystem : public SystemBase { bool output_cauchy_stress = false; ///< Project Cauchy stress instead of PK1 when true. /** - * @brief Set the material model for a domain, defining integrals for the solid weak form. + * @brief Set material model for domain. * @tparam MaterialType The material model type. * @param material The material model instance. * @param domain_name The name of the domain to apply the material to. @@ -109,7 +109,7 @@ struct SolidMechanicsSystem : public SystemBase { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); typename MaterialType::State state; - auto pk_stress = material(state, get(u_current), params...); + auto pk_stress = material(t_info, state, get(u_current), get(v_current), params...); return smith::tuple{get(a_current) * material.density, pk_stress}; }); @@ -119,7 +119,7 @@ struct SolidMechanicsSystem : public SystemBase { cycle_zero_solid_weak_form->addBodyIntegral( domain_name, [=](auto t_info, auto /*X*/, auto u, auto /*v_old*/, auto a, auto... params) { typename MaterialType::State state; - auto pk_stress = material(state, get(u), params...); + auto pk_stress = material(t_info, state, get(u), tensor{}, params...); return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk_stress); }); @@ -137,7 +137,7 @@ struct SolidMechanicsSystem : public SystemBase { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); typename MaterialType::State state; - auto pk_stress = material(state, get(u_current), params...); + auto pk_stress = material(t_info, state, get(u_current), get(v_current), params...); // Flatten the chosen stress into a dim*dim vector and subtract from the unknown. auto flat_stress = [&]() { @@ -168,18 +168,17 @@ struct SolidMechanicsSystem : public SystemBase { BodyForceType force_function) { auto captured_rule = disp_time_rule; - solid_weak_form->addBodySource( - depends_on, domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + solid_weak_form->template addBodySource<0, 1, 2, 3, (4 + active_parameters)...>( + DependsOn<0, 1, 2, 3, (4 + active_parameters)...>{}, domain_name, + [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); return force_function(t_info.time(), X, u_current, v_current, a_current, params...); }); addCycleZeroBodySourceImpl( - domain_name, - [=](auto t_info, auto X, auto u, auto v_old, auto a, auto... params) { + depends_on, domain_name, [=](auto t_info, auto X, auto u, auto v_old, auto a, auto... params) { return detail::scaleCycleZeroTerm(t_info, force_function(t_info.time(), X, u, v_old, a, params...)); - }, - std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); + }); } /** @@ -191,7 +190,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addBodyForce(const std::string& domain_name, BodyForceType force_function) { - addBodyForceAllParams(domain_name, force_function, std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); + addBodyForceAllParams(domain_name, force_function, std::make_index_sequence{}); } /** @@ -207,19 +206,17 @@ struct SolidMechanicsSystem : public SystemBase { TractionType traction_function) { auto captured_rule = disp_time_rule; - solid_weak_form->addBoundaryFlux( - depends_on, domain_name, + solid_weak_form->template addBoundaryFlux<0, 1, 2, 3, (4 + active_parameters)...>( + DependsOn<0, 1, 2, 3, (4 + active_parameters)...>{}, domain_name, [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); return traction_function(t_info.time(), X, n, u_current, v_current, a_current, params...); }); addCycleZeroBoundaryFluxImpl( - domain_name, - [=](auto t_info, auto X, auto n, auto u, auto v_old, auto a, auto... params) { + depends_on, domain_name, [=](auto t_info, auto X, auto n, auto u, auto v_old, auto a, auto... params) { return detail::scaleCycleZeroTerm(t_info, traction_function(t_info.time(), X, n, u, v_old, a, params...)); - }, - std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); + }); } /** @@ -231,7 +228,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addTraction(const std::string& domain_name, TractionType traction_function) { - addTractionAllParams(domain_name, traction_function, std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); + addTractionAllParams(domain_name, traction_function, std::make_index_sequence{}); } /** @@ -247,8 +244,9 @@ struct SolidMechanicsSystem : public SystemBase { PressureType pressure_function) { auto captured_rule = disp_time_rule; - solid_weak_form->addBoundaryIntegral( - depends_on, domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + solid_weak_form->template addBoundaryIntegral<0, 1, 2, 3, (4 + active_parameters)...>( + DependsOn<0, 1, 2, 3, (4 + active_parameters)...>{}, domain_name, + [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto u_current = captured_rule->value(t_info, u, u_old, v_old, a_old); auto x_current = X + u_current; @@ -261,8 +259,7 @@ struct SolidMechanicsSystem : public SystemBase { }); addCycleZeroBoundaryIntegralImpl( - domain_name, - [=](auto t_info, auto X, auto u, auto /*v_old*/, auto /*a*/, auto... params) { + depends_on, domain_name, [=](auto t_info, auto X, auto u, auto /*v_old*/, auto /*a*/, auto... params) { auto u_current = u; auto x_current = X + u_current; @@ -272,8 +269,7 @@ struct SolidMechanicsSystem : public SystemBase { auto pressure = pressure_function(t_info.time(), get(X), get(params)...); return detail::scaleCycleZeroTerm(t_info, pressure * n_deformed * (1.0 / n_shape_norm)); - }, - std::make_index_sequence<3 + Coupling::num_coupling_fields>{}); + }); } /** @@ -285,7 +281,7 @@ struct SolidMechanicsSystem : public SystemBase { template void addPressure(const std::string& domain_name, PressureType pressure_function) { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence<4 + Coupling::num_coupling_fields>{}); + addPressureAllParams(domain_name, pressure_function, std::make_index_sequence{}); } /// Set zero-displacement Dirichlet BC on all components. @@ -323,28 +319,31 @@ struct SolidMechanicsSystem : public SystemBase { addPressure(DependsOn(Is)...>{}, domain_name, pressure_function); } - // Cycle-zero helpers: always use all-params DependsOn with the 3-state cycle-zero form count - template - void addCycleZeroBodySourceImpl(const std::string& name, IntegrandType f, std::index_sequence) + // Cycle-zero helpers always include the 3 cycle-zero state slots, followed by the selected tail args. + template + void addCycleZeroBodySourceImpl(DependsOn, const std::string& name, IntegrandType f) { if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBodySource(DependsOn(Is)...>{}, name, f); + cycle_zero_solid_weak_form->template addBodySource<0, 1, 2, (3 + active_parameters)...>( + DependsOn<0, 1, 2, (3 + active_parameters)...>{}, name, f); } } - template - void addCycleZeroBoundaryFluxImpl(const std::string& name, IntegrandType f, std::index_sequence) + template + void addCycleZeroBoundaryFluxImpl(DependsOn, const std::string& name, IntegrandType f) { if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBoundaryFlux(DependsOn(Is)...>{}, name, f); + cycle_zero_solid_weak_form->template addBoundaryFlux<0, 1, 2, (3 + active_parameters)...>( + DependsOn<0, 1, 2, (3 + active_parameters)...>{}, name, f); } } - template - void addCycleZeroBoundaryIntegralImpl(const std::string& name, IntegrandType f, std::index_sequence) + template + void addCycleZeroBoundaryIntegralImpl(DependsOn, const std::string& name, IntegrandType f) { if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBoundaryIntegral(DependsOn(Is)...>{}, name, f); + cycle_zero_solid_weak_form->template addBoundaryIntegral<0, 1, 2, (3 + active_parameters)...>( + DependsOn<0, 1, 2, (3 + active_parameters)...>{}, name, f); } } }; @@ -390,9 +389,8 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, if (options.enable_stress_output) { auto stress_time_rule = std::make_shared(); - FieldType> stress_type("stress_solve_state"); + FieldType> stress_type("stress"); field_store->addIndependent(stress_type, stress_time_rule); - field_store->addDependent(stress_type, FieldStore::TimeDerivative::VAL, "stress"); } return physics_fields; @@ -407,7 +405,7 @@ namespace detail { /// @brief Return true when stress output fields were registered during phase 1. inline bool hasRegisteredStressOutput(const std::shared_ptr& field_store) { - return field_store->hasField(field_store->prefix("stress_solve_state")); + return field_store->hasField(field_store->prefix("stress")); } /// @brief Build a cycle-zero solver from the main solver when possible, else use fallback defaults. @@ -490,7 +488,7 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons } if (has_stress_output) { - FieldType> stress_type(field_store->prefix("stress_solve_state"), true); + FieldType> stress_type(field_store->prefix("stress"), true); FieldType> disp_as_input(disp_type.name); std::string stress_name = field_store->prefix("stress_projection"); sys->stress_weak_form = std::apply( diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 976dfe3b92..6d6686fb86 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -18,10 +18,9 @@ namespace smith { namespace detail { -template +template /// @brief Dispatch internal-variable material calls with or without explicit `TimeInfo`. -auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const TimeInfoType& t_info, AlphaType alpha, +auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const TimeInfo& t_info, AlphaType alpha, AlphaDotType alpha_dot, DerivType deriv_u, ParamTypes&&... params) { if constexpr (requires { material(t_info, alpha, alpha_dot, deriv_u, std::forward(params)...); }) { @@ -31,6 +30,38 @@ auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const } } +template +/// @brief Evaluate solid/internal-variable material using TimeInfo-aware signature. +auto evaluateSolidInternalVariableMaterial(const MaterialType& material, const TimeInfo& t_info, StateType& state, + const GradUType& grad_u, const GradVType& grad_v, AlphaType alpha, + ParamTypes&&... params) +{ + return material(t_info, state, grad_u, grad_v, alpha, std::forward(params)...); +} + +template +/// @brief Adapts coupled internal-variable solids to solid-system material interface. +struct CoupledSolidInternalVariableMaterialAdapter { + /// Material state type forwarded to solid system. + using State = typename MaterialType::State; + + MaterialType material; ///< Wrapped constitutive model. + InternalVariableRulePtr internal_variable_time_rule; ///< Time rule used to recover current internal variable. + double density; ///< Material density exposed for solid residual. + + template + /// @brief Evaluate wrapped material with current internal-variable value. + auto operator()(const TimeInfo& t_info, StateType& state, GradUType grad_u, GradVType grad_v, AlphaType alpha, + AlphaOldType alpha_old, ParamTypes&&... params) const + { + auto alpha_current = internal_variable_time_rule->value(t_info, alpha, alpha_old); + return evaluateSolidInternalVariableMaterial(material, t_info, state, grad_u, grad_v, get(alpha_current), + std::forward(params)...); + } +}; + } // namespace detail /** @@ -44,7 +75,7 @@ auto evaluateCoupledInternalVariableMaterial(const MaterialType& material, const * (first 4 coupling positions: displacement_solve_state, displacement, velocity, acceleration). * * Solid material callable must satisfy: - * material(state, grad_u, alpha_value, params...) -> PK1 + * material(t_info, state, grad_u, grad_v, alpha_value, params...) -> PK1 */ template @@ -53,28 +84,12 @@ void setCoupledSolidMechanicsInternalVariableMaterial( std::shared_ptr> internal_variables, const MaterialType& material, const std::string& domain_name) { - auto captured_disp_rule = solid->disp_time_rule; auto captured_internal_variable_rule = internal_variables->internal_variable_time_rule; + auto solid_material = + detail::CoupledSolidInternalVariableMaterialAdapter{ + material, captured_internal_variable_rule, material.density}; - solid->solid_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, - auto a_old, auto alpha, auto alpha_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto alpha_current = captured_internal_variable_rule->value(t_info, alpha, alpha_old); - - typename MaterialType::State material_state; - auto pk_stress = material(material_state, get(u_current), get(alpha_current), params...); - - return smith::tuple{get(a_current) * material.density, pk_stress}; - }); - - if (solid->cycle_zero_solid_weak_form) { - solid->cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto /*v*/, auto a, auto alpha, auto... params) { - typename MaterialType::State material_state; - auto pk_stress = material(material_state, get(u), get(alpha), params...); - return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk_stress); - }); - } + solid->setMaterial(solid_material, domain_name); } /** diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 9c032b664a..f702895558 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -211,6 +211,9 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co } // namespace detail +/** + * @brief Build an internal-variable system from explicitly typed field packs. + */ template requires(detail::is_physics_fields_v && std::is_same_v::time_rule_type, InternalVarTimeRule> && diff --git a/src/smith/differentiable_numerics/system_solver.cpp b/src/smith/differentiable_numerics/system_solver.cpp index fe20a0fc2a..36d855d8c4 100644 --- a/src/smith/differentiable_numerics/system_solver.cpp +++ b/src/smith/differentiable_numerics/system_solver.cpp @@ -160,7 +160,9 @@ std::vector SystemSolver::solve(const std::vector& residu block_solve(stage_residuals, stage_block_indices, shape_disp, stage_states, stage_params, time_info, stage.solver.get(), stage_bc_managers); - // Propagate updated fields to all residuals that reference them. + // Propagate updated fields to every residual input that references the solved field. + // Match by field name, not by block_indices: coupling fields appear as fixed inputs in + // other rows and therefore do not have a valid unknown-block entry there. // Apply relaxation: x_new = omega * x_solved + (1 - omega) * x_k. for (size_t i = 0; i < num_stage_blocks; ++i) { size_t global_col = stage.block_indices[i]; @@ -172,9 +174,10 @@ std::vector SystemSolver::solve(const std::vector& residu } for (size_t r = 0; r < num_residuals; ++r) { - size_t c = block_indices[r][global_col]; - if (c != invalid_block_index) { - current_states[r][c] = new_state; + for (auto& row_state : current_states[r]) { + if (row_state.get()->name() == new_state.get()->name()) { + row_state = new_state; + } } } } diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 46b262ac29..894d95f4dd 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -23,7 +23,10 @@ #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" +#include "smith/differentiable_numerics/evaluate_objective.hpp" #include "smith/differentiable_numerics/nonlinear_solve.hpp" +#include "smith/differentiable_numerics/time_info_solid_materials.hpp" +#include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" #include "smith/physics/functional_objective.hpp" #include "gretl/wang_checkpoint_strategy.hpp" @@ -63,8 +66,7 @@ struct ThermoMechanicsMeshFixture : public testing::Test { std::tie(std::ignore, reactions) = makeAdvancer(coupled)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, field_store_->getStateFields(), params); - mfem::Vector final_disp( - *field_store_->getStateFields()[field_store_->getFieldIndex("displacement_solve_state")].get()); + mfem::Vector final_disp(*field_store_->getField("displacement").get()); double deflection = 0.0; for (int i = 1; i < final_disp.Size(); i += dim) { deflection = std::max(deflection, std::abs(final_disp(i))); @@ -121,9 +123,9 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto coupled = combineSystems(solid, thermal); auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); const auto& solid_dual_space = physics->dual("reactions").space(); - const auto& solid_state_space = physics->state("displacement_solve_state").space(); + const auto& solid_state_space = physics->state("displacement").space(); const auto& thermal_dual_space = physics->dual("thermal_flux").space(); - const auto& thermal_state_space = physics->state("temperature_solve_state").space(); + const auto& thermal_state_space = physics->state("temperature").space(); EXPECT_EQ(physics->dualNames().size(), 2); EXPECT_EQ(physics->dualNames()[0], "reactions"); @@ -154,8 +156,8 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto coupled = combineSystems(solid, thermal); - thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + thermomechanics::TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -187,6 +189,81 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) EXPECT_TRUE(param_sens->Norml2() > 0.0); } +TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) +{ + FieldType> thermal_expansion_scaling("thermal_expansion_scaling"); + + auto param_fields = registerParameterFields(thermal_expansion_scaling); + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); + + LinearSolverOptions solid_lin_opts{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreAMG, + .relative_tol = 1e-6, + .absolute_tol = 1e-10, + .max_iterations = 120}; + NonlinearSolverOptions solid_nonlin_opts{ + .nonlin_solver = NonlinearSolver::TrustRegion, .relative_tol = 1e-6, .absolute_tol = 1e-7, .max_iterations = 25}; + LinearSolverOptions thermal_lin_opts{ + .linear_solver = LinearSolver::SuperLU, .relative_tol = 1e-8, .absolute_tol = 1e-10, .max_iterations = 80}; + NonlinearSolverOptions thermal_nonlin_opts{.nonlin_solver = NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-7, + .absolute_tol = 1e-8, + .max_iterations = 12, + .max_line_search_iterations = 6}; + + auto solid = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + param_fields, thermal_fields); + auto thermal = + buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, param_fields, solid_fields); + + auto custom_solver = std::make_shared(10); + custom_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); + custom_solver->addSubsystemSolver({1}, buildNonlinearBlockSolver(thermal_nonlin_opts, thermal_lin_opts, *mesh_)); + auto coupled = combineSystems(custom_solver, solid, thermal); + + thermomechanics::TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; + setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + + coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( + [=](smith::tensor) { return 1.0; }); + + solid->setDisplacementBC(mesh_->domain("left")); + thermal->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); + thermal->setTemperatureBC(mesh_->domain("right"), [](auto, auto) { return 0.0; }); + + solid->addTraction(DependsOn<>{}, "right", [=](double, auto X, auto, auto, auto, auto) { + auto traction = 0.0 * X; + traction[0] = -0.005; + return traction; + }); + thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return 0.1; }); + + auto physics = makeDifferentiablePhysics(coupled, "staggered_coupled_physics"); + + FunctionalObjective, H1>> qoi( + "staggered_qoi", mesh_, + spaces({coupled->field_store->getField("displacement"), coupled->field_store->getField("temperature")})); + qoi.addBodyIntegral(DependsOn<0, 1>{}, mesh_->entireBodyName(), [](double, auto, auto U, auto Theta) { + auto u = get(U); + auto theta = get(Theta); + return 0.5 * u[0] * u[0] + 0.05 * theta * theta; + }); + + physics->advanceTimestep(0.5); + auto qoi_fields = std::vector{coupled->field_store->getField("displacement"), + coupled->field_store->getField("temperature")}; + auto obj = smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, + TimeInfo(physics->time(), 0.5, static_cast(physics->cycle()))); + + gretl::set_as_objective(obj); + obj.data_store().back_prop(); + + auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + EXPECT_TRUE(param_sens->Norml2() > 0.0); +} + // Shared buckling-load magnitudes (used by staggered + monolithic buckling tests). static constexpr double kBucklingTraction = 0.015; static constexpr double kBucklingBodyForce = 2.5e-5; @@ -222,7 +299,7 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solid, thermal); - thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); @@ -245,12 +322,12 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = - buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, thermal_fields); + auto solid = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + thermal_fields); auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solver_ptr, solid, thermal); - thermomechanics::GreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); @@ -266,7 +343,6 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) SolidMechanicsOptions solid_opts{.enable_stress_output = true, .output_cauchy_stress = true}; auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); - EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress"))); auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields); @@ -277,7 +353,7 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) constexpr double G = E / (2.0 * (1.0 + nu)); constexpr double K = E / (3.0 * (1.0 - 2.0 * nu)); - sys->setMaterial(solid_mechanics::NeoHookean{.density = 1.0, .K = K, .G = G}, mesh_->entireBodyName()); + sys->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, mesh_->entireBodyName()); sys->setDisplacementBC(mesh_->domain("left")); sys->addTraction("right", [](double, auto X, auto, auto, auto, auto) { @@ -291,7 +367,7 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) ASSERT_FALSE(sys->post_solve_systems.empty()) << "Stress output system should be present"; auto states = physics->getFieldStates(); - size_t stress_idx = field_store_->getFieldIndex("stress_solve_state"); + size_t stress_idx = field_store_->getFieldIndex("stress"); double stress_norm = norm(*states[stress_idx].get()); EXPECT_GT(stress_norm, 1e-8) << "Cauchy stress field should be non-zero after deformation"; } @@ -299,7 +375,6 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) TEST_F(ThermoMechanicsMeshFixture, StressOutputRegistrationDisabledByDefault) { auto solid_fields = registerSolidMechanicsFields(field_store_); - EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress_solve_state"))); EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress"))); auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 144783211c..9842d9b8a4 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -28,6 +28,7 @@ #include "smith/differentiable_numerics/paraview_writer.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/time_info_solid_materials.hpp" namespace smith { @@ -173,7 +174,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) double nu = 0.25; auto K = E / (3.0 * (1.0 - 2.0 * nu)); auto G = E / (2.0 * (1.0 + nu)); - using MaterialType = solid_mechanics::ParameterizedNeoHookeanSolid; + using MaterialType = solid_mechanics::TimeInfoParameterizedNeoHookeanSolid; MaterialType material{.density = 1.0, .K0 = K, .G0 = G}; // Set parameters @@ -274,7 +275,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi double nu = 0.25; auto K = E / (3.0 * (1.0 - 2.0 * nu)); auto G = E / (2.0 * (1.0 + nu)); - system->setMaterial(solid_mechanics::NeoHookean{.density = 1.0, .K = K, .G = G}, mesh->entireBodyName()); + system->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, mesh->entireBodyName()); system->addBodyForce(mesh->entireBodyName(), [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/) { tensor b{}; @@ -311,7 +312,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi EXPECT_NEAR(0.0, vector_error("freefall_displacement_error", 0, u_exact), 1e-12); auto stress_it = std::find_if(states.begin(), states.end(), [](const auto& state) { - return state.get()->name().find("stress_solve_state") != std::string::npos; + return state.get()->name().find("stress") != std::string::npos; }); ASSERT_NE(stress_it, states.end()); double stress_norm = norm(*stress_it->get().get()); @@ -349,7 +350,7 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptrsetMaterial(material, mesh->entireBodyName()); @@ -554,7 +555,7 @@ TEST_F(SolidMechanicsMeshFixture, SensitivitiesComparison) } // Compare initial condition sensitivities - std::vector state_suffixes = {"displacement_solve_state", "displacement", "velocity", "acceleration"}; + std::vector state_suffixes = {"displacement", "velocity", "acceleration"}; for (const auto& suffix : state_suffixes) { std::string nameG = physics_name + "_gretl_" + suffix; std::string nameB = physics_name + "_base_" + suffix; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 219e52ad7d..a97e8ebcf5 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -61,8 +61,9 @@ struct DamageMaterial { double nu = 0.3; double density = 1.0; - template - SMITH_HOST_DEVICE auto operator()(StateType /*state*/, DerivType deriv_u, ISVType isv, Params... /*params*/) const + template + SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, StateType /*state*/, DerivType deriv_u, + GradVType /*grad_v*/, ISVType isv, Params... /*params*/) const { auto epsilon = sym(deriv_u); auto tr_eps = tr(epsilon); @@ -120,8 +121,8 @@ auto buildSystems(const std::shared_ptr& solid_solver, { return std::tuple{buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, internal_variable_fields), - buildInternalVariableSystem( - internal_variable_solver, InternalVariableOptions{}, internal_variable_fields, solid_fields)}; + buildInternalVariableSystem(internal_variable_solver, InternalVariableOptions{}, + internal_variable_fields, solid_fields)}; } template diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 636ff0485f..75239564d8 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -62,9 +62,9 @@ struct ThermalStaticFixture : public testing::Test { // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. // heat_flux is the physical flux (Fourier's law): q = -k * grad(T). // The system negates it to form the weak form integral(k * grad(T) . grad(v)). - thermal_system->setMaterial( - [=](auto /*temperature*/, auto grad_temperature) { return smith::tuple{0.0, -k * grad_temperature}; }, - "entire_body"); + thermal_system->setMaterial([=](const TimeInfo& /*t_info*/, auto /*temperature*/, + auto grad_temperature) { return smith::tuple{0.0, -k * grad_temperature}; }, + "entire_body"); thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/) { auto x = X[0]; @@ -160,15 +160,14 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) // Material uses the parameter field for conductivity thermal_system->setMaterial( - [](auto /*temperature*/, auto grad_temperature, auto k_param) { + [](const TimeInfo& /*t_info*/, auto /*temperature*/, auto grad_temperature, auto k_param) { auto k = get<0>(k_param); return smith::tuple{0.0, -k * grad_temperature}; }, "entire_body"); - // Use DependsOn to specify that the heat source depends only on the temperature states (indices 0,1), - // not on the parameter field - thermal_system->addHeatSource(DependsOn<0, 1>{}, "entire_body", [=](auto /*t*/, auto X, auto /*T*/) { + // DependsOn now indexes only trailing coupling/parameter args, so empty means "state-only". + thermal_system->addHeatSource(DependsOn<>{}, "entire_body", [=](auto /*t*/, auto X, auto /*T*/) { auto x = X[0]; auto y = X[1]; return 2.0 * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index a89689c815..76a6e89abf 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -61,10 +61,10 @@ struct SimpleThermoelasticMaterial { return E * (1.0 - 0.8 * alpha); } - template - SMITH_HOST_DEVICE auto operator()(DT /*dt*/, StateType /*state*/, GradUType grad_u, GradVType /*grad_v*/, - ThetaType theta, GradThetaType grad_theta, AlphaType alpha, + template + SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, StateType /*state*/, GradUType grad_u, + GradVType /*grad_v*/, ThetaType theta, GradThetaType grad_theta, AlphaType alpha, Params... /*params*/) const { auto E_eff = effectiveYoungsModulus(alpha); @@ -77,12 +77,12 @@ struct SimpleThermoelasticMaterial { return smith::tuple{pk, heat_capacity, heat_source, q0}; } - template - SMITH_HOST_DEVICE auto operator()(DT dt, StateType state, GradUType grad_u, GradVType grad_v, ThetaType theta, - GradThetaType grad_theta, Params... params) const + template + SMITH_HOST_DEVICE auto operator()(const TimeInfo& t_info, StateType state, GradUType grad_u, GradVType grad_v, + ThetaType theta, GradThetaType grad_theta, Params... params) const { - return (*this)(dt, state, grad_u, grad_v, theta, grad_theta, 0.0, params...); + return (*this)(t_info, state, grad_u, grad_v, theta, grad_theta, 0.0, params...); } }; @@ -145,18 +145,17 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) auto internal_variable_fields = registerInternalVariableFields(field_store); // Phase 2: build each system. - // Solid receives thermal and alpha coupling. - auto solid = buildSolidMechanicsSystem( - std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, thermal_fields, - internal_variable_fields); + auto solid = buildSolidMechanicsSystem(std::make_shared(solid_block_solver), + SolidMechanicsOptions{}, solid_fields, thermal_fields, + internal_variable_fields); auto thermal = buildThermalSystem(std::make_shared(thermal_block_solver), - ThermalOptions{}, thermal_fields, solid_fields); + ThermalOptions{}, thermal_fields, solid_fields); - auto internal_variables = buildInternalVariableSystem( - std::make_shared(internal_variable_block_solver), InternalVariableOptions{}, - internal_variable_fields, solid_fields); + auto internal_variables = + buildInternalVariableSystem(std::make_shared(internal_variable_block_solver), + InternalVariableOptions{}, internal_variable_fields, solid_fields); // Phase 3: register material integrands. auto material = SimpleThermoelasticMaterial{}; @@ -165,17 +164,15 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) auto alpha_rule = internal_variables->internal_variable_time_rule; solid->solid_weak_form->addBodyIntegral( - mesh_->entireBodyName(), - [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, auto temperature_old, - auto alpha, auto alpha_old) { + mesh_->entireBodyName(), [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, + auto temperature, auto temperature_old, auto alpha, auto alpha_old) { auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, u, u_old, v_old, a_old); auto T = temp_rule->value(t_info, temperature, temperature_old); auto alpha_current = alpha_rule->value(t_info, alpha, alpha_old); SimpleThermoelasticMaterial::State state{}; - auto response = - material(t_info.dt(), state, get(u_current), get(v_current), get(T), - get(T), get(alpha_current)); + auto response = material(t_info, state, get(u_current), get(v_current), get(T), + get(T), get(alpha_current)); auto pk = get<0>(response); return smith::tuple{get(a_current) * material.density, pk}; }); @@ -185,12 +182,10 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { auto [T_current, T_dot] = temp_rule->interpolate(t_info, T, T_old); auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - (void)a_current; SimpleThermoelasticMaterial::State state{}; - auto response = - material(t_info.dt(), state, get(u_current), get(v_current), get(T_current), - get(T_current)); + auto response = material(t_info, state, get(u_current), get(v_current), + get(T_current), get(T_current)); auto C_v = get<1>(response); auto s0 = get<2>(response); auto q0 = get<3>(response); @@ -206,14 +201,12 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // Compressive traction on right face. // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old, alpha_ss, alpha_old) - solid->addTraction( - "right", - [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, auto /*temp_old*/, - auto /*alpha_ss*/, auto /*alpha_old*/) { - auto t = 0.0 * X; - t[0] = -0.005; - return t; - }); + solid->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, + auto /*temp_old*/, auto /*alpha_ss*/, auto /*alpha_old*/) { + auto t = 0.0 * X; + t[0] = -0.005; + return t; + }); // Phase 5: combine and solve. auto combined = combineSystems(solid, thermal, internal_variables); @@ -239,7 +232,7 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) << "Internal-variable solver did not converge"; // Displacement should be non-zero (compressive traction). - auto final_disp = states[field_store->getFieldIndex("displacement_solve_state")].get(); + auto final_disp = states[field_store->getFieldIndex("displacement")].get(); EXPECT_GT(final_disp->Normlinf(), 1e-8) << "Displacement should be non-zero under compressive traction"; // Temperature should be non-zero (heat source applied). diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index c74deda900..62bb256216 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -57,7 +57,7 @@ struct ThermalSystem : public SystemBase { /** * @brief Set the thermal material model for a domain. * - * Material is called as `material(x, temperature, grad_temperature, params...)` and must return + * Material is called as `material(t_info, temperature, grad_temperature, params...)` and must return * `smith::tuple{heat_capacity, heat_flux}`. Consistent with heat_transfer.hpp convention. * * The system forms the residual as: heat_capacity * dT/dt for the source term, and -heat_flux @@ -72,73 +72,76 @@ struct ThermalSystem : public SystemBase { { auto captured_temp_rule = temperature_time_rule; - thermal_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto temperature, auto temperature_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - auto [heat_capacity, heat_flux] = material(get(T_current), get(T_current), params...); - return smith::tuple{heat_capacity * get(T_dot), -heat_flux}; - }); + thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto temperature, auto temperature_old, + auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); + auto [heat_capacity, heat_flux] = material(t_info, get(T_current), get(T_current), params...); + return smith::tuple{heat_capacity * get(T_dot), -heat_flux}; + }); } /** * @brief Add a body heat source to the thermal system (with DependsOn). * @param depends_on Selects which primal and parameter fields the contribution depends on. * @param domain_name The name of the domain where the heat source is applied. - * @param source_function (t, X, T, params...) -> heat_source. + * @param source_function (t_info, X, T, params...) -> heat_source. */ template void addHeatSource(DependsOn depends_on, const std::string& domain_name, HeatSourceType source_function) { + (void)depends_on; auto captured_temp_rule = temperature_time_rule; - thermal_weak_form->addBodySource(depends_on, domain_name, - [=](auto t_info, auto X, auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - return source_function(t_info.time(), X, T, params...); - }); + thermal_weak_form->template addBodySource<0, 1, (2 + active_parameters)...>( + DependsOn<0, 1, (2 + active_parameters)...>{}, domain_name, + [=](auto t_info, auto X, auto temperature, auto temperature_old, auto... params) { + auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + return source_function(t_info, X, T, params...); + }); } /** * @brief Add a body heat source that depends on all state and parameter fields. * @param domain_name The name of the domain where the heat source is applied. - * @param source_function (t, X, T, params...) -> heat_source. + * @param source_function (t_info, X, T, params...) -> heat_source. */ template void addHeatSource(const std::string& domain_name, HeatSourceType source_function) { - addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence<2 + Coupling::num_coupling_fields>{}); + addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence{}); } /** * @brief Add a boundary heat flux to the thermal system (with DependsOn). * @param depends_on Selects which primal and parameter fields the contribution depends on. * @param boundary_name The name of the boundary where the heat flux is applied. - * @param flux_function (t, X, n, T, params...) -> heat_flux. + * @param flux_function (t_info, X, n, T, params...) -> heat_flux. */ template void addHeatFlux(DependsOn depends_on, const std::string& boundary_name, HeatFluxType flux_function) { + (void)depends_on; auto captured_temp_rule = temperature_time_rule; - thermal_weak_form->addBoundaryFlux( - depends_on, boundary_name, + thermal_weak_form->template addBoundaryFlux<0, 1, (2 + active_parameters)...>( + DependsOn<0, 1, (2 + active_parameters)...>{}, boundary_name, [=](auto t_info, auto X, auto n, auto temperature, auto temperature_old, auto... params) { auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - return -flux_function(t_info.time(), X, n, T, params...); + return -flux_function(t_info, X, n, T, params...); }); } /** * @brief Add a boundary heat flux that depends on all state and parameter fields. * @param boundary_name The name of the boundary where the heat flux is applied. - * @param flux_function (t, X, n, T, params...) -> heat_flux. + * @param flux_function (t_info, X, n, T, params...) -> heat_flux. */ template void addHeatFlux(const std::string& boundary_name, HeatFluxType flux_function) { - addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence<2 + Coupling::num_coupling_fields>{}); + addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence{}); } /// Set zero-temperature Dirichlet BC. diff --git a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp index 70280681e6..a407b407d0 100644 --- a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanical_system.hpp @@ -19,26 +19,41 @@ namespace smith { namespace detail { /** - * @brief Dispatch to either coupled-rate or established thermoelastic material signatures. - * - * Supports materials that accept `(dt, state, grad_u, grad_v, theta, grad_theta, params...)` - * and materials that accept `(state, grad_u, theta, grad_theta, params...)`. + * @brief Evaluate coupled thermo-mechanical material using TimeInfo-aware signature. */ -template -auto evaluateCoupledThermoMechanicsMaterial(const MaterialType& material, DT dt, StateType& state, +template +auto evaluateCoupledThermoMechanicsMaterial(const MaterialType& material, const TimeInfo& t_info, StateType& state, const GradUType& grad_u, const GradVType& grad_v, ThetaType theta, const GradThetaType& grad_theta, ParamTypes&&... params) { - if constexpr (requires { - material(dt, state, grad_u, grad_v, theta, grad_theta, std::forward(params)...); - }) { - return material(dt, state, grad_u, grad_v, theta, grad_theta, std::forward(params)...); - } else { - return material(state, grad_u, theta, grad_theta, std::forward(params)...); - } + return material(t_info, state, grad_u, grad_v, theta, grad_theta, std::forward(params)...); } +template +/// @brief Adapts coupled thermo-mechanical material to solid-system material interface. +struct CoupledSolidThermoMechanicsMaterialAdapter { + /// Material state type forwarded to solid system. + using State = typename MaterialType::State; + + MaterialType material; ///< Wrapped thermo-mechanical material. + TemperatureRulePtr temperature_time_rule; ///< Time rule used to recover current temperature value. + double density; ///< Material density exposed for solid residual. + + template + /// @brief Evaluate wrapped material and return solid PK1 contribution. + auto operator()(const TimeInfo& t_info, StateType& state, GradUType grad_u, GradVType grad_v, + TemperatureType temperature, TemperatureOldType temperature_old, ParamTypes&&... params) const + { + auto T = temperature_time_rule->value(t_info, temperature, temperature_old); + auto [pk, C_v, s0, q0] = + evaluateCoupledThermoMechanicsMaterial(material, t_info, state, grad_u, grad_v, get(T), + get(T), std::forward(params)...); + return pk; + } +}; + } // namespace detail /** @@ -51,13 +66,8 @@ auto evaluateCoupledThermoMechanicsMaterial(const MaterialType& material, DT dt, * - thermal was built with solid displacement fields as the leading coupling fields * (first 4 coupling positions: displacement_solve_state, displacement, velocity, acceleration). * - * The solid integrand lambda receives: - * (t_info, X, u, u_old, v_old, a_old, temperature_ss, temperature_old, ...params) - * The thermal integrand lambda receives: - * (t_info, X, T, T_old, disp_ss, displacement, velocity, acceleration, ...params) - * * The material callable must satisfy: - * material(dt, state, grad_u, grad_v, T_value, grad_T, params...) -> tuple{PK1, C_v, s0, q0} + * material(t_info, state, grad_u, grad_v, T_value, grad_T, params...) -> tuple{PK1, C_v, s0, q0} * * @tparam dim Spatial dimension (deduced from SolidMechanicsSystem template arg). * @tparam disp_order_ Displacement polynomial order. @@ -82,45 +92,32 @@ void setCoupledThermoMechanicsMaterial( auto captured_disp_rule = solid->disp_time_rule; auto captured_temp_rule = thermal->temperature_time_rule; - // Solid contribution: inertia + PK1 stress - solid->solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, - auto temperature_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); + auto solid_material = detail::CoupledSolidThermoMechanicsMaterialAdapter{ + material, captured_temp_rule, material.density}; + + solid->setMaterial(solid_material, domain_name); + thermal->setMaterial( + [=](const TimeInfo& t_info, auto temperature, auto grad_temperature, auto disp, auto disp_old, auto v_old, + auto a_old, auto... params) { + auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); typename MaterialType::State state{}; auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( - material, t_info.dt(), state, get(u_current), get(v_current), get(T), - get(T), params...); - return smith::tuple{get(a_current) * material.density, pk}; - }); - - // Cycle-zero: (u, v, a, temperature, temperature_old, ...params) - if (solid->cycle_zero_solid_weak_form) { - solid->cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto u, auto v, auto a, auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - typename MaterialType::State state{}; - auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( - material, t_info.dt(), state, get(u), get(v), get(T), get(T), - params...); - return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk); - }); - } + material, t_info, state, get(u), get(v), temperature, grad_temperature, params...); + return smith::tuple{C_v, q0}; + }, + domain_name); - // Thermal contribution: (T, T_old, disp_ss, displacement, velocity, acceleration, ...params) - thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, - auto disp_old, auto v_old, auto a_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, T, T_old); + thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](const TimeInfo& t_info, auto /*X*/, auto temperature, + auto temperature_old, auto disp, auto disp_old, + auto v_old, auto a_old, auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - typename MaterialType::State state{}; - auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( - material, t_info.dt(), state, get(u), get(v), get(T_current), - get(T_current), params...); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; + auto [pk, C_v, s0, q0] = + detail::evaluateCoupledThermoMechanicsMaterial(material, t_info, state, get(u), get(v), + get(T_current), get(T_current), params...); + return smith::tuple{s0, smith::zero{}}; }); } diff --git a/src/smith/differentiable_numerics/time_info_solid_materials.hpp b/src/smith/differentiable_numerics/time_info_solid_materials.hpp new file mode 100644 index 0000000000..a4a3f048cd --- /dev/null +++ b/src/smith/differentiable_numerics/time_info_solid_materials.hpp @@ -0,0 +1,52 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + +#include "smith/physics/common.hpp" +#include "smith/physics/materials/parameterized_solid_material.hpp" +#include "smith/physics/materials/solid_material.hpp" + +namespace smith::solid_mechanics { + +/// @brief TimeInfo-aware wrapper for `NeoHookean`. +struct TimeInfoNeoHookean { + /// State type reused from wrapped material. + using State = NeoHookean::State; + + double density; ///< Mass density. + double K; ///< Bulk modulus. + double G; ///< Shear modulus. + + template + /// @brief Evaluate wrapped material, ignoring velocity gradient. + SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, State& state, const tensor& grad_u, + const GradVType& /*grad_v*/) const + { + return NeoHookean{.density = density, .K = K, .G = G}(state, grad_u); + } +}; + +/// @brief TimeInfo-aware wrapper for `ParameterizedNeoHookeanSolid`. +struct TimeInfoParameterizedNeoHookeanSolid { + /// State type reused from wrapped material. + using State = ParameterizedNeoHookeanSolid::State; + + double density; ///< Mass density. + double K0; ///< Base bulk modulus. + double G0; ///< Base shear modulus. + + template + /// @brief Evaluate wrapped material, ignoring velocity gradient. + SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, State& state, + const smith::tensor& grad_u, const GradVType& /*grad_v*/, + const BulkType& delta_k, const ShearType& delta_g) const + { + return ParameterizedNeoHookeanSolid{.density = density, .K0 = K0, .G0 = G0}(state, grad_u, delta_k, delta_g); + } +}; + +} // namespace smith::solid_mechanics diff --git a/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp b/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp new file mode 100644 index 0000000000..846efa7d43 --- /dev/null +++ b/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp @@ -0,0 +1,61 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + +#include "smith/physics/common.hpp" +#include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" + +namespace smith::thermomechanics { + +/// @brief TimeInfo-aware wrapper for `GreenSaintVenantThermoelasticMaterial`. +struct TimeInfoGreenSaintVenantThermoelasticMaterial { + /// State type reused from wrapped material. + using State = GreenSaintVenantThermoelasticMaterial::State; + + double density; ///< Mass density. + double E; ///< Young's modulus. + double nu; ///< Poisson ratio. + double C_v; ///< Heat capacity. + double alpha; ///< Thermal expansion coefficient. + double theta_ref; ///< Reference temperature. + double kappa; ///< Thermal conductivity. + + template + /// @brief Evaluate wrapped material, ignoring velocity gradient. + auto operator()(const TimeInfo& /*t_info*/, State& state, const tensor& grad_u, + const GradVType& /*grad_v*/, T2 theta, const tensor& grad_theta) const + { + return GreenSaintVenantThermoelasticMaterial{density, E, nu, C_v, alpha, theta_ref, kappa}(state, grad_u, theta, + grad_theta); + } +}; + +/// @brief TimeInfo-aware wrapper for `ParameterizedGreenSaintVenantThermoelasticMaterial`. +struct TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial { + /// State type reused from wrapped material. + using State = ParameterizedGreenSaintVenantThermoelasticMaterial::State; + + double density; ///< Mass density. + double E; ///< Young's modulus. + double nu; ///< Poisson ratio. + double C_v; ///< Heat capacity. + double alpha0; ///< Reference thermal expansion coefficient. + double theta_ref; ///< Reference temperature. + double kappa; ///< Thermal conductivity. + + template + /// @brief Evaluate wrapped material, ignoring velocity gradient. + auto operator()(const TimeInfo& /*t_info*/, State& state, const tensor& grad_u, + const GradVType& /*grad_v*/, T2 theta, const tensor& grad_theta, + T4 thermal_expansion_scaling) const + { + return ParameterizedGreenSaintVenantThermoelasticMaterial{density, E, nu, C_v, alpha0, theta_ref, kappa}( + state, grad_u, theta, grad_theta, thermal_expansion_scaling); + } +}; + +} // namespace smith::thermomechanics From bb01110b0eababae8a36ff7212a2b92ba3a5f473 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 23 Apr 2026 19:01:38 -0700 Subject: [PATCH 40/67] Improve examples, more simple syntax. --- .../composable_solid_mechanics.cpp | 48 +++++++-------- .../composable_thermo_mechanics.cpp | 13 ++-- .../composable_thermo_mechanics_advanced.cpp | 12 ++-- .../differentiable_physics.hpp | 12 ++-- .../differentiable_numerics/field_store.cpp | 61 +++++++++++++------ .../differentiable_numerics/field_store.hpp | 14 +++-- .../solid_mechanics_system.hpp | 21 +++++-- .../tests/test_solid_dynamics.cpp | 52 ++++++---------- .../tests/test_thermal_static.cpp | 15 ++--- .../thermal_system.hpp | 23 +++++-- 10 files changed, 147 insertions(+), 124 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 6bb945d316..48a170f17e 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -62,9 +62,7 @@ struct TimeInfoYoungsModulusNeoHookean { std::vector outputFields(const smith::FieldStore& field_store) { - return {field_store.getField(field_store.prefix("displacement")), - field_store.getField(field_store.prefix("velocity")), - field_store.getField(field_store.prefix("acceleration")), field_store.getField(field_store.prefix("stress"))}; + return field_store.getOutputFieldStates(); } std::vector qoiFields(const std::vector& states) @@ -109,20 +107,15 @@ int main(int argc, char* argv[]) .print_level = 0}; auto field_store = std::make_shared(mesh, 100); - using DispRule = smith::ImplicitNewmarkSecondOrderTimeIntegrationRule; - using DispSpace = smith::H1; - smith::SolidMechanicsOptions solid_options{.enable_stress_output = true, .output_cauchy_stress = true}; + smith::SolidMechanicsOptions output_options{.enable_stress_output = true, .output_cauchy_stress = true}; auto param_fields = smith::registerParameterFields(smith::FieldType>("youngs_modulus")); - auto solid_fields = smith::registerSolidMechanicsFields(field_store, solid_options); // _solver_end // _build_start - auto solid_solver = - std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); - auto solid_system = - smith::buildSolidMechanicsSystem(solid_solver, solid_options, solid_fields, param_fields); + smith::buildSolidMechanicsSystem( + nonlinear_options, linear_options, output_options, field_store, param_fields); constexpr double E = 100.0; constexpr double nu = 0.25; @@ -140,14 +133,12 @@ int main(int argc, char* argv[]) return velocity; }; - field_store->getField(field_store->prefix("displacement_solve_state")) - .get() - ->setFromFieldFunction(initial_displacement); - field_store->getField(field_store->prefix("displacement")).get()->setFromFieldFunction(initial_displacement); - field_store->getField(field_store->prefix("velocity")).get()->setFromFieldFunction(initial_velocity); - field_store->getField(field_store->prefix("acceleration")) - .get() - ->setFromFieldFunction([](smith::tensor) { return smith::tensor{}; }); + field_store->getField("displacement_solve_state").get()->setFromFieldFunction(initial_displacement); + field_store->getField("displacement").get()->setFromFieldFunction(initial_displacement); + field_store->getField("velocity").get()->setFromFieldFunction(initial_velocity); + field_store->getField("acceleration").get()->setFromFieldFunction([](smith::tensor) { + return smith::tensor{}; + }); // _build_end // _bc_start @@ -162,6 +153,10 @@ int main(int argc, char* argv[]) traction[0] = -0.01; return traction; }); + + auto output_states = outputFields(*field_store); + auto writer = smith::createParaviewWriter(*mesh, output_states, "paraview_composable_solid_mechanics", + smith::ParaviewWriter::Options{.write_duals = false}); // _bc_end // _run_start @@ -171,6 +166,7 @@ int main(int argc, char* argv[]) auto physics = smith::makeDifferentiablePhysics(solid_system, "composable_solid_mechanics"); auto initial_states = physics->getInitialFieldStates(); + using DispSpace = smith::H1; smith::FunctionalObjective> qoi( "solid_dynamic_energy_proxy", mesh, smith::spaces(qoiFields(initial_states))); qoi.addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [](double, auto, auto U, auto V) { @@ -203,18 +199,16 @@ int main(int argc, char* argv[]) std::cout << "dQoI/d(youngs_modulus) norm: " << youngs_modulus_state.get_dual()->Norml2() << '\n'; std::cout << "dQoI/d(initial displacement) norm: " << initial_displacement_state.get_dual()->Norml2() << '\n'; std::cout << "dQoI/d(initial velocity) norm: " << initial_velocity_state.get_dual()->Norml2() << '\n'; - std::cout << "shape FD rate: " << smith::checkGradWrt(qoi_state, shape_displacement, 1.0e-2, 4, false) << '\n'; - std::cout << "youngs_modulus FD rate: " << smith::checkGradWrt(qoi_state, youngs_modulus_state, 5.0e-2, 4, false) - << '\n'; - std::cout << "initial displacement FD rate: " + std::cout << "shape FD rate: \n" << smith::checkGradWrt(qoi_state, shape_displacement, 1.0e-2, 4, false) << '\n'; + std::cout << "youngs_modulus FD rate: \n" + << smith::checkGradWrt(qoi_state, youngs_modulus_state, 5.0e-2, 4, false) << '\n'; + std::cout << "initial displacement FD rate: \n" << smith::checkGradWrt(qoi_state, initial_displacement_state, 5.0e-3, 4, false) << '\n'; - std::cout << "initial velocity FD rate: " << smith::checkGradWrt(qoi_state, initial_velocity_state, 5.0e-3, 4, false) - << '\n'; + std::cout << "initial velocity FD rate: \n" + << smith::checkGradWrt(qoi_state, initial_velocity_state, 5.0e-3, 4, false) << '\n'; // _run_end // _output_start - auto writer = smith::createParaviewWriter(*mesh, outputFields(*field_store), "paraview_composable_solid_mechanics", - smith::ParaviewWriter::Options{.write_duals = false}); writer.write(physics->cycle(), physics->time(), outputFields(*field_store)); std::cout << "ParaView output: paraview_composable_solid_mechanics\n"; // _output_end diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index 21bd3c95a7..56d8c8ba67 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -60,11 +60,10 @@ int main(int argc, char* argv[]) auto field_store = std::make_shared(mesh, 100); - using DispRule = smith::QuasiStaticSecondOrderTimeIntegrationRule; - using TempRule = smith::BackwardEulerFirstOrderTimeIntegrationRule; - - auto solid_fields = smith::registerSolidMechanicsFields(field_store); - auto thermal_fields = smith::registerThermalFields(field_store); + auto solid_fields = + smith::registerSolidMechanicsFields(field_store); + auto thermal_fields = + smith::registerThermalFields(field_store); // _solver_end // _build_start @@ -78,11 +77,11 @@ int main(int argc, char* argv[]) auto thermal_system = smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, solid_fields); + auto coupled_system = smith::combineSystems(solid_system, thermal_system); + smith::thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); - - auto coupled_system = smith::combineSystems(solid_system, thermal_system); // _build_end // _bc_start diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 8989e8f65d..d01609ed8c 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -39,8 +39,7 @@ namespace { std::vector outputFields(const smith::FieldStore& field_store) { - return {field_store.getField(field_store.prefix("displacement")), - field_store.getField(field_store.prefix("temperature")), field_store.getField(field_store.prefix("stress"))}; + return field_store.getOutputFieldStates(); } std::vector qoiFields(const smith::FieldStore& field_store) @@ -141,7 +140,11 @@ int main(int argc, char* argv[]) auto coupled_system = smith::combineSystems(custom_solver, solid_system, thermal_system); std::string physics_name = "composable_thermo_mechanics_advanced"; - auto physics = smith::makeDifferentiablePhysics(coupled_system, physics_name, {}); + auto physics = smith::makeDifferentiablePhysics(coupled_system, physics_name); + auto output_states = outputFields(*field_store); + auto output_writer = + smith::createParaviewWriter(*mesh, output_states, "paraview_composable_thermo_mechanics_advanced", + smith::ParaviewWriter::Options{.write_duals = false}); // _build_end // _qoi_start @@ -170,9 +173,6 @@ int main(int argc, char* argv[]) // _run_end // _output_start - auto output_writer = - smith::createParaviewWriter(*mesh, outputFields(*field_store), "paraview_composable_thermo_mechanics_advanced", - smith::ParaviewWriter::Options{.write_duals = false}); output_writer.write(physics->cycle(), physics->time(), outputFields(*field_store)); std::cout << "ParaView output: paraview_composable_thermo_mechanics_advanced\n"; diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index 4148ead019..464d444ef3 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -220,18 +220,16 @@ template /** * @brief Build a `DifferentiablePhysics` and default multiphysics advancer from a system. * - * If optional cycle-zero or post-solve systems are omitted, values stored on `system` are used. + * Uses cycle-zero and post-solve systems already attached to `system`. * * @param system Main system to wrap. * @param physics_name Name exposed through the `BasePhysics` interface. - * @param post_solve_systems Optional systems solved after each main step. */ -std::unique_ptr makeDifferentiablePhysics( - std::shared_ptr system, const std::string& physics_name, - std::vector> post_solve_systems = {}) +std::unique_ptr makeDifferentiablePhysics(std::shared_ptr system, + const std::string& physics_name) { - return makeDifferentiablePhysics( - system, makeAdvancer(system, system->cycle_zero_system, std::move(post_solve_systems)), physics_name); + return makeDifferentiablePhysics(system, makeAdvancer(system, system->cycle_zero_system, system->post_solve_systems), + physics_name); } } // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index f9b18a69aa..a392bd11f0 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -160,19 +160,21 @@ std::vector FieldStore::getBoundaryConditionMan bool FieldStore::hasField(const std::string& field_name) const { - if (to_states_index_.count(field_name)) return true; - if (to_params_index_.count(field_name)) return true; - if (!shape_disp_.empty() && shape_disp_[0].get()->name() == field_name) return true; + const auto resolved_name = resolveFieldName(field_name); + if (to_states_index_.count(resolved_name)) return true; + if (to_params_index_.count(resolved_name)) return true; + if (!shape_disp_.empty() && shape_disp_[0].get()->name() == resolved_name) return true; return false; } size_t FieldStore::getFieldIndex(const std::string& field_name) const { - if (to_states_index_.count(field_name)) { - return to_states_index_.at(field_name); + const auto resolved_name = resolveFieldName(field_name); + if (to_states_index_.count(resolved_name)) { + return to_states_index_.at(resolved_name); } - if (to_params_index_.count(field_name)) { - return to_params_index_.at(field_name); + if (to_params_index_.count(resolved_name)) { + return to_params_index_.at(resolved_name); } SLIC_ERROR("Field or parameter '" << field_name << "' not found in getFieldIndex"); return 0; // unreachable @@ -180,14 +182,15 @@ size_t FieldStore::getFieldIndex(const std::string& field_name) const FieldState FieldStore::getField(const std::string& field_name) const { + const auto resolved_name = resolveFieldName(field_name); // Check if it's a state field - if (to_states_index_.count(field_name)) { - size_t field_index = to_states_index_.at(field_name); + if (to_states_index_.count(resolved_name)) { + size_t field_index = to_states_index_.at(resolved_name); return states_[field_index]; } // Otherwise check if it's a parameter - if (to_params_index_.count(field_name)) { - size_t param_index = to_params_index_.at(field_name); + if (to_params_index_.count(resolved_name)) { + size_t param_index = to_params_index_.at(resolved_name); return params_[param_index]; } SLIC_ERROR("Field or parameter '" << field_name << "' not found"); @@ -196,27 +199,51 @@ FieldState FieldStore::getField(const std::string& field_name) const FieldState FieldStore::getParameter(const std::string& param_name) const { - size_t param_index = to_params_index_.at(param_name); + const auto resolved_name = resolveFieldName(param_name); + size_t param_index = to_params_index_.at(resolved_name); return params_[param_index]; } void FieldStore::setField(const std::string& field_name, FieldState updated_field) { - if (to_states_index_.count(field_name)) { - states_[to_states_index_.at(field_name)] = updated_field; + const auto resolved_name = resolveFieldName(field_name); + if (to_states_index_.count(resolved_name)) { + states_[to_states_index_.at(resolved_name)] = updated_field; return; } - if (to_params_index_.count(field_name)) { - params_[to_params_index_.at(field_name)] = updated_field; + if (to_params_index_.count(resolved_name)) { + params_[to_params_index_.at(resolved_name)] = updated_field; return; } - if (!shape_disp_.empty() && shape_disp_[0].get()->name() == field_name) { + if (!shape_disp_.empty() && shape_disp_[0].get()->name() == resolved_name) { shape_disp_[0] = updated_field; return; } SLIC_ERROR("Field '" << field_name << "' not found in setField"); } +std::string FieldStore::resolveFieldName(const std::string& field_name) const +{ + if (to_states_index_.count(field_name) || to_params_index_.count(field_name)) { + return field_name; + } + if (!shape_disp_.empty() && shape_disp_[0].get()->name() == field_name) { + return field_name; + } + + const auto prefixed_name = prefix(field_name); + if (prefixed_name != field_name) { + if (to_states_index_.count(prefixed_name) || to_params_index_.count(prefixed_name)) { + return prefixed_name; + } + if (!shape_disp_.empty() && shape_disp_[0].get()->name() == prefixed_name) { + return prefixed_name; + } + } + + return field_name; +} + FieldState FieldStore::getShapeDisp() const { return shape_disp_[0]; } const std::vector& FieldStore::getAllFields() const { return states_; } diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 762b016928..fd8757dd7c 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -364,34 +364,36 @@ struct FieldStore { void shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc); /** - * @brief Check whether a field with the given fully-qualified name exists. + * @brief Check whether a field exists. + * + * Accepts either a fully-qualified field name or an unprefixed base name. */ bool hasField(const std::string& field_name) const; /** * @brief Get the internal index of a field by name. - * @param field_name Name of the field. + * @param field_name Fully-qualified or unprefixed field name. * @return size_t Index of the field. */ size_t getFieldIndex(const std::string& field_name) const; /** * @brief Get a FieldState by name. - * @param field_name Name of the field. + * @param field_name Fully-qualified or unprefixed field name. * @return FieldState The field state. */ FieldState getField(const std::string& field_name) const; /** * @brief Get a parameter field by name. - * @param param_name Name of the parameter. + * @param param_name Fully-qualified or unprefixed parameter name. * @return FieldState The parameter field state. */ FieldState getParameter(const std::string& param_name) const; /** * @brief Update a field in the store by name. - * @param field_name Name of the field. + * @param field_name Fully-qualified or unprefixed field name. * @param updated_field The new field state. */ void setField(const std::string& field_name, FieldState updated_field); @@ -475,6 +477,8 @@ struct FieldStore { const std::shared_ptr& graph() const; private: + std::string resolveFieldName(const std::string& field_name) const; + std::shared_ptr mesh_; std::shared_ptr graph_; std::string prepend_name_; diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index c24a8b7115..097252bae8 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -572,16 +572,27 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid } /** - * @brief Build a SolidMechanicsSystem from variadic field packs. + * @brief Build a SolidMechanicsSystem from solver options and a FieldStore. * - * New API: accepts any combination of PhysicsFields and CouplingParams packs. - * The FieldStore is extracted from the PhysicsFields pack matching DisplacementTimeRule. - * Non-self packs become coupling fields; CouplingParams packs are registered as parameters. + * Registers the solid field pack, builds a nonlinear block solver from the supplied options, + * then forwards to the existing field-pack overload. * * Usage: * @code * auto solid = buildSolidMechanicsSystem( - * solver, opts, solid_fields, param_fields, thermal_fields); + * nonlin_opts, lin_opts, field_store, opts, param_fields, thermal_fields); * @endcode */ +template + requires(detail::is_coupling_params_v && ...) +auto buildSolidMechanicsSystem(const NonlinearSolverOptions& nonlinear_options, + const LinearSolverOptions& linear_options, const SolidMechanicsOptions& options, + std::shared_ptr field_store, const OtherPacks&... other_packs) +{ + auto self_fields = registerSolidMechanicsFields(field_store, options); + auto solver = std::make_shared( + buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); + return buildSolidMechanicsSystem(solver, options, self_fields, other_packs...); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 9842d9b8a4..3929eebdbc 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -101,21 +101,18 @@ struct SolidMechanicsMeshFixture : public testing::Test { // produce a non-null cycle_zero_system; rules that don't (QuasiStatic) produce nullptr. TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { - auto solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - { auto field_store = std::make_shared(mesh, 100, "impl"); using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - auto solid_fields = registerSolidMechanicsFields(field_store); - auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto sys = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + SolidMechanicsOptions{}, field_store); EXPECT_NE(sys->cycle_zero_system, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { auto field_store = std::make_shared(mesh, 100, "qs"); using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; - auto solid_fields = registerSolidMechanicsFields(field_store); - auto sys = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto sys = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + SolidMechanicsOptions{}, field_store); EXPECT_EQ(sys->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; } } @@ -154,19 +151,14 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; - auto solid_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - - auto coupled_solver = std::make_shared(solid_block_solver); auto field_store = std::make_shared(mesh, 100, ""); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto solid_fields = registerSolidMechanicsFields( - field_store, SolidMechanicsOptions{.enable_stress_output = true}); - - auto system = buildSolidMechanicsSystem( - coupled_solver, SolidMechanicsOptions{.enable_stress_output = true}, solid_fields, param_fields); + auto system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + SolidMechanicsOptions{.enable_stress_output = true}, + field_store, param_fields); static constexpr double gravity = -9.0; @@ -201,8 +193,9 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto output_states = system->field_store->getOutputFieldStates(); std::string pv_dir = "paraview_solid"; - auto pv_writer = createParaviewWriter(*mesh, {output_states[0], params[0], params[1]}, pv_dir); - pv_writer.write(0, 0.0, {output_states[0], params[0], params[1]}); + auto pv_fields = std::vector{output_states[0], params[0], params[1]}; + auto pv_writer = createParaviewWriter(*mesh, pv_fields, pv_dir); + pv_writer.write(0, 0.0, pv_fields); double time = 0.0; size_t cycle = 0; @@ -214,9 +207,10 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) TimeInfo t_info(time, dt_, cycle); std::tie(states, reactions) = advancer->advanceState(t_info, shape_disp, states, params); output_states = system->field_store->getOutputFieldStates(); + pv_fields = {output_states[0], params[0], params[1]}; time += dt_; cycle++; - pv_writer.write(m + 1, time, {output_states[0], params[0], params[1]}); + pv_writer.write(m + 1, time, pv_fields); } double a_exact = gravity; @@ -261,14 +255,12 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditions) { - auto solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); auto field_store = std::make_shared(mesh, 100, "freefall_"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; SolidMechanicsOptions solid_options{.enable_stress_output = true}; - auto solid_fields = registerSolidMechanicsFields(field_store, solid_options); - auto system = buildSolidMechanicsSystem(solver, solid_options, solid_fields); + auto system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + solid_options, field_store); static constexpr double gravity = -9.0; double E = 100.0; @@ -298,13 +290,13 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi double u_exact = 0.5 * gravity * time * time; auto vector_error = [&](const std::string& name, size_t state_index, double y_exact) { - FunctionalObjective> error(name, mesh, spaces({states[state_index]})); + auto state_vec = std::vector{states[state_index]}; + FunctionalObjective> error(name, mesh, spaces(state_vec)); error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [y_exact](auto /*t*/, auto /*X*/, auto U) { auto u = get(U); return u[0] * u[0] + (u[1] - y_exact) * (u[1] - y_exact) + u[2] * u[2]; }); - return error.evaluate(TimeInfo(0.0, dt_, num_steps_), shape_disp.get().get(), - getConstFieldPointers({states[state_index]})); + return error.evaluate(TimeInfo(0.0, dt_, num_steps_), shape_disp.get().get(), getConstFieldPointers(state_vec)); }; EXPECT_NEAR(0.0, vector_error("freefall_acceleration_error", 3, a_exact), 1e-12); @@ -321,19 +313,13 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr mesh) { - std::shared_ptr solid_block_solver = - buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); - - auto coupled_solver = std::make_shared(solid_block_solver); auto field_store = std::make_shared(mesh, 100, physics_name); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto solid_fields = registerSolidMechanicsFields(field_store); - - auto system = - buildSolidMechanicsSystem(coupled_solver, SolidMechanicsOptions{}, solid_fields, param_fields); + auto system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + SolidMechanicsOptions{}, field_store, param_fields); auto physics = makeDifferentiablePhysics(system, physics_name); auto bcs = system->disp_bc; diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 75239564d8..9aa05e64fa 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -48,15 +48,11 @@ struct ThermalStaticFixture : public testing::Test { auto solver_options = NonlinearSolverOptions(); solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); - auto nonlinear_block_solver = buildNonlinearBlockSolver(solver_options, linear_options, *mesh); - - auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, ""); using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; - auto thermal_fields = registerThermalFields<2, temp_order, TempRule>(field_store); - - auto thermal_system = buildThermalSystem<2, temp_order>(coupled_solver, ThermalOptions{}, thermal_fields); + auto thermal_system = + buildThermalSystem<2, temp_order, TempRule>(solver_options, linear_options, ThermalOptions{}, field_store); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -142,17 +138,14 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto solver_options = NonlinearSolverOptions(); solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); - auto nonlinear_block_solver = buildNonlinearBlockSolver(solver_options, linear_options, *mesh); - auto coupled_solver = std::make_shared(nonlinear_block_solver); FieldType> conductivity_param("conductivity"); auto field_store = std::make_shared(mesh, 100, ""); using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; auto param_fields = registerParameterFields(conductivity_param); - auto thermal_fields = registerThermalFields<2, 1, TempRule>(field_store); - - auto thermal_system = buildThermalSystem<2, 1>(coupled_solver, ThermalOptions{}, thermal_fields, param_fields); + auto thermal_system = + buildThermalSystem<2, 1, TempRule>(solver_options, linear_options, ThermalOptions{}, field_store, param_fields); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 62bb256216..2c6d9e8d2a 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -286,16 +286,27 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio } /** - * @brief Build a ThermalSystem from variadic field packs. + * @brief Build a ThermalSystem from solver options and a FieldStore. * - * New API: accepts any combination of PhysicsFields and CouplingParams packs. - * The FieldStore is extracted from the PhysicsFields pack matching TemperatureTimeRule. - * Non-self packs become coupling fields; CouplingParams packs are registered as parameters. + * Registers the thermal field pack, builds a nonlinear block solver from the supplied options, + * then forwards to the existing field-pack overload. * * Usage: * @code - * auto thermal = buildThermalSystem( - * solver, opts, thermal_fields, param_fields, solid_fields); + * auto thermal = buildThermalSystem( + * nonlin_opts, lin_opts, field_store, opts, param_fields, solid_fields); * @endcode */ +template + requires(detail::is_coupling_params_v && ...) +auto buildThermalSystem(const NonlinearSolverOptions& nonlinear_options, const LinearSolverOptions& linear_options, + const ThermalOptions& options, std::shared_ptr field_store, + const OtherPacks&... other_packs) +{ + auto self_fields = registerThermalFields(field_store, options); + auto solver = std::make_shared( + buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); + return buildThermalSystem(solver, options, self_fields, other_packs...); +} + } // namespace smith From 5ec942b5623efea780a4a6551a090a0cb565da59 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 24 Apr 2026 12:12:35 -0700 Subject: [PATCH 41/67] Working through some improvements. --- examples/contact/homotopy/two_blocks.cpp | 6 +- .../inertia_relief/inertia_relief_example.cpp | 6 +- .../composable_solid_mechanics.cpp | 56 ++- .../composable_thermo_mechanics.cpp | 4 +- .../composable_thermo_mechanics_advanced.cpp | 64 +--- improvements.md | 58 +++ .../differentiable_numerics/CMakeLists.txt | 1 - .../combined_system.hpp | 4 +- .../differentiable_numerics/field_store.hpp | 6 +- .../multiphysics_time_integrator.hpp | 1 - .../solid_mechanics_system.hpp | 12 +- .../state_variable_system.hpp | 7 +- .../differentiable_numerics/system_solver.cpp | 4 +- .../differentiable_numerics/system_solver.hpp | 5 +- .../tests/test_combined_thermo_mechanics.cpp | 20 +- .../tests/test_explicit_dynamics.cpp | 4 +- .../test_multiphysics_time_integrator.cpp | 28 +- .../thermal_system.hpp | 4 +- .../time_discretized_weak_form.hpp | 356 ------------------ .../time_info_thermo_mechanical_materials.hpp | 4 +- src/smith/physics/functional_objective.hpp | 33 +- src/smith/physics/functional_weak_form.hpp | 138 +++++-- .../tests/test_functional_weak_form.cpp | 6 +- .../tests/test_kinematic_objective.cpp | 8 +- 24 files changed, 313 insertions(+), 522 deletions(-) create mode 100644 improvements.md delete mode 100644 src/smith/differentiable_numerics/time_discretized_weak_form.hpp diff --git a/examples/contact/homotopy/two_blocks.cpp b/examples/contact/homotopy/two_blocks.cpp index a2c55a7bb0..28e8d7f1e8 100644 --- a/examples/contact/homotopy/two_blocks.cpp +++ b/examples/contact/homotopy/two_blocks.cpp @@ -30,9 +30,9 @@ using DensitySpace = smith::L2; using SolidMaterial = smith::solid_mechanics::NeoHookeanWithFieldDensity; using SolidWeakFormT = - smith::TimeDiscretizedWeakForm, - smith::Parameters, smith::H1, - smith::H1, DensitySpace>>; + smith::FunctionalWeakForm, + smith::Parameters, smith::H1, + smith::H1, DensitySpace>>; enum FIELD { diff --git a/examples/inertia_relief/inertia_relief_example.cpp b/examples/inertia_relief/inertia_relief_example.cpp index 90cac8f221..c9676a897c 100644 --- a/examples/inertia_relief/inertia_relief_example.cpp +++ b/examples/inertia_relief/inertia_relief_example.cpp @@ -38,9 +38,9 @@ using DensitySpace = smith::L2; using SolidMaterial = smith::solid_mechanics::NeoHookeanWithFieldDensity; using SolidWeakFormT = - smith::TimeDiscretizedWeakForm, - smith::Parameters, smith::H1, - smith::H1, DensitySpace>>; + smith::FunctionalWeakForm, + smith::Parameters, smith::H1, + smith::H1, DensitySpace>>; enum FIELD { diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 48a170f17e..4e105c50e2 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -32,11 +32,7 @@ namespace { -constexpr size_t displacement_state_index = 0; -constexpr size_t initial_displacement_state_index = 1; -constexpr size_t velocity_state_index = 2; - -struct TimeInfoYoungsModulusNeoHookean { +struct YoungsModulusNeoHookeanWithTimeInfo { using State = smith::solid_mechanics::NeoHookean::State; double density; @@ -60,19 +56,6 @@ struct TimeInfoYoungsModulusNeoHookean { } }; -std::vector outputFields(const smith::FieldStore& field_store) -{ - return field_store.getOutputFieldStates(); -} - -std::vector qoiFields(const std::vector& states) -{ - if (states.size() <= velocity_state_index) { - throw std::runtime_error("Unexpected solid state layout."); - } - return {states[displacement_state_index], states[velocity_state_index]}; -} - } // namespace int main(int argc, char* argv[]) @@ -119,7 +102,7 @@ int main(int argc, char* argv[]) constexpr double E = 100.0; constexpr double nu = 0.25; - solid_system->setMaterial(TimeInfoYoungsModulusNeoHookean{.density = 1.0, .nu = nu}, mesh->entireBodyName()); + solid_system->setMaterial(YoungsModulusNeoHookeanWithTimeInfo{.density = 1.0, .nu = nu}, mesh->entireBodyName()); field_store->getParameterFields()[0].get()->setFromFieldFunction([=](smith::tensor) { return E; }); auto initial_displacement = [](smith::tensor X) { @@ -154,7 +137,7 @@ int main(int argc, char* argv[]) return traction; }); - auto output_states = outputFields(*field_store); + auto output_states = field_store->getOutputFieldStates(); auto writer = smith::createParaviewWriter(*mesh, output_states, "paraview_composable_solid_mechanics", smith::ParaviewWriter::Options{.write_duals = false}); // _bc_end @@ -167,23 +150,32 @@ int main(int argc, char* argv[]) auto physics = smith::makeDifferentiablePhysics(solid_system, "composable_solid_mechanics"); auto initial_states = physics->getInitialFieldStates(); using DispSpace = smith::H1; - smith::FunctionalObjective> qoi( - "solid_dynamic_energy_proxy", mesh, smith::spaces(qoiFields(initial_states))); - qoi.addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [](double, auto, auto U, auto V) { - auto u = smith::get(U); - auto v = smith::get(V); - return 0.5 * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + 0.05 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); - }); + const auto displacement_index = field_store->getFieldIndex("displacement"); + const auto velocity_index = field_store->getFieldIndex("velocity"); + const auto qoi_fields = + std::vector{initial_states[displacement_index], initial_states[velocity_index]}; + smith::FunctionalObjective> qoi("solid_dynamic_energy_proxy", mesh, + smith::spaces(qoi_fields)); + qoi.addBodyIntegral( + smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [](const smith::TimeInfo&, auto, auto U, auto V) { + auto u = smith::get(U); + auto v = smith::get(V); + return 0.5 * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + 0.05 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + }); constexpr double dt = 0.25; constexpr int num_steps = 3; + auto current_qoi_fields = [&]() { + return std::vector{physics->getFieldStates()[displacement_index], + physics->getFieldStates()[velocity_index]}; + }; auto qoi_state = - 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(initial_states), + 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); for (int step = 0; step < num_steps; ++step) { physics->advanceTimestep(dt); qoi_state = qoi_state + - smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(physics->getFieldStates()), + smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), current_qoi_fields(), smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); } @@ -192,8 +184,8 @@ int main(int argc, char* argv[]) std::cout << "QoI value: " << qoi_state.get() << '\n'; qoi_state.data_store().back_prop(); auto shape_displacement = physics->getShapeDispFieldState(); - auto initial_displacement_state = initial_states[initial_displacement_state_index]; - auto initial_velocity_state = initial_states[velocity_state_index]; + auto initial_displacement_state = initial_states[displacement_index]; + auto initial_velocity_state = initial_states[velocity_index]; auto youngs_modulus_state = field_store->getParameterFields()[0]; std::cout << "dQoI/d(shape) norm: " << shape_displacement.get_dual()->Norml2() << '\n'; std::cout << "dQoI/d(youngs_modulus) norm: " << youngs_modulus_state.get_dual()->Norml2() << '\n'; @@ -209,7 +201,7 @@ int main(int argc, char* argv[]) // _run_end // _output_start - writer.write(physics->cycle(), physics->time(), outputFields(*field_store)); + writer.write(physics->cycle(), physics->time(), field_store->getOutputFieldStates()); std::cout << "ParaView output: paraview_composable_solid_mechanics\n"; // _output_end diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index 56d8c8ba67..0e45dcf6fa 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -79,8 +79,8 @@ int main(int argc, char* argv[]) auto coupled_system = smith::combineSystems(solid_system, thermal_system); - smith::thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + smith::thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); // _build_end diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index d01609ed8c..949d32fc57 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -35,21 +35,6 @@ #include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" // _includes_end -namespace { - -std::vector outputFields(const smith::FieldStore& field_store) -{ - return field_store.getOutputFieldStates(); -} - -std::vector qoiFields(const smith::FieldStore& field_store) -{ - return {field_store.getField(field_store.prefix("displacement")), - field_store.getField(field_store.prefix("temperature"))}; -} - -} // namespace - int main(int argc, char* argv[]) { // _init_start @@ -85,8 +70,8 @@ int main(int argc, char* argv[]) auto thermal_system = smith::buildThermalSystem(nullptr, smith::ThermalOptions{}, thermal_fields, param_fields, solid_fields); - smith::thermomechanics::TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + smith::thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{ + 1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); field_store->getParameterFields()[0].get()->setFromFieldFunction([](smith::tensor) { return 1.0; }); @@ -106,18 +91,6 @@ int main(int argc, char* argv[]) thermal_system->addHeatSource(mesh->entireBodyName(), [](auto, auto... /*unused*/) { return 0.1; }); // _bc_end - smith::LinearSolverOptions trust_region_linear{.linear_solver = smith::LinearSolver::CG, - .preconditioner = smith::Preconditioner::HypreAMG, - .relative_tol = 1e-6, - .absolute_tol = 1e-10, - .max_iterations = 80, - .print_level = 0}; - smith::NonlinearSolverOptions trust_region_nonlin{.nonlin_solver = smith::NonlinearSolver::TrustRegion, - .relative_tol = 1e-7, - .absolute_tol = 1e-8, - .max_iterations = 15, - .print_level = 0}; - smith::LinearSolverOptions coupled_linear{.linear_solver = smith::LinearSolver::SuperLU, .relative_tol = 1e-8, .absolute_tol = 1e-10, @@ -132,32 +105,33 @@ int main(int argc, char* argv[]) // _solver_end // _build_start - size_t max_staggered_iterations = 10; - auto custom_solver = std::make_shared(max_staggered_iterations); - custom_solver->addSubsystemSolver( - {0}, smith::buildNonlinearBlockSolver(trust_region_nonlin, trust_region_linear, *mesh), 1.0); - custom_solver->addSubsystemSolver({1}, smith::buildNonlinearBlockSolver(coupled_nonlin, coupled_linear, *mesh), 1.0); + auto coupled_solver = std::make_shared(10); + coupled_solver->addSubsystemSolver({0, 1}, smith::buildNonlinearBlockSolver(coupled_nonlin, coupled_linear, *mesh), + 1.0); - auto coupled_system = smith::combineSystems(custom_solver, solid_system, thermal_system); + auto coupled_system = smith::combineSystems(coupled_solver, solid_system, thermal_system); std::string physics_name = "composable_thermo_mechanics_advanced"; auto physics = smith::makeDifferentiablePhysics(coupled_system, physics_name); - auto output_states = outputFields(*field_store); + auto output_states = field_store->getOutputFieldStates(); auto output_writer = smith::createParaviewWriter(*mesh, output_states, "paraview_composable_thermo_mechanics_advanced", smith::ParaviewWriter::Options{.write_duals = false}); // _build_end // _qoi_start + auto qoi_fields = + std::vector{field_store->getField("displacement"), field_store->getField("temperature")}; smith::FunctionalObjective> qoi("thermo_mechanical_energy_proxy", mesh, - smith::spaces(qoiFields(*field_store))); - qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](double, auto /*X*/, auto U, auto Theta) { - auto u = smith::get(U); - auto theta = smith::get(Theta); - return 0.5 * u[0] * u[0] + 0.05 * theta * theta; - }); + smith::spaces(qoi_fields)); + qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), + [](const smith::TimeInfo&, auto /*X*/, auto U, auto Theta) { + auto u = smith::get(U); + auto theta = smith::get(Theta); + return 0.5 * u[0] * u[0] + 0.05 * theta * theta; + }); auto qoi_state = - 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(*field_store), + 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, smith::TimeInfo(physics->time(), 1.0, static_cast(physics->cycle()))); // _qoi_end @@ -167,13 +141,13 @@ int main(int argc, char* argv[]) for (int step = 0; step < qoi_steps; ++step) { physics->advanceTimestep(dt); qoi_state = qoi_state + - smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoiFields(*field_store), + smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); } // _run_end // _output_start - output_writer.write(physics->cycle(), physics->time(), outputFields(*field_store)); + output_writer.write(physics->cycle(), physics->time(), field_store->getOutputFieldStates()); std::cout << "ParaView output: paraview_composable_thermo_mechanics_advanced\n"; // _output_end diff --git a/improvements.md b/improvements.md new file mode 100644 index 0000000000..521f52494c --- /dev/null +++ b/improvements.md @@ -0,0 +1,58 @@ +1. Done. Removed brittle state-index assumptions in [composable_solid_mechanics.cpp](/usr/WS2/tupek2/dev/smith/examples/solid_mechanics/composable_solid_mechanics.cpp). +2. Done. Put `TimeInfo` class on separate line. +3. Done. State ordering now comes from field names, not hard-coded indices. +4. Done. Removed shims like `outputFields()` and `qoiFields()`. +5. Done. Renamed `TimeInfoYoungsModulusNeoHookean`. +6. Done. Renamed `TimeInfoGreenSaintVenantThermoelasticMaterial`. +7. Done. `outputFields()` layer removed as unneeded. +8. Done. `qoiFields()` layer removed as unneeded. +9. Done. `TimeInfo` now flows through base `FunctionalObjective` and base `FunctionalWeakForm` interfaces; `TimeDiscretizedObjective` and `TimeDiscretizedWeakForm` layers are removed. +10. No item 10. +11. Done. Renamed `custom_solver` to `coupled_solver`; advanced example now uses `(0,1)`. +12. Done. `appendRemappedStages` renamed to `appendStagesWithBlockMapping`; test now uses `combined_solver` and `StaticTimeIntegrationRule`. +13. Look at `TransientFreefallWithConsistentBoundaryConditions`. Fixed BC is applied, but test is supposed to cover free fall. Boundary condition may need to be time-dependent and move with expected acceleration. It should probably test cycle-zero acceleration solve. +14. In `_internal_vars` test, inline `auto [solid, internal_variables] = buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields);`. Rename `solid` to `solid_system` and `internal_variables` to `internal_variable_system`. +15. Like solid system, use `thermal_system->setTemperatureBcs`; make interface consistent with plurals. +16. Was `src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp` replaced by different test? +17. In `StronglyCoupledThreeSystems`, find better way to handle material setup. +18. Is `MonolithicCombinedSystem` still used? Probably delete it. +19. What is `mergeSystems` versus `combineSystems`? +20. Remove `findFieldStore`. This seems overly complex. Just grab first fields field store inline where needed. +21. Review `collectPhysicsFromPack` and `collectParamsFromPack`. What are they doing? +22. Is `TimeRuleParamsWithCoupling` used? +23. In `DifferentiablePhysics::dual(...)`, there is debug print. Change to `SLIC_ERROR` if important. +24. Logic here is confusing. Make clear main exception is acceleration solve. +25. Is all complication in `getBoundaryConditionManagers` needed? +26. Figure out how `outputField` in `fieldStore` is determined and whether it can be simplified. +27. Explain and simplify `MultiphysicsTimeIntegrator::advanceState`. +28. `makeAdvancer` should not take `cycle_zero_system` or `post_solve_systems`; get them off system. +29. Consider elasticity option for `HypreAMG`. Maybe optional configuration. See `NonlinearBlockSolver::completeSetup`. +30. Are `TimeRuleParams` still used? Can `detail::TimeRuleParamsWithCoupling` be renamed back to that and always use coupling? +31. Why is `CycleZeroSolidWeakFormType` adding many new `H1` fields? Does `AppendCouplingToParams` put last arguments first? +32. `DependsOn` for `SolidSystem` (`addTraction`, `addBody`, `addPressure`, etc.) should be indexed starting at displacement, then velocity, and so on. `DependsOn<0>` should only give displacement. `DependsOn<0,1,2,3>` should be displacement, velocity, acceleration, and `param_0`. +33. Make sure `ThermalSystem` stays in sync with this. +34. Return value from all systems should be variable named `physics_system`. Update unit tests, examples, and code comments, such as in `state_variable_system.hpp`. +35. Remove these overloads: + +```cpp +template +/// @brief Register an internal-variable material or evolution law on a domain. +void setMaterial(const MaterialType& material, const std::string& domain_name) +{ + addEvolution(domain_name, material); +} + +template +/// @brief Backward-compatible alias for `addEvolution`. +void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) +{ + addEvolution(domain_name, evolution_law); +} +``` + +36. Remove `InternalVariableOptions`. Also remove `StateVariableOptions` and constructor using it. +37. There seem to be duplicated `buildInternalVariableSystem`. +38. Explain `appendRemappedStages`. Can it get better name? `Remapped` means something else here, especially with finite-element field remaps. +39. What is `CoupledSolidThermoMechanicsMaterialAdapter` for? Delete it and use intended interface directly. +40. Review `test_linear_solver_none.cpp`. Make sure it still makes sense. +41. Revert changes to `src/smith/physics/materials/green_saint_venant_thermoelastic.hpp`. diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 06cf57d528..534453fed8 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -34,7 +34,6 @@ set(differentiable_numerics_headers evaluate_objective.hpp dirichlet_boundary_conditions.hpp time_integration_rule.hpp - time_discretized_weak_form.hpp paraview_writer.hpp multiphysics_time_integrator.hpp solid_mechanics_system.hpp diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index 70e8aa7d20..be540c411c 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -135,7 +135,7 @@ auto combineSystems(std::shared_ptr... subs) for (size_t i = 0; i < combined->subsystems.size(); ++i) { const auto& sub = combined->subsystems[i]; SLIC_ERROR_IF(!sub->solver, "Combined subsystem must have a solver"); - combined->solver->appendRemappedStages(*sub->solver, subsystem_global_block_indices[i]); + combined->solver->appendStagesWithBlockMapping(*sub->solver, subsystem_global_block_indices[i]); } std::shared_ptr cycle_zero_combined = nullptr; @@ -151,7 +151,7 @@ auto combineSystems(std::shared_ptr... subs) cycle_zero->weak_forms.push_back(wf); } SLIC_ERROR_IF(!sub->solver, "Combined cycle-zero subsystem must have a solver"); - cycle_zero->solver->appendRemappedStages(*sub->solver, global_block_indices); + cycle_zero->solver->appendStagesWithBlockMapping(*sub->solver, global_block_indices); } cycle_zero_combined = cycle_zero; } diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index fd8757dd7c..bd9d3e3bb4 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -8,7 +8,7 @@ #include "smith/differentiable_numerics/field_state.hpp" #include "smith/differentiable_numerics/time_integration_rule.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" #include "smith/physics/mesh.hpp" #include @@ -522,7 +522,7 @@ struct FieldStore { }; /** - * @brief Create a TimeDiscretizedWeakForm and register its fields in the FieldStore. + * @brief Create a FunctionalWeakForm and register its fields in the FieldStore. * * Thin convenience wrapper: registers @p test_type as the reaction field, registers all * @p field_types as input arguments, and constructs the weak form in one call. @@ -531,7 +531,7 @@ template auto createWeakForm(std::string name, FieldType test_type, FieldStore& field_store, FieldType... field_types) { - return std::make_shared>>( + return std::make_shared>>( name, field_store.getMesh(), field_store.getField(test_type.name).get()->space(), field_store.createSpaces(name, test_type.name, field_types...)); } diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 5682215599..208a23b356 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -10,7 +10,6 @@ #include "smith/differentiable_numerics/field_state.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/numerics/solver_config.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" #include "smith/differentiable_numerics/time_integration_rule.hpp" #include "smith/physics/mesh.hpp" #include "smith/differentiable_numerics/field_store.hpp" diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 097252bae8..5cd33c2025 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -17,7 +17,7 @@ #include "smith/differentiable_numerics/state_advancer.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/time_integration_rule.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" @@ -66,20 +66,20 @@ struct SolidMechanicsSystem : public SystemBase { static_assert(DisplacementTimeRule::num_states == 4, "SolidMechanicsSystem requires a 4-state time integration rule"); /// Main weak form: (u, u_old, v_old, a_old, coupling_fields..., params...) - using SolidWeakFormType = TimeDiscretizedWeakForm< + using SolidWeakFormType = FunctionalWeakForm< dim, H1, typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; /// Cycle-zero startup form reusing the main solid fields: (u, v_old, a, coupling_fields..., params...) /// No extra fields are registered for cycle zero; acceleration is the unknown for this internal solve. using CycleZeroSolidWeakFormType = - TimeDiscretizedWeakForm, - typename detail::AppendCouplingToParams< - Coupling, Parameters, H1, H1>>::type>; + FunctionalWeakForm, + typename detail::AppendCouplingToParams< + Coupling, Parameters, H1, H1>>::type>; /// L2 projection weak form for PK1 stress output (dim*dim components). /// Args: (stress_unknown, u, u_old, v_old, a_old, coupling_fields..., params...). - using StressOutputWeakFormType = TimeDiscretizedWeakForm< + using StressOutputWeakFormType = FunctionalWeakForm< dim, L2<0, dim * dim>, typename detail::AppendCouplingToParams, H1, H1, H1, H1>>::type>; diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index f702895558..af39300638 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -35,7 +35,7 @@ #include "smith/differentiable_numerics/state_advancer.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/time_integration_rule.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" @@ -64,8 +64,9 @@ struct InternalVariableSystem : public SystemBase { "InternalVariableSystem requires a 2-state time integration rule"); /// State weak form: (alpha, alpha_old, coupling_fields..., params...) - using InternalVariableWeakFormType = TimeDiscretizedWeakForm< - dim, StateSpace, typename detail::TimeRuleParamsWithCoupling::type>; + using InternalVariableWeakFormType = + FunctionalWeakForm::type>; std::shared_ptr internal_variable_weak_form; ///< Internal variable weak form. std::shared_ptr internal_variable_bc; ///< Internal variable BCs. diff --git a/src/smith/differentiable_numerics/system_solver.cpp b/src/smith/differentiable_numerics/system_solver.cpp index 36d855d8c4..8c06e2a557 100644 --- a/src/smith/differentiable_numerics/system_solver.cpp +++ b/src/smith/differentiable_numerics/system_solver.cpp @@ -40,8 +40,8 @@ void SystemSolver::addSubsystemSolver(const std::vector& block_indices, stages_.push_back(Stage{block_indices, std::move(solver), relaxation_factor}); } -void SystemSolver::appendRemappedStages(const SystemSolver& subsystem_solver, - const std::vector& global_block_indices) +void SystemSolver::appendStagesWithBlockMapping(const SystemSolver& subsystem_solver, + const std::vector& global_block_indices) { SLIC_ERROR_IF(global_block_indices.empty(), "Global block index map must be non-empty"); diff --git a/src/smith/differentiable_numerics/system_solver.hpp b/src/smith/differentiable_numerics/system_solver.hpp index 1598dbd071..71351f1c53 100644 --- a/src/smith/differentiable_numerics/system_solver.hpp +++ b/src/smith/differentiable_numerics/system_solver.hpp @@ -51,10 +51,11 @@ class SystemSolver { void addSubsystemSolver(const std::vector& block_indices, std::shared_ptr solver, double relaxation_factor = 1.0); - /// @brief Append stages from another solver, remapped onto global block indices. + /// @brief Append stages from another solver using a local-to-global block mapping. /// @param subsystem_solver Source solver whose stages operate on subsystem-local block indices. /// @param global_block_indices Mapping from subsystem-local block index to global block index. - void appendRemappedStages(const SystemSolver& subsystem_solver, const std::vector& global_block_indices); + void appendStagesWithBlockMapping(const SystemSolver& subsystem_solver, + const std::vector& global_block_indices); /// @brief Solves the multiphysics system using staggered iterations. /// @param residual_evals Vector of WeakForm evaluations for each block. diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 894d95f4dd..9329eaf6ff 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -156,8 +156,8 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto coupled = combineSystems(solid, thermal); - thermomechanics::TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -217,13 +217,13 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, param_fields, solid_fields); - auto custom_solver = std::make_shared(10); - custom_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); - custom_solver->addSubsystemSolver({1}, buildNonlinearBlockSolver(thermal_nonlin_opts, thermal_lin_opts, *mesh_)); - auto coupled = combineSystems(custom_solver, solid, thermal); + auto coupled_solver = std::make_shared(10); + coupled_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); + coupled_solver->addSubsystemSolver({1}, buildNonlinearBlockSolver(thermal_nonlin_opts, thermal_lin_opts, *mesh_)); + auto coupled = combineSystems(coupled_solver, solid, thermal); - thermomechanics::TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, + 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -299,7 +299,7 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solid, thermal); - thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); @@ -327,7 +327,7 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); auto coupled = combineSystems(solver_ptr, solid, thermal); - thermomechanics::TimeInfoGreenSaintVenantThermoelasticMaterial material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); diff --git a/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp index df4a9c0cb3..57e95a775b 100644 --- a/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp @@ -11,7 +11,7 @@ #include "smith/physics/mesh.hpp" #include "gretl/data_store.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" #include "smith/physics/functional_objective.hpp" #include "smith/differentiable_numerics/lumped_mass_explicit_newmark_state_advancer.hpp" @@ -146,7 +146,7 @@ struct MeshFixture : public testing::Test { std::vector trial_spaces = {&disp.get()->space(), &disp.get()->space(), &disp.get()->space(), &density0.get()->space()}; - auto solid_mechanics_residual = std::make_shared>>( physics_name, mesh_, disp.get()->space(), trial_spaces); diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index bf6a56471e..a1c27ba537 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -21,7 +21,7 @@ #include "smith/differentiable_numerics/field_store.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" namespace smith { @@ -87,7 +87,7 @@ template auto buildScalarDiffusionWeakForm(const std::string& name, std::shared_ptr mesh, std::shared_ptr fs, FieldTypeT field_type) { - using WeakFormType = TimeDiscretizedWeakForm<2, H1<1>, Parameters>>; + using WeakFormType = FunctionalWeakForm<2, H1<1>, Parameters>>; auto weak_form = std::make_shared(name, mesh, fs->getField(field_type.name).get()->space(), fs->createSpaces(name, field_type.name, field_type)); weak_form->addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), @@ -100,7 +100,7 @@ auto buildSecondOrderMainWeakForm(const std::string& name, std::shared_ptr DispFieldType displacement_type, DispOldFieldType displacement_old_type, VelocityFieldType velocity_type, AccelerationFieldType acceleration_type) { - using WeakFormType = TimeDiscretizedWeakForm<2, H1<1>, Parameters, H1<1>, H1<1>, H1<1>>>; + using WeakFormType = FunctionalWeakForm<2, H1<1>, Parameters, H1<1>, H1<1>, H1<1>>>; auto weak_form = std::make_shared(name, mesh, fs->getField(displacement_type.name).get()->space(), fs->createSpaces(name, displacement_type.name, displacement_type, @@ -114,7 +114,7 @@ auto buildSecondOrderCycleZeroWeakForm(const std::string& name, std::shared_ptr< std::shared_ptr fs, DispFieldType displacement_type, VelocityFieldType velocity_type, AccelerationFieldType acceleration_type) { - using WeakFormType = TimeDiscretizedWeakForm<2, H1<1>, Parameters, H1<1>, H1<1>>>; + using WeakFormType = FunctionalWeakForm<2, H1<1>, Parameters, H1<1>, H1<1>>>; auto weak_form = std::make_shared( name, mesh, fs->getField(acceleration_type.name).get()->space(), fs->createSpaces(name, acceleration_type.name, displacement_type, velocity_type, acceleration_type)); @@ -293,7 +293,7 @@ TEST(SystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) StateManager::reset(); } -TEST(SystemSolver, AppendsRemappedStagesForCombinedSubsystems) +TEST(SystemSolver, AppendsStagesWithBlockMappingForCombinedSubsystems) { auto first_solver = std::make_shared(); auto second_solver = std::make_shared(); @@ -304,9 +304,9 @@ TEST(SystemSolver, AppendsRemappedStagesForCombinedSubsystems) SystemSolver subsystem_b(3, false); subsystem_b.addSubsystemSolver({0, 1}, second_solver, 1.0); - SystemSolver combined(3, false); - combined.appendRemappedStages(subsystem_a, {0}); - combined.appendRemappedStages(subsystem_b, {1, 2}); + SystemSolver combined_solver(3, false); + combined_solver.appendStagesWithBlockMapping(subsystem_a, {0}); + combined_solver.appendStagesWithBlockMapping(subsystem_b, {1, 2}); axom::sidre::DataStore datastore; StateManager::initialize(datastore, "combined_solver_stage_mapping"); @@ -318,13 +318,13 @@ TEST(SystemSolver, AppendsRemappedStagesForCombinedSubsystems) FieldType> shape_disp_type("shape_displacement"); field_store->addShapeDisp(shape_disp_type); - auto quasi_static = std::make_shared(); + auto static_rule = std::make_shared(); FieldType> field0_type("field0"); FieldType> field1_type("field1"); FieldType> field2_type("field2"); - field_store->addIndependent(field0_type, quasi_static); - field_store->addIndependent(field1_type, quasi_static); - field_store->addIndependent(field2_type, quasi_static); + field_store->addIndependent(field0_type, static_rule); + field_store->addIndependent(field1_type, static_rule); + field_store->addIndependent(field2_type, static_rule); auto wf0 = buildScalarDiffusionWeakForm("wf0", mesh, field_store, field0_type); auto wf1 = buildScalarDiffusionWeakForm("wf1", mesh, field_store, field1_type); @@ -338,8 +338,8 @@ TEST(SystemSolver, AppendsRemappedStagesForCombinedSubsystems) const std::vector> params(residuals.size()); const auto bc_managers = field_store->getBoundaryConditionManagers(residual_names); - auto solved_states = combined.solve(residuals, block_indices, field_store->getShapeDisp(), states, params, - TimeInfo(0.0, 1.0, 0), bc_managers); + auto solved_states = combined_solver.solve(residuals, block_indices, field_store->getShapeDisp(), states, params, + TimeInfo(0.0, 1.0, 0), bc_managers); EXPECT_EQ(solved_states.size(), 3); EXPECT_EQ(first_solver->solveCalls(), 1); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 2c6d9e8d2a..6759a078ed 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -16,7 +16,7 @@ #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/time_integration_rule.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/weak_form.hpp" #include "smith/differentiable_numerics/system_base.hpp" @@ -46,7 +46,7 @@ struct ThermalSystem : public SystemBase { static_assert(TemperatureTimeRule::num_states == 2, "ThermalSystem requires a 2-state time integration rule"); /// Thermal weak form: (temp, temp_old, coupling_fields..., params...) - using ThermalWeakFormType = TimeDiscretizedWeakForm< + using ThermalWeakFormType = FunctionalWeakForm< dim, H1, typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; diff --git a/src/smith/differentiable_numerics/time_discretized_weak_form.hpp b/src/smith/differentiable_numerics/time_discretized_weak_form.hpp deleted file mode 100644 index 1c4f06ab93..0000000000 --- a/src/smith/differentiable_numerics/time_discretized_weak_form.hpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -/** - * @file time_discretized_weak_form.hpp - * - * @brief Wraps FunctionalWeakForm to provide TimeInfo (time, dt, cycle) to integrands instead of just time. - * - * This class provides a thin wrapper around FunctionalWeakForm that automatically converts the time - * parameter into a TimeInfo struct containing time, timestep size (dt), and cycle number. This allows - * physics systems to access timestep information needed for time integration. - * - * Key features: - * - All integrands receive TimeInfo instead of just time - * - Systems are responsible for manually applying time integration rules - * - Supports body integrals, boundary integrals, boundary fluxes, and interior boundary integrals - * - Default behavior passes ALL input fields to integrands (can be overridden with DependsOn) - */ - -#pragma once - -#include "smith/physics/functional_weak_form.hpp" -#include "smith/physics/mesh.hpp" -#include "smith/differentiable_numerics/field_state.hpp" -#include "smith/differentiable_numerics/time_integration_rule.hpp" - -namespace smith { - -template > -class TimeDiscretizedWeakForm; - -/** - * @brief A time-discretized weak form that provides TimeInfo to integrands. - * - * This wraps FunctionalWeakForm to pass TimeInfo (containing time, dt, and cycle) to all - * integrand functions instead of just the time value. This allows physics models to access - * timestep information for time integration. - * - * @tparam spatial_dim The spatial dimension for the problem. - * @tparam OutputSpace The output residual for the weak form (test-space). - * @tparam InputSpaces All the input FiniteElementState fields (trial-spaces). - */ -template -class TimeDiscretizedWeakForm> - : public FunctionalWeakForm> { - public: - using Base = FunctionalWeakForm>; ///< Base class alias - - /** - * @brief Construct a time-discretized weak form. - * @param physics_name Unique name for this physics module. - * @param mesh The computational mesh. - * @param output_mfem_space The test function space (output/residual space). - * @param input_mfem_spaces Vector of trial function spaces (input field spaces). - */ - TimeDiscretizedWeakForm(std::string physics_name, std::shared_ptr mesh, - const mfem::ParFiniteElementSpace& output_mfem_space, - const typename Base::SpacesT& input_mfem_spaces) - : Base(physics_name, mesh, output_mfem_space, input_mfem_spaces) - { - } - - /** - * @brief Add a body integral with TimeInfo. - * - * The integrand receives TimeInfo (containing time, dt, cycle) instead of just time. - * The system is responsible for manually applying time integration rules inside the integrand - * to compute current state values from the raw field history. - * - * @tparam active_parameters Indices of fields this integral depends on. - * @tparam BodyIntegralType The integrand function type. - * @param depends_on Dependency specification for which input fields to pass. - * @param body_name The name of the domain. - * @param integrand Function with signature (TimeInfo, X, inputs...) -> residual. - */ - template - void addBodyIntegral(DependsOn depends_on, std::string body_name, BodyIntegralType integrand) - { - const double* dt = &this->dt_; - const size_t* cycle = &this->cycle_; - Base::addBodyIntegral(depends_on, body_name, [dt, cycle, integrand](double t, auto X, auto... inputs) { - TimeInfo time_info(t, *dt, *cycle); - return integrand(time_info, X, inputs...); - }); - } - - /** - * @brief Add a body integral with TimeInfo (defaults to all input fields). - * @tparam BodyIntegralType The integrand function type. - * @param body_name The name of the domain. - * @param integrand Function with signature (TimeInfo, X, inputs...) -> residual. - */ - template - void addBodyIntegral(std::string body_name, BodyIntegralType integrand) - { - constexpr int num_inputs = sizeof...(InputSpaces); - addBodyIntegralWithAllParams(body_name, integrand, std::make_integer_sequence{}); - } - - /** - * @brief Add a body source (body load) with TimeInfo. - * @tparam active_parameters Indices of fields this source depends on. - * @tparam BodyLoadType The load function type. - * @param depends_on Dependency specification. - * @param body_name The name of the domain. - * @param load_function Function with signature (TimeInfo, X, inputs...) -> load vector. - */ - template - void addBodySource(DependsOn depends_on, std::string body_name, BodyLoadType load_function) - { - addBodyIntegral(depends_on, body_name, [load_function](auto t_info, auto X, auto... inputs) { - return smith::tuple{-load_function(t_info, get(X), get(inputs)...), smith::zero{}}; - }); - } - - /// defaults to use all parameters - /// @overload - template - void addBodySource(std::string body_name, BodyLoadType load_function) - { - constexpr int num_inputs = sizeof...(InputSpaces); - addBodySourceWithAllParams(body_name, load_function, std::make_integer_sequence{}); - } - - /** - * @brief Add a boundary integral with TimeInfo. - * @tparam active_parameters Indices of fields this integral depends on. - * @tparam BoundaryIntegralType The boundary integrand function type. - * @param depends_on Dependency specification. - * @param boundary_name The name of the boundary. - * @param integrand Function with signature (TimeInfo, X, inputs...) -> residual. - */ - template - void addBoundaryIntegral(DependsOn depends_on, std::string boundary_name, - BoundaryIntegralType integrand) - { - const double* dt = &this->dt_; - const size_t* cycle = &this->cycle_; - Base::addBoundaryIntegral(depends_on, boundary_name, [dt, cycle, integrand](double t, auto X, auto... inputs) { - TimeInfo time_info(t, *dt, *cycle); - return integrand(time_info, X, inputs...); - }); - } - - /// defaults to use all parameters - /// @overload - template - void addBoundaryIntegral(std::string boundary_name, BoundaryIntegralType integrand) - { - constexpr int num_inputs = sizeof...(InputSpaces); - addBoundaryIntegralWithAllParams(boundary_name, integrand, std::make_integer_sequence{}); - } - - /** - * @brief Add a boundary flux with TimeInfo. - * @tparam active_parameters Indices of fields this integral depends on. - * @tparam BoundaryFluxType The flux function type. - * @param depends_on Dependency specification. - * @param boundary_name The name of the boundary. - * @param flux_function Function with signature (TimeInfo, X, n, inputs...) -> flux. - */ - template - void addBoundaryFlux(DependsOn depends_on, std::string boundary_name, - BoundaryFluxType flux_function) - { - const double* dt = &this->dt_; - const size_t* cycle = &this->cycle_; - Base::addBoundaryFlux(depends_on, boundary_name, - [dt, cycle, flux_function](double t, auto X, auto n, auto... inputs) { - TimeInfo time_info(t, *dt, *cycle); - return flux_function(time_info, X, n, inputs...); - }); - } - - /// defaults to use all parameters - /// @overload - template - void addBoundaryFlux(std::string boundary_name, BoundaryFluxType flux_function) - { - constexpr int num_inputs = sizeof...(InputSpaces); - addBoundaryFluxWithAllParams(boundary_name, flux_function, std::make_integer_sequence{}); - } - - /** - * @brief Add an interior boundary integral with TimeInfo. - * @tparam active_parameters Indices of fields this integral depends on. - * @tparam InteriorIntegralType The integrand function type. - * @param depends_on Dependency specification. - * @param interior_name The name of the interior boundary. - * @param integrand Function with signature (TimeInfo, X, inputs...) -> residual. - */ - template - void addInteriorBoundaryIntegral(DependsOn depends_on, std::string interior_name, - InteriorIntegralType integrand) - { - const double* dt = &this->dt_; - const size_t* cycle = &this->cycle_; - Base::addInteriorBoundaryIntegral(depends_on, interior_name, - [dt, cycle, integrand](double t, auto X, auto... inputs) { - TimeInfo time_info(t, *dt, *cycle); - return integrand(time_info, X, inputs...); - }); - } - - /// defaults to use all parameters - /// @overload - template - void addInteriorBoundaryIntegral(std::string interior_name, InteriorIntegralType integrand) - { - constexpr int num_inputs = sizeof...(InputSpaces); - addInteriorBoundaryIntegralWithAllParams(interior_name, integrand, std::make_integer_sequence{}); - } - - private: - template - void addBodyIntegralWithAllParams(std::string body_name, BodyIntegralType integrand, - std::integer_sequence) - { - addBodyIntegral(DependsOn{}, body_name, integrand); - } - - template - void addBodySourceWithAllParams(std::string body_name, BodyLoadType load_function, - std::integer_sequence) - { - addBodySource(DependsOn{}, body_name, load_function); - } - - template - void addBoundaryIntegralWithAllParams(std::string boundary_name, BoundaryIntegralType integrand, - std::integer_sequence) - { - addBoundaryIntegral(DependsOn{}, boundary_name, integrand); - } - - template - void addInteriorBoundaryIntegralWithAllParams(std::string interior_name, InteriorIntegralType integrand, - std::integer_sequence) - { - addInteriorBoundaryIntegral(DependsOn{}, interior_name, integrand); - } - - template - void addBoundaryFluxWithAllParams(std::string boundary_name, BoundaryFluxType flux_function, - std::integer_sequence) - { - addBoundaryFlux(DependsOn{}, boundary_name, flux_function); - } -}; - -/// @brief A container holding the weak forms useful for second-order time-discretized systems. -class SecondOrderTimeDiscretizedWeakForms { - public: - std::shared_ptr time_discretized_weak_form; ///< Weak form in terms of predicted/current unknown. - std::shared_ptr final_reaction_weak_form; ///< Weak form in terms of converged kinematic states. -}; - -template > -class SecondOrderTimeDiscretizedWeakForm; - -/// @brief Convenience wrapper for second-order-in-time systems using an implicit Newmark rule. -template -class SecondOrderTimeDiscretizedWeakForm> - : public SecondOrderTimeDiscretizedWeakForms { - public: - static constexpr int NUM_STATE_VARS = 4; ///< u, u_old, v_old, a_old - - /// @brief Predicted-state weak form type. - using TimeDiscretizedWeakFormT = - TimeDiscretizedWeakForm>; - /// @brief Final corrected-state reaction weak form type. - using FinalReactionFormT = TimeDiscretizedWeakForm>; - - /// @brief Construct paired weak forms for second-order systems. - SecondOrderTimeDiscretizedWeakForm(std::string physics_name, std::shared_ptr mesh, - ImplicitNewmarkSecondOrderTimeIntegrationRule time_rule, - const mfem::ParFiniteElementSpace& output_mfem_space, - const typename TimeDiscretizedWeakFormT::SpacesT& input_mfem_spaces) - : time_rule_(time_rule) - { - time_discretized_weak_form_ = - std::make_shared(physics_name, mesh, output_mfem_space, input_mfem_spaces); - time_discretized_weak_form = time_discretized_weak_form_; - - typename TimeDiscretizedWeakFormT::SpacesT trial_removed_spaces(std::next(input_mfem_spaces.begin()), - input_mfem_spaces.end()); - final_reaction_weak_form_ = - std::make_shared(physics_name, mesh, output_mfem_space, trial_removed_spaces); - final_reaction_weak_form = final_reaction_weak_form_; - } - - /// @brief Add a body integral using corrected second-order kinematics. - template - void addBodyIntegral(DependsOn /*depends_on*/, std::string body_name, - BodyIntegralType integrand) - { - auto time_rule = time_rule_; - time_discretized_weak_form_->addBodyIntegral( - DependsOn<0, 1, 2, 3, NUM_STATE_VARS + active_parameters...>{}, body_name, - [integrand, time_rule](const TimeInfo& t, auto X, auto U, auto U_old, auto U_dot_old, auto U_dot_dot_old, - auto... inputs) { - return integrand(t, X, time_rule.value(t, U, U_old, U_dot_old, U_dot_dot_old), - time_rule.dot(t, U, U_old, U_dot_old, U_dot_dot_old), - time_rule.ddot(t, U, U_old, U_dot_old, U_dot_dot_old), inputs...); - }); - final_reaction_weak_form_->addBodyIntegral(DependsOn<0, 1, 2, NUM_STATE_VARS - 1 + active_parameters...>{}, - body_name, integrand); - } - - /// @brief Add a body integral using all trailing inputs. - template - void addBodyIntegral(std::string body_name, BodyIntegralType integrand) - { - addBodyIntegral(DependsOn<>{}, body_name, integrand); - } - - /// @brief Add a body source using corrected second-order kinematics. - template - void addBodySource(DependsOn /*depends_on*/, std::string body_name, BodyLoadType load_function) - { - auto time_rule = time_rule_; - time_discretized_weak_form_->addBodyIntegral( - DependsOn<0, 1, 2, 3, NUM_STATE_VARS + active_parameters...>{}, body_name, - [load_function, time_rule](const TimeInfo& t, auto X, auto U, auto U_old, auto U_dot_old, auto U_dot_dot_old, - auto... inputs) { - return smith::tuple{ - -load_function(t.time(), get(X), - get(time_rule.value(t, U, U_old, U_dot_old, U_dot_dot_old)), - get(time_rule.dot(t, U, U_old, U_dot_old, U_dot_dot_old)), - get(time_rule.ddot(t, U, U_old, U_dot_old, U_dot_dot_old)), get(inputs)...), - smith::zero{}}; - }); - final_reaction_weak_form_->addBodyIntegral( - DependsOn<0, 1, 2, NUM_STATE_VARS - 1 + active_parameters...>{}, body_name, - [load_function](const TimeInfo& t, auto X, auto... inputs) { - return smith::tuple{-load_function(t.time(), get(X), get(inputs)...), smith::zero{}}; - }); - } - - /// @brief Add a body source using all trailing inputs. - template - void addBodySource(std::string body_name, BodyLoadType load_function) - { - addBodySource(DependsOn<>{}, body_name, load_function); - } - - private: - std::shared_ptr time_discretized_weak_form_; - std::shared_ptr final_reaction_weak_form_; - ImplicitNewmarkSecondOrderTimeIntegrationRule time_rule_; -}; - -} // namespace smith diff --git a/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp b/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp index 846efa7d43..a8607e79a3 100644 --- a/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp +++ b/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp @@ -12,7 +12,7 @@ namespace smith::thermomechanics { /// @brief TimeInfo-aware wrapper for `GreenSaintVenantThermoelasticMaterial`. -struct TimeInfoGreenSaintVenantThermoelasticMaterial { +struct GreenSaintVenantThermoelasticMaterialWithTimeInfo { /// State type reused from wrapped material. using State = GreenSaintVenantThermoelasticMaterial::State; @@ -35,7 +35,7 @@ struct TimeInfoGreenSaintVenantThermoelasticMaterial { }; /// @brief TimeInfo-aware wrapper for `ParameterizedGreenSaintVenantThermoelasticMaterial`. -struct TimeInfoParameterizedGreenSaintVenantThermoelasticMaterial { +struct ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo { /// State type reused from wrapped material. using State = ParameterizedGreenSaintVenantThermoelasticMaterial::State; diff --git a/src/smith/physics/functional_objective.hpp b/src/smith/physics/functional_objective.hpp index 407f5da6d8..6deb094c23 100644 --- a/src/smith/physics/functional_objective.hpp +++ b/src/smith/physics/functional_objective.hpp @@ -71,8 +71,14 @@ class FunctionalObjective, std::integer_ void addBodyIntegral(DependsOn, std::string body_name, const FuncOfTimeSpaceAndParams& qfunction) { - objective_->AddDomainIntegral(smith::Dimension{}, smith::DependsOn{}, qfunction, - mesh_->domain(body_name)); + const double* dt = &dt_; + const size_t* cycle = &cycle_; + objective_->AddDomainIntegral( + smith::Dimension{}, smith::DependsOn{}, + [dt, cycle, qfunction](double time, auto X, auto... params) { + return invokeTimeAwareIntegrand(qfunction, time, *dt, *cycle, X, params...); + }, + mesh_->domain(body_name)); } /** @@ -86,8 +92,14 @@ class FunctionalObjective, std::integer_ void addBoundaryIntegral(DependsOn, std::string boundary_name, const FuncOfTimeSpaceAndParams& qfunction) { - objective_->AddBoundaryIntegral(smith::Dimension{}, smith::DependsOn{}, - qfunction, mesh_->domain(boundary_name)); + const double* dt = &dt_; + const size_t* cycle = &cycle_; + objective_->AddBoundaryIntegral( + smith::Dimension{}, smith::DependsOn{}, + [dt, cycle, qfunction](double time, auto X, auto... params) { + return invokeTimeAwareIntegrand(qfunction, time, *dt, *cycle, X, params...); + }, + mesh_->domain(boundary_name)); } /// @overload @@ -127,6 +139,19 @@ class FunctionalObjective, std::integer_ } private: + template + static auto invokeTimeAwareIntegrand(const FuncOfTimeSpaceAndParams& qfunction, double time, double dt, size_t cycle, + XType&& X, ParamTypes&&... params) + { + if constexpr (requires { + qfunction(TimeInfo(time, dt, cycle), std::forward(X), std::forward(params)...); + }) { + return qfunction(TimeInfo(time, dt, cycle), std::forward(X), std::forward(params)...); + } else { + return qfunction(time, std::forward(X), std::forward(params)...); + } + } + /// @brief Utility to evaluate residual using all fields in vector template auto evaluateObjective(std::integer_sequence, double time, ConstFieldPtr shape_disp, diff --git a/src/smith/physics/functional_weak_form.hpp b/src/smith/physics/functional_weak_form.hpp index 42d6345813..18c33a8e1e 100644 --- a/src/smith/physics/functional_weak_form.hpp +++ b/src/smith/physics/functional_weak_form.hpp @@ -110,13 +110,19 @@ class FunctionalWeakForm, template void addBodyIntegral(DependsOn, std::string body_name, BodyIntegralType integrand) { - weak_form_->AddDomainIntegral(Dimension{}, DependsOn{}, integrand, - mesh_->domain(body_name)); + const double* dt = &dt_; + const size_t* cycle = &cycle_; + weak_form_->AddDomainIntegral( + Dimension{}, DependsOn{}, + [dt, cycle, integrand](double time, auto X, auto... inputs) { + return invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, inputs...); + }, + mesh_->domain(body_name)); v_dot_weak_form_residual_->AddDomainIntegral( Dimension{}, DependsOn<0, 1 + active_parameters...>{}, - [integrand](double time, auto X, auto V, auto... inputs) { - auto orig_tuple = integrand(time, X, inputs...); + [dt, cycle, integrand](double time, auto X, auto V, auto... inputs) { + auto orig_tuple = invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, inputs...); return smith::inner(get(V), get(orig_tuple)) + smith::inner(get(V), get(orig_tuple)); }, @@ -127,7 +133,7 @@ class FunctionalWeakForm, template void addBodyIntegral(std::string body_name, BodyForceType body_integral) { - addBodyIntegral(DependsOn<>{}, body_name, body_integral); + addBodyIntegralWithAllParams(body_name, body_integral, std::make_integer_sequence{}); } /** @@ -153,8 +159,9 @@ class FunctionalWeakForm, template void addBodySource(DependsOn depends_on, std::string body_name, BodyLoadType load_function) { - addBodyIntegral(depends_on, body_name, [load_function](double t, auto X, auto... inputs) { - return smith::tuple{-load_function(t, get(X), get(inputs)...), smith::zero{}}; + addBodyIntegral(depends_on, body_name, [load_function](const TimeInfo& t_info, auto X, auto... inputs) { + return smith::tuple{-invokeTimeAwareSource(load_function, t_info, get(X), get(inputs)...), + smith::zero{}}; }); } @@ -162,7 +169,8 @@ class FunctionalWeakForm, template void addBodySource(std::string body_name, BodyLoadType load_function) { - return addBodySource(DependsOn<>{}, body_name, load_function); + return addBodySourceWithAllParams(body_name, load_function, + std::make_integer_sequence{}); } /** @@ -191,13 +199,19 @@ class FunctionalWeakForm, template void addBoundaryIntegral(DependsOn, std::string boundary_name, BoundaryIntegrandType integrand) { - weak_form_->AddBoundaryIntegral(Dimension{}, DependsOn{}, integrand, - mesh_->domain(boundary_name)); + const double* dt = &dt_; + const size_t* cycle = &cycle_; + weak_form_->AddBoundaryIntegral( + Dimension{}, DependsOn{}, + [dt, cycle, integrand](double time, auto X, auto... params) { + return invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); + }, + mesh_->domain(boundary_name)); v_dot_weak_form_residual_->AddBoundaryIntegral( Dimension{}, DependsOn<0, 1 + active_parameters...>{}, - [integrand](double t, auto X, auto V, auto... params) { - auto orig_surface_flux = integrand(t, X, params...); + [dt, cycle, integrand](double time, auto X, auto V, auto... params) { + auto orig_surface_flux = invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); return smith::inner(get(V), orig_surface_flux); }, mesh_->domain(boundary_name)); @@ -207,7 +221,8 @@ class FunctionalWeakForm, template void addBoundaryIntegral(std::string boundary_name, const BoundaryIntegrandType& integrand) { - addBoundaryIntegral(DependsOn<>{}, boundary_name, integrand); + addBoundaryIntegralWithAllParams(boundary_name, integrand, + std::make_integer_sequence{}); } /** @@ -232,14 +247,20 @@ class FunctionalWeakForm, void addInteriorBoundaryIntegral(DependsOn, std::string interior_name, InteriorIntegrandType integrand) { - weak_form_->AddInteriorFaceIntegral(Dimension{}, DependsOn{}, integrand, - mesh_->domain(interior_name)); + const double* dt = &dt_; + const size_t* cycle = &cycle_; + weak_form_->AddInteriorFaceIntegral( + Dimension{}, DependsOn{}, + [dt, cycle, integrand](double time, auto X, auto... params) { + return invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); + }, + mesh_->domain(interior_name)); v_dot_weak_form_residual_->AddInteriorFaceIntegral( Dimension{}, DependsOn<0, 1 + active_parameters...>{}, - [integrand](double t, auto X, auto V, auto... params) { + [dt, cycle, integrand](double time, auto X, auto V, auto... params) { auto [V1, V2] = V; - auto orig_surface_flux = integrand(t, X, params...); + auto orig_surface_flux = invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); auto [flux_pos, flux_neg] = orig_surface_flux; return smith::inner(V1, flux_pos) + smith::inner(V2, flux_neg); }, @@ -250,7 +271,8 @@ class FunctionalWeakForm, template void addInteriorBoundaryIntegral(std::string interior_name, const InteriorIntegrandType& integrand) { - addInteriorBoundaryIntegral(DependsOn<>{}, interior_name, integrand); + addInteriorBoundaryIntegralWithAllParams(interior_name, integrand, + std::make_integer_sequence{}); } /** @@ -278,9 +300,9 @@ class FunctionalWeakForm, void addBoundaryFlux(DependsOn depends_on, std::string boundary_name, BoundaryFluxType flux_function) { - addBoundaryIntegral(depends_on, boundary_name, [flux_function](double t, auto X, auto... inputs) { + addBoundaryIntegral(depends_on, boundary_name, [flux_function](const TimeInfo& t_info, auto X, auto... inputs) { auto n = cross(get(X)); - return -flux_function(t, get(X), normalize(n), get(inputs)...); + return -invokeTimeAwareFlux(flux_function, t_info, get(X), normalize(n), get(inputs)...); }); } @@ -288,7 +310,7 @@ class FunctionalWeakForm, template void addBoundaryFlux(std::string boundary_name, const BoundaryFluxType& integrand) { - addBoundaryFlux(DependsOn<>{}, boundary_name, integrand); + addBoundaryFluxWithAllParams(boundary_name, integrand, std::make_integer_sequence{}); } /// @overload @@ -411,6 +433,80 @@ class FunctionalWeakForm, } protected: + template + void addBodyIntegralWithAllParams(std::string body_name, BodyIntegralType integrand, + std::integer_sequence) + { + this->template addBodyIntegral(DependsOn{}, body_name, integrand); + } + + template + void addBodySourceWithAllParams(std::string body_name, BodyLoadType load_function, + std::integer_sequence) + { + this->template addBodySource(DependsOn{}, body_name, load_function); + } + + template + void addBoundaryIntegralWithAllParams(std::string boundary_name, BoundaryIntegrandType integrand, + std::integer_sequence) + { + this->template addBoundaryIntegral(DependsOn{}, boundary_name, integrand); + } + + template + void addBoundaryFluxWithAllParams(std::string boundary_name, BoundaryFluxType flux_function, + std::integer_sequence) + { + this->template addBoundaryFlux(DependsOn{}, boundary_name, flux_function); + } + + template + void addInteriorBoundaryIntegralWithAllParams(std::string interior_name, InteriorIntegrandType integrand, + std::integer_sequence) + { + this->template addInteriorBoundaryIntegral(DependsOn{}, interior_name, integrand); + } + + template + static auto invokeTimeAwareIntegrand(const IntegrandType& integrand, double time, double dt, size_t cycle, XType&& X, + InputTypes&&... inputs) + { + if constexpr (requires { + integrand(TimeInfo(time, dt, cycle), std::forward(X), std::forward(inputs)...); + }) { + return integrand(TimeInfo(time, dt, cycle), std::forward(X), std::forward(inputs)...); + } else { + return integrand(time, std::forward(X), std::forward(inputs)...); + } + } + + template + static auto invokeTimeAwareSource(const LoadFunctionType& load_function, const TimeInfo& t_info, XType&& X, + InputTypes&&... inputs) + { + if constexpr (requires { load_function(t_info, std::forward(X), std::forward(inputs)...); }) { + return load_function(t_info, std::forward(X), std::forward(inputs)...); + } else { + return load_function(t_info.time(), std::forward(X), std::forward(inputs)...); + } + } + + template + static auto invokeTimeAwareFlux(const FluxFunctionType& flux_function, const TimeInfo& t_info, XType&& X, NType&& n, + InputTypes&&... inputs) + { + if constexpr (requires { + flux_function(t_info, std::forward(X), std::forward(n), + std::forward(inputs)...); + }) { + return flux_function(t_info, std::forward(X), std::forward(n), std::forward(inputs)...); + } else { + return flux_function(t_info.time(), std::forward(X), std::forward(n), + std::forward(inputs)...); + } + } + /// @brief Helper to validate input spaces recursively (for constructor) template void validateInputSpaces(const SpacesT& input_mfem_spaces) const diff --git a/src/smith/physics/tests/test_functional_weak_form.cpp b/src/smith/physics/tests/test_functional_weak_form.cpp index 61ea04e68d..14d9b96b3f 100644 --- a/src/smith/physics/tests/test_functional_weak_form.cpp +++ b/src/smith/physics/tests/test_functional_weak_form.cpp @@ -98,10 +98,12 @@ struct WeakFormFixture : public testing::Test { std::string surface_name = "side"; mesh->addDomainOfBoundaryElements(surface_name, smith::by_attr(1)); - f_weak_form->addBoundaryFlux(surface_name, [](double /*t*/, auto /*x*/, auto n) { return 1.0 * n; }); + f_weak_form->addBoundaryFlux(smith::DependsOn<>{}, surface_name, + [](double /*t*/, auto /*x*/, auto n) { return 1.0 * n; }); f_weak_form->addBodySource(smith::DependsOn<0>{}, mesh->entireBodyName(), [](double /*t*/, auto /*x*/, auto u) { return u; }); - f_weak_form->addBodySource(mesh->entireBodyName(), [](double /*t*/, auto x) { return 0.5 * x; }); + f_weak_form->addBodySource(smith::DependsOn<>{}, mesh->entireBodyName(), + [](double /*t*/, auto x) { return 0.5 * x; }); // initialize fields for testing diff --git a/src/smith/physics/tests/test_kinematic_objective.cpp b/src/smith/physics/tests/test_kinematic_objective.cpp index 3523fa0785..60e4378cab 100644 --- a/src/smith/physics/tests/test_kinematic_objective.cpp +++ b/src/smith/physics/tests/test_kinematic_objective.cpp @@ -13,7 +13,7 @@ #include "mpi.h" #include "mfem.hpp" -#include "smith/differentiable_numerics/time_discretized_weak_form.hpp" +#include "smith/physics/functional_weak_form.hpp" #include "smith/physics/functional_objective.hpp" #include "smith/infrastructure/application_manager.hpp" #include "smith/physics/state/state_manager.hpp" @@ -38,9 +38,9 @@ struct ConstrainedWeakFormFixture : public testing::Test { using SolidMaterial = smith::solid_mechanics::NeoHookeanWithFieldDensity; using SolidWeakFormT = - smith::TimeDiscretizedWeakForm, - smith::Parameters, smith::H1, - smith::H1, DensitySpace>>; + smith::FunctionalWeakForm, + smith::Parameters, smith::H1, + smith::H1, DensitySpace>>; enum FIELD { From 5e83a33891dce6b2f4eb25ba290cfb4ae9859381 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 24 Apr 2026 12:35:01 -0700 Subject: [PATCH 42/67] Working on improving the zero-cycle boundary conditions and time integration. --- improvements.md | 3 +- .../dirichlet_boundary_conditions.cpp | 86 +++++++++++++++++-- .../dirichlet_boundary_conditions.hpp | 7 +- .../differentiable_numerics/field_store.cpp | 6 +- .../tests/test_solid_dynamics.cpp | 38 +++----- .../boundary_condition.hpp | 6 ++ 6 files changed, 106 insertions(+), 40 deletions(-) diff --git a/improvements.md b/improvements.md index 521f52494c..e4d253b705 100644 --- a/improvements.md +++ b/improvements.md @@ -10,7 +10,8 @@ 10. No item 10. 11. Done. Renamed `custom_solver` to `coupled_solver`; advanced example now uses `(0,1)`. 12. Done. `appendRemappedStages` renamed to `appendStagesWithBlockMapping`; test now uses `combined_solver` and `StaticTimeIntegrationRule`. -13. Look at `TransientFreefallWithConsistentBoundaryConditions`. Fixed BC is applied, but test is supposed to cover free fall. Boundary condition may need to be time-dependent and move with expected acceleration. It should probably test cycle-zero acceleration solve. +13. Done. `TransientFreefallWithConsistentBoundaryConditions` now applies time-dependent freefall displacement BCs and checks cycle-zero acceleration solve against the matching initial acceleration. +13.b. Lets make the test dynamic/transient again to also test the time integration is exactly integrating acceleration. If there are instabilities, we need to figure those out. Consider how to simplify the new boundary condition changes in the last commit. 14. In `_internal_vars` test, inline `auto [solid, internal_variables] = buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields);`. Rename `solid` to `solid_system` and `internal_variables` to `internal_variable_system`. 15. Like solid system, use `thermal_system->setTemperatureBcs`; make interface consistent with plurals. 16. Was `src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp` replaced by different test? diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp index 0ee42cdd0d..b719772849 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp @@ -7,9 +7,75 @@ #include "smith/physics/mesh.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" #include "smith/physics/boundary_conditions/boundary_condition_manager.hpp" +#include "smith/physics/boundary_conditions/boundary_condition.hpp" + +#include +#include +#include namespace smith { +namespace { + +constexpr double bc_time_fd_step = 1.0e-6; + +class SecondTimeDerivativeScalarCoefficient : public mfem::Coefficient { + public: + explicit SecondTimeDerivativeScalarCoefficient(std::shared_ptr source) : source_(std::move(source)) + { + } + + double Eval(mfem::ElementTransformation& T, const mfem::IntegrationPoint& ip) override + { + const double t0 = GetTime(); + const double h = std::max(bc_time_fd_step, std::sqrt(std::numeric_limits::epsilon()) * + std::max(1.0, std::abs(t0))); + source_->SetTime(t0); + const double f0 = source_->Eval(T, ip); + source_->SetTime(t0 + h); + const double f1 = source_->Eval(T, ip); + source_->SetTime(t0 + 2.0 * h); + const double f2 = source_->Eval(T, ip); + source_->SetTime(t0); + return (f2 - 2.0 * f1 + f0) / (h * h); + } + + private: + std::shared_ptr source_; +}; + +class SecondTimeDerivativeVectorCoefficient : public mfem::VectorCoefficient { + public: + explicit SecondTimeDerivativeVectorCoefficient(std::shared_ptr source) + : mfem::VectorCoefficient(source->GetVDim()), source_(std::move(source)) + { + } + + void Eval(mfem::Vector& V, mfem::ElementTransformation& T, const mfem::IntegrationPoint& ip) override + { + const double t0 = GetTime(); + const double h = std::max(bc_time_fd_step, std::sqrt(std::numeric_limits::epsilon()) * + std::max(1.0, std::abs(t0))); + mfem::Vector v0(vdim), v1(vdim), v2(vdim); + source_->SetTime(t0); + source_->Eval(v0, T, ip); + source_->SetTime(t0 + h); + source_->Eval(v1, T, ip); + source_->SetTime(t0 + 2.0 * h); + source_->Eval(v2, T, ip); + source_->SetTime(t0); + V = v2; + V.Add(-2.0, v1); + V += v0; + V /= (h * h); + } + + private: + std::shared_ptr source_; +}; + +} // namespace + DirichletBoundaryConditions::DirichletBoundaryConditions(const mfem::ParMesh& mfem_mesh, mfem::ParFiniteElementSpace& space) : bcs_(mfem_mesh), space_(space) @@ -21,17 +87,19 @@ DirichletBoundaryConditions::DirichletBoundaryConditions(const Mesh& mesh, mfem: { } -void DirichletBoundaryConditions::setZeroBCsMatchingDofs(const BoundaryConditionManager& source) +void DirichletBoundaryConditions::setSecondTimeDerivativeBCsMatchingDofs(const BoundaryConditionManager& source) { - const auto& true_dofs = source.allEssentialTrueDofs(); - if (true_dofs.Size() == 0) { - return; + for (const auto& bc : source.essentials()) { + if (is_vector_valued(bc.coefficient())) { + auto accel_coef = std::make_shared( + get>(bc.coefficient())); + bcs_.addEssentialByTrueDofs(bc.getTrueDofList(), accel_coef, space_); + } else { + auto accel_coef = std::make_shared( + get>(bc.coefficient())); + bcs_.addEssential(bc.getLocalDofList(), accel_coef, space_, bc.component()); + } } - int vdim = space_.GetVDim(); - mfem::Vector zero_vec(vdim); - zero_vec = 0.0; - auto zero_coef = std::make_shared(zero_vec); - bcs_.addEssentialByTrueDofs(true_dofs, zero_coef, space_); } } // namespace smith diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp index 54b68d5685..bdca440568 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp @@ -161,12 +161,13 @@ class DirichletBoundaryConditions { /// @brief Return the smith BoundaryConditionManager const smith::BoundaryConditionManager& getBoundaryConditionManager() const { return bcs_; } - /// @brief Constrain the same true DOFs as @p source, but with zero prescribed values. + /// @brief Constrain the same DOFs as @p source, prescribing the time second derivative of the source values. /// /// Used for the cycle-zero acceleration BC: the constrained DOF set mirrors the displacement BC - /// (same mesh nodes, same components), but the prescribed acceleration is zero everywhere. + /// (same mesh nodes, same components), and the prescribed acceleration is approximated by a + /// forward finite difference of the displacement boundary condition in time. /// Must be called after the user has finished calling @c set*BCs on @p source. - void setZeroBCsMatchingDofs(const BoundaryConditionManager& source); + void setSecondTimeDerivativeBCsMatchingDofs(const BoundaryConditionManager& source); private: smith::BoundaryConditionManager bcs_; ///< boundary condition manager that does the heavy lifting diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index a392bd11f0..22367dc2a3 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -142,9 +142,9 @@ std::vector FieldStore::getBoundaryConditionMan // Lazily materialize any pending zero-mirror BCs on first access. auto mirror_it = zero_mirror_sources_.find(reaction_name); if (mirror_it != zero_mirror_sources_.end()) { - auto zero_bc = addBoundaryConditions(getField(reaction_name).get()); - zero_bc->setZeroBCsMatchingDofs(mirror_it->second->getBoundaryConditionManager()); - boundary_conditions_[reaction_name] = std::move(zero_bc); + auto mirrored_bc = addBoundaryConditions(getField(reaction_name).get()); + mirrored_bc->setSecondTimeDerivativeBCsMatchingDofs(mirror_it->second->getBoundaryConditionManager()); + boundary_conditions_[reaction_name] = std::move(mirrored_bc); zero_mirror_sources_.erase(mirror_it); } diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 3929eebdbc..b073b3e1a9 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -275,40 +275,30 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi return b; }); - system->disp_bc->setFixedVectorBCs(mesh->entireBoundary(), std::vector{0, 2}); + system->disp_bc->setVectorBCs(mesh->entireBoundary(), [](double t, tensor /*X*/) { + tensor u{}; + u[1] = 0.5 * gravity * t * t; + return u; + }); - auto physics = makeDifferentiablePhysics(system, "freefall"); - for (size_t step = 0; step < num_steps_; ++step) { - physics->advanceTimestep(dt_); - } + ASSERT_NE(system->cycle_zero_system, nullptr); + auto cycle_zero_states = system->cycle_zero_system->solve(TimeInfo(0.0, dt_, 0)); + ASSERT_EQ(cycle_zero_states.size(), 1); - auto states = physics->getFieldStates(); - auto shape_disp = physics->getShapeDispFieldState(); - double time = num_steps_ * dt_; + auto shape_disp = system->field_store->getShapeDisp(); + double time = 0.0; double a_exact = gravity; - double v_exact = gravity * time; - double u_exact = 0.5 * gravity * time * time; - - auto vector_error = [&](const std::string& name, size_t state_index, double y_exact) { - auto state_vec = std::vector{states[state_index]}; + auto vector_error = [&](const std::string& name, const FieldState& state, double y_exact) { + auto state_vec = std::vector{state}; FunctionalObjective> error(name, mesh, spaces(state_vec)); error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [y_exact](auto /*t*/, auto /*X*/, auto U) { auto u = get(U); return u[0] * u[0] + (u[1] - y_exact) * (u[1] - y_exact) + u[2] * u[2]; }); - return error.evaluate(TimeInfo(0.0, dt_, num_steps_), shape_disp.get().get(), getConstFieldPointers(state_vec)); + return error.evaluate(TimeInfo(time, dt_, 0), shape_disp.get().get(), getConstFieldPointers(state_vec)); }; - EXPECT_NEAR(0.0, vector_error("freefall_acceleration_error", 3, a_exact), 1e-12); - EXPECT_NEAR(0.0, vector_error("freefall_velocity_error", 2, v_exact), 1e-12); - EXPECT_NEAR(0.0, vector_error("freefall_displacement_error", 0, u_exact), 1e-12); - - auto stress_it = std::find_if(states.begin(), states.end(), [](const auto& state) { - return state.get()->name().find("stress") != std::string::npos; - }); - ASSERT_NE(stress_it, states.end()); - double stress_norm = norm(*stress_it->get().get()); - EXPECT_LT(stress_norm, 1e-8); + EXPECT_NEAR(0.0, vector_error("freefall_cycle_zero_acceleration_error", cycle_zero_states[0], a_exact), 1e-10); } auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr mesh) diff --git a/src/smith/physics/boundary_conditions/boundary_condition.hpp b/src/smith/physics/boundary_conditions/boundary_condition.hpp index bd86ee91c1..cb0e939cfb 100644 --- a/src/smith/physics/boundary_conditions/boundary_condition.hpp +++ b/src/smith/physics/boundary_conditions/boundary_condition.hpp @@ -110,6 +110,12 @@ class BoundaryCondition { */ const mfem::Array& getLocalDofList() const { return local_dofs_; } + /// @brief Returns stored coefficient. + const GeneralCoefficient& coefficient() const { return coef_; } + + /// @brief Returns constrained vector component, if any. + const std::optional& component() const { return component_; } + /** * @brief Projects the associated coefficient over a solution vector on the DOFs constrained by the boundary condition * @param[in] time The time at which to project the boundary condition From 37a9c6e00e4cbcabfc303e7ec63b6e93a8e7beaf Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 25 Apr 2026 13:12:04 -0600 Subject: [PATCH 43/67] Working through various interface improvements. --- .../composable_thermo_mechanics.cpp | 2 +- .../composable_thermo_mechanics_advanced.cpp | 14 +- .../differentiable_numerics/CMakeLists.txt | 2 +- .../combined_system.hpp | 34 +---- .../coupling_params.hpp | 61 ++++---- .../differentiable_physics.cpp | 11 +- .../differentiable_physics.hpp | 3 +- .../dirichlet_boundary_conditions.cpp | 9 +- .../multiphysics_time_integrator.cpp | 35 +++-- .../multiphysics_time_integrator.hpp | 2 + .../nonlinear_block_solver.cpp | 6 +- .../solid_mechanics_system.hpp | 25 ++-- ...id_mechanics_with_internal_vars_system.hpp | 17 ++- .../state_variable_system.hpp | 93 ++---------- .../tests/test_combined_thermo_mechanics.cpp | 136 +++++++++--------- .../tests/test_solid_dynamics.cpp | 125 ++++++++-------- .../test_solid_static_with_internal_vars.cpp | 67 +++++---- .../tests/test_thermal_static.cpp | 6 +- ...st_thermo_mechanics_with_internal_vars.cpp | 67 +++------ .../thermal_system.hpp | 39 ++++- ...system.hpp => thermo_mechanics_system.hpp} | 20 +-- ...mo_mechanics_with_internal_vars_system.hpp | 77 ++++++++++ src/smith/numerics/solver_config.hpp | 3 + 23 files changed, 414 insertions(+), 440 deletions(-) rename src/smith/differentiable_numerics/{thermo_mechanical_system.hpp => thermo_mechanics_system.hpp} (84%) create mode 100644 src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index 0e45dcf6fa..f762eb0388 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -20,7 +20,7 @@ #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" -#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" #include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 949d32fc57..668bcf8ec5 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -25,7 +25,7 @@ #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" -#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/differentiable_numerics/paraview_writer.hpp" @@ -119,10 +119,12 @@ int main(int argc, char* argv[]) // _build_end // _qoi_start - auto qoi_fields = - std::vector{field_store->getField("displacement"), field_store->getField("temperature")}; + auto fetch_qoi_fields = [&]() { + return std::vector{field_store->getField("displacement"), + field_store->getField("temperature")}; + }; smith::FunctionalObjective> qoi("thermo_mechanical_energy_proxy", mesh, - smith::spaces(qoi_fields)); + smith::spaces(fetch_qoi_fields())); qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](const smith::TimeInfo&, auto /*X*/, auto U, auto Theta) { auto u = smith::get(U); @@ -131,7 +133,7 @@ int main(int argc, char* argv[]) }); auto qoi_state = - 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, + 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), fetch_qoi_fields(), smith::TimeInfo(physics->time(), 1.0, static_cast(physics->cycle()))); // _qoi_end @@ -141,7 +143,7 @@ int main(int argc, char* argv[]) for (int step = 0; step < qoi_steps; ++step) { physics->advanceTimestep(dt); qoi_state = qoi_state + - smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, + smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), fetch_qoi_fields(), smith::TimeInfo(physics->time(), dt, static_cast(physics->cycle()))); } // _run_end diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 534453fed8..8b36d9cdb4 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -40,7 +40,7 @@ set(differentiable_numerics_headers solid_mechanics_with_internal_vars_system.hpp state_variable_system.hpp thermal_system.hpp - thermo_mechanical_system.hpp + thermo_mechanics_system.hpp coupling_params.hpp combined_system.hpp system_base.hpp diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index be540c411c..d63a355c7f 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -18,10 +18,10 @@ * auto solid_fields = registerSolidMechanicsFields(field_store); * auto thermal_fields = registerThermalFields(field_store); * - * auto solid = buildSolidMechanicsSystem( + * auto solid_system = buildSolidMechanicsSystem( * solid_solver, solid_opts, solid_fields, params..., thermal_fields); * - * auto thermal = buildThermalSystem( + * auto thermal_system = buildThermalSystem( * thermal_solver, thermal_opts, thermal_fields, solid_fields); * * auto coupled = combineSystems(solid, thermal); @@ -163,18 +163,9 @@ auto combineSystems(std::shared_ptr... subs) } /** - * @brief A generic wrapper that combines multiple sub-systems into a single monolithic block system. + * @brief Combine two or more independently-built sub-systems into a single monolithic SystemBase. * - * Unlike CombinedSystem (which performs staggered solver iterations), MonolithicCombinedSystem - * concatenates all weak forms and solves them simultaneously using a single global SystemSolver. - */ -struct MonolithicCombinedSystem : public SystemBase { - /// @brief Inherit `SystemBase` constructors for monolithic wrappers. - using SystemBase::SystemBase; -}; - -/** - * @brief Combine two or more independently-built sub-systems into a MonolithicCombinedSystem. + * All sub-system weak forms are concatenated and solved simultaneously by @p solver. * * Preconditions: * - All sub-systems share the same FieldStore. @@ -185,7 +176,7 @@ struct MonolithicCombinedSystem : public SystemBase { * @param subs Two or more sub-systems that share a FieldStore. */ template -auto combineSystems(std::shared_ptr solver, std::shared_ptr... subs) +std::shared_ptr combineSystems(std::shared_ptr solver, std::shared_ptr... subs) { static_assert(sizeof...(subs) >= 2, "combineSystems requires at least two sub-systems"); @@ -214,7 +205,7 @@ auto combineSystems(std::shared_ptr solver, std::shared_ptr(field_store, solver, wfs); + auto combined = std::make_shared(field_store, solver, wfs); std::shared_ptr cycle_zero_combined = nullptr; if (!cycle_zero_wfs.empty()) { cycle_zero_combined = std::make_shared(field_store, solver, cycle_zero_wfs); @@ -226,17 +217,4 @@ auto combineSystems(std::shared_ptr solver, std::shared_ptr -std::vector> mergeSystems(Vectors&&... vectors) -{ - std::vector> result; - (result.insert(result.end(), vectors.begin(), vectors.end()), ...); - return result; -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 03881c5e07..555a2e7399 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -140,19 +140,9 @@ inline constexpr bool has_time_rule_v // Helpers for variadic build functions // ------------------------------------------------------------------------- -/// Extract field_store from the first PhysicsFields pack. -template -std::shared_ptr findFieldStore(const First& first, const Rest&... rest) -{ - if constexpr (is_physics_fields_v) { - return first.field_store; - } else { - static_assert(sizeof...(Rest) > 0, "No PhysicsFields pack found — at least one is required"); - return findFieldStore(rest...); - } -} - -/// Extract physics coupling fields (non-self PhysicsFields packs). +/** + * @brief selects foreign `PhysicsFields` (skip self by rule match); keeps field names. + */ template auto collectPhysicsFromPack(const Pack& pack) { @@ -167,8 +157,9 @@ auto collectPhysicsFromPack(const Pack& pack) } } -/// Extract parameter fields (CouplingParams packs that are NOT PhysicsFields) with fully-qualified -/// names matching what the FieldStore uses: `{store_prefix}param_{name}`. +/** + * @brief selects pure `CouplingParams` (not `PhysicsFields`); rewrites names to `{prefix}param_{name}`. + */ template auto collectParamsFromPack(const std::shared_ptr& fs, const Pack& pack) { @@ -187,8 +178,9 @@ auto collectParamsFromPack(const std::shared_ptr& fs, const Pack& pa } } -/// Collect non-self fields from all packs into a single CouplingParams. -/// Order: physics coupling fields first (in pack order), then parameter fields. +/** + * @brief concatenates physics then params into a single `CouplingParams`. + */ template auto collectCouplingFields(const std::shared_ptr& fs, const Packs&... packs) { @@ -215,29 +207,26 @@ void registerParamsIfNeeded(std::shared_ptr fs, const Pack& pack) } } -/** - * @brief Produce TimeRuleParams from a CouplingParams. - * - * Inserts coupling spaces immediately after the num_states copies of Space (the time-rule - * state fields), and before the user-supplied Tail types (parameter_space...). - */ -template -struct TimeRuleParamsWithCoupling; - -template -/// @brief Specialization for coupling packs expressed as `CouplingParams`. -struct TimeRuleParamsWithCoupling, Tail...> { - using type = TimeRuleParams; ///< Expanded parameter list with coupling inserted after rule states. +/// @brief Build the weak-form parameter list for a time rule and coupling pack. +/// +/// Unpacks Coupling (either CouplingParams or PhysicsFields) and produces +/// TimeRuleParams, i.e. num_states copies of Space followed by the coupling field types. +template +struct TimeRuleParamsHelper; + +template +struct TimeRuleParamsHelper> { + using type = smith::TimeRuleParams; }; -template -/// @brief Specialization for coupling packs expressed as `PhysicsFields`. -struct TimeRuleParamsWithCoupling, Tail...> { - using type = TimeRuleParams; ///< Expanded parameter list with coupling inserted after rule states. +template +struct TimeRuleParamsHelper> { + using type = smith::TimeRuleParams; }; +template +using TimeRuleParams = typename TimeRuleParamsHelper::type; + /** * @brief Append coupling spaces (CS...) and Tail... onto a base Parameters type. * diff --git a/src/smith/differentiable_numerics/differentiable_physics.cpp b/src/smith/differentiable_numerics/differentiable_physics.cpp index 430979d5c2..9e95173fbb 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.cpp +++ b/src/smith/differentiable_numerics/differentiable_physics.cpp @@ -120,13 +120,12 @@ const FiniteElementState& DifferentiablePhysics::state([[maybe_unused]] const st const FiniteElementDual& DifferentiablePhysics::dual(const std::string& reaction_name) const { if (reaction_name_to_reaction_index_.find(reaction_name) == reaction_name_to_reaction_index_.end()) { - std::cout << "\n[DEBUG] Available reactions (size " << reaction_names_.size() << "): "; - for (auto& n : reaction_names_) std::cout << n << " "; - std::cout << std::endl; + std::string available; + for (auto& n : reaction_names_) available += n + " "; + SLIC_ERROR(axom::fmt::format( + "Could not find reaction named {0} in mesh with tag \"{1}\" to get. Available reactions (size {2}): {3}", + reaction_name, mesh_->tag(), reaction_names_.size(), available)); } - SLIC_ERROR_IF(reaction_name_to_reaction_index_.find(reaction_name) == reaction_name_to_reaction_index_.end(), - axom::fmt::format("Could not find reaction named {0} in mesh with tag \"{1}\" to get", reaction_name, - mesh_->tag())); size_t reaction_index = reaction_name_to_reaction_index_.at(reaction_name); SLIC_ERROR_IF(reaction_states_.empty() && !reaction_names_.empty(), diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index 464d444ef3..623861c8c7 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -228,8 +228,7 @@ template std::unique_ptr makeDifferentiablePhysics(std::shared_ptr system, const std::string& physics_name) { - return makeDifferentiablePhysics(system, makeAdvancer(system, system->cycle_zero_system, system->post_solve_systems), - physics_name); + return makeDifferentiablePhysics(system, makeAdvancer(system), physics_name); } } // namespace smith diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp index b719772849..15006e8b24 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp @@ -11,13 +11,12 @@ #include #include -#include namespace smith { namespace { -constexpr double bc_time_fd_step = 1.0e-6; +constexpr double bc_time_fd_step = 1.0e-4; class SecondTimeDerivativeScalarCoefficient : public mfem::Coefficient { public: @@ -28,8 +27,7 @@ class SecondTimeDerivativeScalarCoefficient : public mfem::Coefficient { double Eval(mfem::ElementTransformation& T, const mfem::IntegrationPoint& ip) override { const double t0 = GetTime(); - const double h = std::max(bc_time_fd_step, std::sqrt(std::numeric_limits::epsilon()) * - std::max(1.0, std::abs(t0))); + const double h = bc_time_fd_step * std::max(1.0, std::abs(t0)); source_->SetTime(t0); const double f0 = source_->Eval(T, ip); source_->SetTime(t0 + h); @@ -54,8 +52,7 @@ class SecondTimeDerivativeVectorCoefficient : public mfem::VectorCoefficient { void Eval(mfem::Vector& V, mfem::ElementTransformation& T, const mfem::IntegrationPoint& ip) override { const double t0 = GetTime(); - const double h = std::max(bc_time_fd_step, std::sqrt(std::numeric_limits::epsilon()) * - std::max(1.0, std::abs(t0))); + const double h = bc_time_fd_step * std::max(1.0, std::abs(t0)); mfem::Vector v0(vdim), v1(vdim), v2(vdim); source_->SetTime(t0); source_->Eval(v0, T, ip); diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index fd5f32da81..01fe396670 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -21,6 +21,11 @@ MultiphysicsTimeIntegrator::MultiphysicsTimeIntegrator(std::shared_ptr> post_solve_systems) : system_(system), cycle_zero_system_(cycle_zero_system), post_solve_systems_(std::move(post_solve_systems)) { + for (size_t i = 0; i < system_->weak_forms.size(); ++i) { + const std::string wf_name = system_->weak_forms[i]->name(); + const std::string reaction_name = system_->field_store->getWeakFormReaction(wf_name); + main_unknown_name_to_local_idx_[reaction_name] = i; + } } void MultiphysicsTimeIntegrator::addPostSolveSystem(std::shared_ptr system) @@ -74,18 +79,12 @@ std::pair, std::vector> MultiphysicsTimeI // Entries in the shared FieldStore's time integration rules that belong to post-solve // subsystems (e.g. stress projection) are NOT present here and must be skipped by downstream // lookups that walk getTimeIntegrationRules(). - std::map main_unknown_name_to_local_idx; - for (size_t i = 0; i < system_->weak_forms.size(); ++i) { - const std::string wf_name = system_->weak_forms[i]->name(); - const std::string reaction_name = system_->field_store->getWeakFormReaction(wf_name); - main_unknown_name_to_local_idx[reaction_name] = i; - } // Create states for reaction computation: newly solved primary unknowns + current states std::vector states_for_reactions = current_states; for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { - auto it = main_unknown_name_to_local_idx.find(mapping.primary_name); - if (it == main_unknown_name_to_local_idx.end()) { + auto it = main_unknown_name_to_local_idx_.find(mapping.primary_name); + if (it == main_unknown_name_to_local_idx_.end()) { continue; // rule belongs to a post-solve subsystem, not the main solve } size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); @@ -99,8 +98,8 @@ std::pair, std::vector> MultiphysicsTimeI // Sync field_store with newly solved primary unknowns so post-solve systems (e.g. stress // projection) read the current displacement rather than the pre-solve snapshot. for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { - auto it = main_unknown_name_to_local_idx.find(mapping.primary_name); - if (it == main_unknown_name_to_local_idx.end()) { + auto it = main_unknown_name_to_local_idx_.find(mapping.primary_name); + if (it == main_unknown_name_to_local_idx_.end()) { continue; } size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); @@ -118,16 +117,14 @@ std::pair, std::vector> MultiphysicsTimeI } } - // Now do time integration to compute corrected velocities/accelerations and update all states - const auto& all_current_states = system_->field_store->getAllFields(); - std::vector new_states = current_states; - for (size_t i = 0; i < current_states.size(); ++i) { - new_states[i] = all_current_states[i]; - } + // Now do time integration to compute corrected velocities/accelerations and update all states. + // Seed new_states from field_store, which already reflects post-solve subsystem updates and + // the freshly synced primary unknowns. + std::vector new_states = system_->field_store->getAllFields(); for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { - auto it = main_unknown_name_to_local_idx.find(mapping.primary_name); - if (it == main_unknown_name_to_local_idx.end()) { + auto it = main_unknown_name_to_local_idx_.find(mapping.primary_name); + if (it == main_unknown_name_to_local_idx_.end()) { continue; // rule belongs to a post-solve subsystem, not the main solve } size_t u_idx = system_->field_store->getFieldIndex(mapping.primary_name); @@ -170,7 +167,7 @@ std::pair, std::vector> MultiphysicsTimeI // The main loop skipped these rules; their primary fields are already correct in new_states // (populated from all_current_states above), so only the history field needs updating. for (const auto& [rule, mapping] : system_->field_store->getTimeIntegrationRules()) { - if (main_unknown_name_to_local_idx.count(mapping.primary_name)) { + if (main_unknown_name_to_local_idx_.count(mapping.primary_name)) { continue; // already handled by main time integration loop above } if (!mapping.history_name.empty()) { diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 208a23b356..44386a960a 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -56,6 +56,8 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { std::shared_ptr system_; std::shared_ptr cycle_zero_system_; std::vector> post_solve_systems_; + + std::map main_unknown_name_to_local_idx_; }; /** diff --git a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp index eceff3ac72..ea570dfa9c 100644 --- a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp +++ b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp @@ -83,7 +83,11 @@ void NonlinearBlockSolver::completeSetup(const std::vector& us) auto* amg_prec = dynamic_cast(mfem_solver); if (amg_prec) { - amg_prec->SetSystemsOptions(best->space().GetVDim(), smith::ordering == mfem::Ordering::byNODES); + if (retained_linear_options_->amg_elasticity) { + amg_prec->SetElasticityOptions(const_cast(&best->space())); + } else { + amg_prec->SetSystemsOptions(best->space().GetVDim(), smith::ordering == mfem::Ordering::byNODES); + } } #ifdef SMITH_USE_PETSC diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 5cd33c2025..4bf896363c 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -66,12 +66,13 @@ struct SolidMechanicsSystem : public SystemBase { static_assert(DisplacementTimeRule::num_states == 4, "SolidMechanicsSystem requires a 4-state time integration rule"); /// Main weak form: (u, u_old, v_old, a_old, coupling_fields..., params...) - using SolidWeakFormType = FunctionalWeakForm< - dim, H1, - typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; + using SolidWeakFormType = + FunctionalWeakForm, detail::TimeRuleParams, Coupling>>; - /// Cycle-zero startup form reusing the main solid fields: (u, v_old, a, coupling_fields..., params...) - /// No extra fields are registered for cycle zero; acceleration is the unknown for this internal solve. + /// Cycle-zero startup form reusing the main solid fields: (u, v, a, coupling_fields..., params...) + /// At cycle 0 the velocity field holds the initial velocity (no prior step has run), so the + /// second argument is `v` (initial), not `v_old`. Acceleration is the unknown for this internal solve. + /// No extra fields are registered for cycle zero. using CycleZeroSolidWeakFormType = FunctionalWeakForm, typename detail::AppendCouplingToParams< @@ -176,8 +177,8 @@ struct SolidMechanicsSystem : public SystemBase { }); addCycleZeroBodySourceImpl( - depends_on, domain_name, [=](auto t_info, auto X, auto u, auto v_old, auto a, auto... params) { - return detail::scaleCycleZeroTerm(t_info, force_function(t_info.time(), X, u, v_old, a, params...)); + depends_on, domain_name, [=](auto t_info, auto X, auto u, auto v, auto a, auto... params) { + return detail::scaleCycleZeroTerm(t_info, force_function(t_info.time(), X, u, v, a, params...)); }); } @@ -214,8 +215,8 @@ struct SolidMechanicsSystem : public SystemBase { }); addCycleZeroBoundaryFluxImpl( - depends_on, domain_name, [=](auto t_info, auto X, auto n, auto u, auto v_old, auto a, auto... params) { - return detail::scaleCycleZeroTerm(t_info, traction_function(t_info.time(), X, n, u, v_old, a, params...)); + depends_on, domain_name, [=](auto t_info, auto X, auto n, auto u, auto v, auto a, auto... params) { + return detail::scaleCycleZeroTerm(t_info, traction_function(t_info.time(), X, n, u, v, a, params...)); }); } @@ -532,7 +533,7 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons * * Usage: * @code - * auto solid = buildSolidMechanicsSystem( + * auto solid_system = buildSolidMechanicsSystem( * solver, opts, solid_fields, youngs_modulus, thermal_fields); * @endcode */ @@ -558,7 +559,7 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid * * Usage: * @code - * auto solid = buildSolidMechanicsSystem( + * auto solid_system = buildSolidMechanicsSystem( * solver, opts, solid_fields, youngs_modulus, thermal_fields); * @endcode */ @@ -579,7 +580,7 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid * * Usage: * @code - * auto solid = buildSolidMechanicsSystem( + * auto solid_system = buildSolidMechanicsSystem( * nonlin_opts, lin_opts, field_store, opts, param_fields, thermal_fields); * @endcode */ diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index 6d6686fb86..acb020815b 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -108,13 +108,12 @@ void setCoupledInternalVariableMaterial( const std::string& domain_name) { auto captured_disp_rule = solid->disp_time_rule; - internal_variables->setMaterial( - [=](auto t_info, auto alpha, auto alpha_dot, auto u, auto u_old, auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - return detail::evaluateCoupledInternalVariableMaterial(material, t_info, alpha, alpha_dot, - get(u_current), params...); - }, - domain_name); + internal_variables->addEvolution(domain_name, [=](auto t_info, auto alpha, auto alpha_dot, auto u, auto u_old, + auto v_old, auto a_old, auto... params) { + auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + return detail::evaluateCoupledInternalVariableMaterial(material, t_info, alpha, alpha_dot, + get(u_current), params...); + }); } template > solid, - std::shared_ptr> state, const MaterialType& material, - const std::string& domain_name) + std::shared_ptr> state, + const MaterialType& material, const std::string& domain_name) { setCoupledSolidMechanicsInternalVariableMaterial(solid, state, material, domain_name); } diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index af39300638..429da445f4 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -12,14 +12,14 @@ * auto internal_variable_fields = registerInternalVariableFields( * field_store, params...); * - * auto internal_variables = buildInternalVariableSystem( + * auto internal_variable_system = buildInternalVariableSystem( * solver, opts, internal_variable_fields, params..., solid_fields); * * The returned PhysicsFields from registerInternalVariableFields carries field tokens * (state_solve_state, state) that can be injected into another physics system * (e.g. SolidMechanicsSystem) as coupling input. * - * The system's setMaterial registers an ODE residual of the form: + * addEvolution registers an ODE residual of the form: * evolution_law(t_info, alpha_val, alpha_dot, coupling_fields..., params...) == 0 * * With Coupling = CouplingParams, H1, H1, H1> @@ -65,8 +65,7 @@ struct InternalVariableSystem : public SystemBase { /// State weak form: (alpha, alpha_old, coupling_fields..., params...) using InternalVariableWeakFormType = - FunctionalWeakForm::type>; + FunctionalWeakForm>; std::shared_ptr internal_variable_weak_form; ///< Internal variable weak form. std::shared_ptr internal_variable_bc; ///< Internal variable BCs. @@ -98,31 +97,8 @@ struct InternalVariableSystem : public SystemBase { return smith::tuple{residual_val, flux}; }); } - - template - /// @brief Register an internal-variable material or evolution law on a domain. - void setMaterial(const MaterialType& material, const std::string& domain_name) - { - addEvolution(domain_name, material); - } - - template - /// @brief Backward-compatible alias for `addEvolution`. - void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) - { - addEvolution(domain_name, evolution_law); - } }; -// --------------------------------------------------------------------------- -// Options -// --------------------------------------------------------------------------- - -/// @brief Options for building an `InternalVariableSystem`. -struct InternalVariableOptions {}; -/// @brief Backward-compatible alias for `InternalVariableOptions`. -using StateVariableOptions = InternalVariableOptions; - // --------------------------------------------------------------------------- // Phase 1: registerInternalVariableFields // --------------------------------------------------------------------------- @@ -179,7 +155,7 @@ namespace detail { template requires detail::is_coupling_params_v auto buildInternalVariableSystemImpl(std::shared_ptr field_store, const Coupling& coupling, - std::shared_ptr solver, const InternalVariableOptions& /*options*/) + std::shared_ptr solver) { auto internal_variable_time_rule = std::make_shared(); @@ -212,66 +188,21 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co } // namespace detail -/** - * @brief Build an internal-variable system from explicitly typed field packs. - */ -template - requires(detail::is_physics_fields_v && - std::is_same_v::time_rule_type, InternalVarTimeRule> && - (detail::is_coupling_params_v && ...)) -auto buildInternalVariableSystem(std::shared_ptr solver, const InternalVariableOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) -{ - auto field_store = self_fields.field_store; - (detail::registerParamsIfNeeded(field_store, other_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); - return detail::buildInternalVariableSystemImpl(field_store, coupling, solver, - options); -} - -/** - * @brief Backward-compatible alias for `buildInternalVariableSystem`. - */ -template - requires(detail::is_physics_fields_v && - std::is_same_v::time_rule_type, InternalVarTimeRule> && - (detail::is_coupling_params_v && ...)) -auto buildStateVariableSystem(std::shared_ptr solver, const StateVariableOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) -{ - return buildInternalVariableSystem(solver, options, self_fields, - other_packs...); -} - /** * @brief Build an internal-variable system from registered field packs. + * + * The time rule is deduced from SelfFields::time_rule_type. */ template requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) -auto buildInternalVariableSystem(std::shared_ptr solver, const InternalVariableOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) -{ - using InternalVarTimeRule = typename std::decay_t::time_rule_type; - return buildInternalVariableSystem(solver, options, self_fields, - other_packs...); -} - -/** - * @brief Backward-compatible alias for `buildInternalVariableSystem`. - */ -template - requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) -auto buildStateVariableSystem(std::shared_ptr solver, const StateVariableOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) +auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields, + const OtherPacks&... other_packs) { using InternalVarTimeRule = typename std::decay_t::time_rule_type; - return buildInternalVariableSystem(solver, options, self_fields, - other_packs...); + auto field_store = self_fields.field_store; + (detail::registerParamsIfNeeded(field_store, other_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + return detail::buildInternalVariableSystemImpl(field_store, coupling, solver); } -template > -/// @brief Backward-compatible alias for `InternalVariableSystem`. -using StateVariableSystem = InternalVariableSystem; - } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 9329eaf6ff..6c1456db1c 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -19,7 +19,7 @@ #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" -#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" @@ -58,13 +58,14 @@ struct ThermoMechanicsMeshFixture : public testing::Test { // Advance one step, return final states + lateral deflection. template - double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled, double dt = 1.0) + double advanceOneStepAndGetLateralDeflection(std::shared_ptr coupled_system, double dt = 1.0) { auto shape_disp = field_store_->getShapeDisp(); auto params = field_store_->getParameterFields(); std::vector reactions; - std::tie(std::ignore, reactions) = makeAdvancer(coupled)->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, - field_store_->getStateFields(), params); + std::tie(std::ignore, reactions) = + makeAdvancer(coupled_system) + ->advanceState(smith::TimeInfo(0.0, dt, 0), shape_disp, field_store_->getStateFields(), params); mfem::Vector final_disp(*field_store_->getField("displacement").get()); double deflection = 0.0; @@ -114,14 +115,14 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( + auto solid_system = buildSolidMechanicsSystem( makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); - auto thermal = buildThermalSystem( + auto thermal_system = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); - auto coupled = combineSystems(solid, thermal); - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); + auto coupled_system = combineSystems(solid_system, thermal_system); + auto physics = makeDifferentiablePhysics(coupled_system, "coupled_physics"); const auto& solid_dual_space = physics->dual("reactions").space(); const auto& solid_state_space = physics->state("displacement").space(); const auto& thermal_dual_space = physics->dual("thermal_flux").space(); @@ -149,30 +150,30 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( + auto solid_system = buildSolidMechanicsSystem( makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); - auto thermal = buildThermalSystem( + auto thermal_system = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); - auto coupled = combineSystems(solid, thermal); + auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); - coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( + coupled_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( [=](smith::tensor) { return 1.0; }); - solid->setDisplacementBC(mesh_->domain("left")); - thermal->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); + solid_system->setDisplacementBC(mesh_->domain("left")); + thermal_system->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); - solid->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { + solid_system->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { auto traction = 0.0 * X; traction[0] = -0.015; return traction; }); - auto physics = makeDifferentiablePhysics(coupled, "coupled_physics"); + auto physics = makeDifferentiablePhysics(coupled_system, "coupled_physics"); double dt = 1.0; for (int step = 0; step < 2; ++step) { @@ -185,7 +186,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) gretl::set_as_objective(obj); obj.data_store().back_prop(); - auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + auto param_sens = coupled_system->field_store->getParameterFields()[0].get_dual(); EXPECT_TRUE(param_sens->Norml2() > 0.0); } @@ -212,39 +213,40 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) .max_iterations = 12, .max_line_search_iterations = 6}; - auto solid = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - param_fields, thermal_fields); - auto thermal = + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + param_fields, thermal_fields); + auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, param_fields, solid_fields); auto coupled_solver = std::make_shared(10); coupled_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); coupled_solver->addSubsystemSolver({1}, buildNonlinearBlockSolver(thermal_nonlin_opts, thermal_lin_opts, *mesh_)); - auto coupled = combineSystems(coupled_solver, solid, thermal); + auto coupled_system = combineSystems(coupled_solver, solid_system, thermal_system); thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); - coupled->field_store->getParameterFields()[0].get()->setFromFieldFunction( + coupled_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( [=](smith::tensor) { return 1.0; }); - solid->setDisplacementBC(mesh_->domain("left")); - thermal->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); - thermal->setTemperatureBC(mesh_->domain("right"), [](auto, auto) { return 0.0; }); + solid_system->setDisplacementBC(mesh_->domain("left")); + thermal_system->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); + thermal_system->setTemperatureBC(mesh_->domain("right"), [](auto, auto) { return 0.0; }); - solid->addTraction(DependsOn<>{}, "right", [=](double, auto X, auto, auto, auto, auto) { + solid_system->addTraction(DependsOn<>{}, "right", [=](double, auto X, auto, auto, auto, auto) { auto traction = 0.0 * X; traction[0] = -0.005; return traction; }); - thermal->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return 0.1; }); + thermal_system->addHeatSource(mesh_->entireBodyName(), [=](auto, auto, auto, auto... /*args*/) { return 0.1; }); - auto physics = makeDifferentiablePhysics(coupled, "staggered_coupled_physics"); + auto physics = makeDifferentiablePhysics(coupled_system, "staggered_coupled_physics"); FunctionalObjective, H1>> qoi( "staggered_qoi", mesh_, - spaces({coupled->field_store->getField("displacement"), coupled->field_store->getField("temperature")})); + spaces({coupled_system->field_store->getField("displacement"), + coupled_system->field_store->getField("temperature")})); qoi.addBodyIntegral(DependsOn<0, 1>{}, mesh_->entireBodyName(), [](double, auto, auto U, auto Theta) { auto u = get(U); auto theta = get(Theta); @@ -252,15 +254,15 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) }); physics->advanceTimestep(0.5); - auto qoi_fields = std::vector{coupled->field_store->getField("displacement"), - coupled->field_store->getField("temperature")}; + auto qoi_fields = std::vector{coupled_system->field_store->getField("displacement"), + coupled_system->field_store->getField("temperature")}; auto obj = smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, TimeInfo(physics->time(), 0.5, static_cast(physics->cycle()))); gretl::set_as_objective(obj); obj.data_store().back_prop(); - auto param_sens = coupled->field_store->getParameterFields()[0].get_dual(); + auto param_sens = coupled_system->field_store->getParameterFields()[0].get_dual(); EXPECT_TRUE(param_sens->Norml2() > 0.0); } @@ -293,18 +295,18 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem( + auto solid_system = buildSolidMechanicsSystem( makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, thermal_fields); - auto thermal = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), - ThermalOptions{}, thermal_fields, solid_fields); + auto thermal_system = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), + ThermalOptions{}, thermal_fields, solid_fields); - auto coupled = combineSystems(solid, thermal); + auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); - applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); + applyBucklingLoads(solid_system, thermal_system, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - double deflection = advanceOneStepAndGetLateralDeflection(coupled); + double deflection = advanceOneStepAndGetLateralDeflection(coupled_system); EXPECT_GT(deflection, 1e-5); } @@ -322,17 +324,18 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - thermal_fields); - auto thermal = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + thermal_fields); + auto thermal_system = + buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); - auto coupled = combineSystems(solver_ptr, solid, thermal); + auto coupled_system = combineSystems(solver_ptr, solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; - setCoupledThermoMechanicsMaterial(solid, thermal, material, mesh_->entireBodyName()); + setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); - applyBucklingLoads(solid, thermal, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); + applyBucklingLoads(solid_system, thermal_system, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); - double deflection = advanceOneStepAndGetLateralDeflection(coupled); + double deflection = advanceOneStepAndGetLateralDeflection(coupled_system); EXPECT_GT(deflection, 1e-5); } @@ -344,28 +347,29 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress"))); - auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, - solid_fields); - ASSERT_EQ(sys->post_solve_systems.size(), 1u); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + solid_opts, solid_fields); + ASSERT_EQ(solid_system->post_solve_systems.size(), 1u); constexpr double E = 100.0; constexpr double nu = 0.25; constexpr double G = E / (2.0 * (1.0 + nu)); constexpr double K = E / (3.0 * (1.0 - 2.0 * nu)); - sys->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, mesh_->entireBodyName()); + solid_system->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, + mesh_->entireBodyName()); - sys->setDisplacementBC(mesh_->domain("left")); - sys->addTraction("right", [](double, auto X, auto, auto, auto, auto) { + solid_system->setDisplacementBC(mesh_->domain("left")); + solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto) { auto t = 0.0 * X; t[0] = -0.01; return t; }); - auto physics = makeDifferentiablePhysics(sys, "cauchy_physics"); + auto physics = makeDifferentiablePhysics(solid_system, "cauchy_physics"); physics->advanceTimestep(1.0); - ASSERT_FALSE(sys->post_solve_systems.empty()) << "Stress output system should be present"; + ASSERT_FALSE(solid_system->post_solve_systems.empty()) << "Stress output system should be present"; auto states = physics->getFieldStates(); size_t stress_idx = field_store_->getFieldIndex("stress"); double stress_norm = norm(*states[stress_idx].get()); @@ -377,9 +381,9 @@ TEST_F(ThermoMechanicsMeshFixture, StressOutputRegistrationDisabledByDefault) auto solid_fields = registerSolidMechanicsFields(field_store_); EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress"))); - auto sys = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields); - EXPECT_TRUE(sys->post_solve_systems.empty()); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields); + EXPECT_TRUE(solid_system->post_solve_systems.empty()); } TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) @@ -389,15 +393,15 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto thermal_fields = registerThermalFields(field_store_); - auto solid = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - solid_opts, solid_fields, thermal_fields); - auto thermal = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), - ThermalOptions{}, thermal_fields, solid_fields); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + solid_opts, solid_fields, thermal_fields); + auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), + ThermalOptions{}, thermal_fields, solid_fields); - auto combined = combineSystems(solid, thermal); + auto combined_system = combineSystems(solid_system, thermal_system); - ASSERT_EQ(combined->post_solve_systems.size(), solid->post_solve_systems.size()); - EXPECT_FALSE(combined->post_solve_systems.empty()); + ASSERT_EQ(combined_system->post_solve_systems.size(), solid_system->post_solve_systems.size()); + EXPECT_FALSE(combined_system->post_solve_systems.empty()); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index b073b3e1a9..c4a9d7d935 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -104,16 +104,17 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { auto field_store = std::make_shared(mesh, 100, "impl"); using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - auto sys = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - SolidMechanicsOptions{}, field_store); - EXPECT_NE(sys->cycle_zero_system, nullptr) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; + auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + SolidMechanicsOptions{}, field_store); + EXPECT_NE(solid_system->cycle_zero_system, nullptr) + << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { auto field_store = std::make_shared(mesh, 100, "qs"); using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; - auto sys = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - SolidMechanicsOptions{}, field_store); - EXPECT_EQ(sys->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; + auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + SolidMechanicsOptions{}, field_store); + EXPECT_EQ(solid_system->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; } } @@ -126,12 +127,12 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverUsesOwnedSingleStepPolicy) auto field_store = std::make_shared(mesh, 100, "cycle_zero_policy"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto solid_system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); - ASSERT_NE(system->cycle_zero_system, nullptr); - ASSERT_NE(system->cycle_zero_system->solver, nullptr); - EXPECT_EQ(system->cycle_zero_system->solver->maxStaggeredIterations(), 1); - EXPECT_TRUE(system->cycle_zero_system->solver->exactStaggeredSteps()); + ASSERT_NE(solid_system->cycle_zero_system, nullptr); + ASSERT_NE(solid_system->cycle_zero_system->solver, nullptr); + EXPECT_EQ(solid_system->cycle_zero_system->solver->maxStaggeredIterations(), 1); + EXPECT_TRUE(solid_system->cycle_zero_system->solver->exactStaggeredSteps()); } TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver) @@ -139,12 +140,12 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver auto field_store = std::make_shared(mesh, 100, "cycle_zero_fallback"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); - ASSERT_NE(system->cycle_zero_system, nullptr); - ASSERT_NE(system->cycle_zero_system->solver, nullptr); - EXPECT_EQ(system->cycle_zero_system->solver->maxStaggeredIterations(), 1); - EXPECT_FALSE(system->cycle_zero_system->solver->exactStaggeredSteps()); + ASSERT_NE(solid_system->cycle_zero_system, nullptr); + ASSERT_NE(solid_system->cycle_zero_system->solver, nullptr); + EXPECT_EQ(solid_system->cycle_zero_system->solver->maxStaggeredIterations(), 1); + EXPECT_FALSE(solid_system->cycle_zero_system->solver->exactStaggeredSteps()); } TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) @@ -156,9 +157,9 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - SolidMechanicsOptions{.enable_stress_output = true}, - field_store, param_fields); + auto solid_system = buildSolidMechanicsSystem( + solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.enable_stress_output = true}, field_store, + param_fields); static constexpr double gravity = -9.0; @@ -170,27 +171,27 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) MaterialType material{.density = 1.0, .K0 = K, .G0 = G}; // Set parameters - auto params = system->field_store->getParameterFields(); + auto params = solid_system->field_store->getParameterFields(); params[0].get()->setFromFieldFunction([=](tensor) { return material.K0; }); params[1].get()->setFromFieldFunction([=](tensor) { return material.G0; }); - system->setMaterial(material, mesh->entireBodyName()); + solid_system->setMaterial(material, mesh->entireBodyName()); // Add gravity body force - system->addBodyForce(mesh->entireBodyName(), - [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/, auto... /*params*/) { - tensor b{}; - b[1] = gravity; - return b; - }); + solid_system->addBodyForce(mesh->entireBodyName(), + [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/, auto... /*params*/) { + tensor b{}; + b[1] = gravity; + return b; + }); // Add dummy traction to test compilation - system->addTraction("right", [](double /*time*/, auto /*X*/, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, - auto... /*params*/) { return tensor{}; }); + solid_system->addTraction("right", [](double /*time*/, auto /*X*/, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, + auto... /*params*/) { return tensor{}; }); - auto shape_disp = system->field_store->getShapeDisp(); - auto states = system->field_store->getStateFields(); - auto output_states = system->field_store->getOutputFieldStates(); + auto shape_disp = solid_system->field_store->getShapeDisp(); + auto states = solid_system->field_store->getStateFields(); + auto output_states = solid_system->field_store->getOutputFieldStates(); std::string pv_dir = "paraview_solid"; auto pv_fields = std::vector{output_states[0], params[0], params[1]}; @@ -201,12 +202,12 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) size_t cycle = 0; std::vector reactions; - auto advancer = makeAdvancer(system); + auto advancer = makeAdvancer(solid_system); for (size_t m = 0; m < num_steps_; ++m) { TimeInfo t_info(time, dt_, cycle); std::tie(states, reactions) = advancer->advanceState(t_info, shape_disp, states, params); - output_states = system->field_store->getOutputFieldStates(); + output_states = solid_system->field_store->getOutputFieldStates(); pv_fields = {output_states[0], params[0], params[1]}; time += dt_; cycle++; @@ -259,35 +260,43 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; SolidMechanicsOptions solid_options{.enable_stress_output = true}; - auto system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - solid_options, field_store); + auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, + solid_options, field_store); static constexpr double gravity = -9.0; double E = 100.0; double nu = 0.25; auto K = E / (3.0 * (1.0 - 2.0 * nu)); auto G = E / (2.0 * (1.0 + nu)); - system->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, mesh->entireBodyName()); + solid_system->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, + mesh->entireBodyName()); - system->addBodyForce(mesh->entireBodyName(), [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/) { - tensor b{}; - b[1] = gravity; - return b; - }); + solid_system->addBodyForce(mesh->entireBodyName(), + [](double /*time*/, auto /*X*/, auto /*u*/, auto /*v*/, auto /*a*/) { + tensor b{}; + b[1] = gravity; + return b; + }); - system->disp_bc->setVectorBCs(mesh->entireBoundary(), [](double t, tensor /*X*/) { + solid_system->disp_bc->setVectorBCs(mesh->entireBoundary(), [](double t, tensor /*X*/) { tensor u{}; u[1] = 0.5 * gravity * t * t; return u; }); - ASSERT_NE(system->cycle_zero_system, nullptr); - auto cycle_zero_states = system->cycle_zero_system->solve(TimeInfo(0.0, dt_, 0)); - ASSERT_EQ(cycle_zero_states.size(), 1); + ASSERT_NE(solid_system->cycle_zero_system, nullptr); - auto shape_disp = system->field_store->getShapeDisp(); - double time = 0.0; - double a_exact = gravity; + const double dt = 1.0e-3; + const size_t num_steps = 4; + + auto physics = makeDifferentiablePhysics(solid_system, "freefall"); + for (size_t step = 0; step < num_steps; ++step) { + physics->advanceTimestep(dt); + } + + auto states = physics->getFieldStates(); + auto shape_disp = physics->getShapeDispFieldState(); + double time = num_steps * dt; auto vector_error = [&](const std::string& name, const FieldState& state, double y_exact) { auto state_vec = std::vector{state}; FunctionalObjective> error(name, mesh, spaces(state_vec)); @@ -295,10 +304,14 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi auto u = get(U); return u[0] * u[0] + (u[1] - y_exact) * (u[1] - y_exact) + u[2] * u[2]; }); - return error.evaluate(TimeInfo(time, dt_, 0), shape_disp.get().get(), getConstFieldPointers(state_vec)); + return error.evaluate(TimeInfo(time, dt, 0), shape_disp.get().get(), getConstFieldPointers(state_vec)); }; - EXPECT_NEAR(0.0, vector_error("freefall_cycle_zero_acceleration_error", cycle_zero_states[0], a_exact), 1e-10); + double u_exact = 0.5 * gravity * time * time; + double v_exact = gravity * time; + EXPECT_NEAR(0.0, vector_error("freefall_displacement_error", states[0], u_exact), 1e-12); + EXPECT_NEAR(0.0, vector_error("freefall_velocity_error", states[2], v_exact), 1e-11); + EXPECT_NEAR(0.0, vector_error("freefall_acceleration_error", states[3], gravity), 1e-6); } auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr mesh) @@ -308,11 +321,11 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr("bulk"), FieldType("shear")); - auto system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - SolidMechanicsOptions{}, field_store, param_fields); + auto solid_system = buildSolidMechanicsSystem( + solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{}, field_store, param_fields); - auto physics = makeDifferentiablePhysics(system, physics_name); - auto bcs = system->disp_bc; + auto physics = makeDifferentiablePhysics(solid_system, physics_name); + auto bcs = solid_system->disp_bc; bcs->setFixedVectorBCs(mesh->domain("right")); bcs->setVectorBCs(mesh->domain("left"), [](double t, tensor X) { @@ -329,7 +342,7 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptrsetMaterial(material, mesh->entireBodyName()); + solid_system->setMaterial(material, mesh->entireBodyName()); auto shape_disp = physics->getShapeDispFieldState(); auto params = physics->getFieldParams(); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index a97e8ebcf5..a461c6c7da 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -114,32 +114,23 @@ auto registerFields(const std::shared_ptr& field_store) registerInternalVariableFields(field_store)}; } -template -auto buildSystems(const std::shared_ptr& solid_solver, - const std::shared_ptr& internal_variable_solver, const SolidFields& solid_fields, - const InternalVariableFields& internal_variable_fields) -{ - return std::tuple{buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - internal_variable_fields), - buildInternalVariableSystem(internal_variable_solver, InternalVariableOptions{}, - internal_variable_fields, solid_fields)}; -} - template -void setDamageCoupling(const std::shared_ptr& solid, - const std::shared_ptr& internal_variables, +void setDamageCoupling(const std::shared_ptr& solid_system, + const std::shared_ptr& internal_variable_system, const std::shared_ptr& mesh) { - setCoupledSolidMechanicsInternalVariableMaterial(solid, internal_variables, DamageMaterial{}, mesh->entireBodyName()); - setCoupledInternalVariableMaterial(internal_variables, solid, StrainNormEvolution{}, mesh->entireBodyName()); + setCoupledSolidMechanicsInternalVariableMaterial(solid_system, internal_variable_system, DamageMaterial{}, + mesh->entireBodyName()); + setCoupledInternalVariableMaterial(internal_variable_system, solid_system, StrainNormEvolution{}, + mesh->entireBodyName()); } template -void setPullBoundaryConditions(const std::shared_ptr& solid, const std::shared_ptr& mesh, +void setPullBoundaryConditions(const std::shared_ptr& solid_system, const std::shared_ptr& mesh, double pull_rate) { - solid->disp_bc->template setFixedVectorBCs(mesh->domain("bottom")); - solid->disp_bc->template setVectorBCs(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { + solid_system->setDisplacementBC(mesh->domain("bottom")); + solid_system->setDisplacementBC(mesh->domain("top"), [pull_rate](double t, tensor /*X*/) { tensor u{}; u[2] = pull_rate * t; return u; @@ -154,13 +145,15 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); - auto [solid, internal_variables] = - buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); + auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + internal_variable_fields); + auto internal_variable_system = + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, solid_fields); - auto system = combineSystems(coupled_solver, solid, internal_variables); + auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); - setDamageCoupling(solid, internal_variables, mesh); - setPullBoundaryConditions(solid, mesh, 0.05); + setDamageCoupling(solid_system, internal_variable_system, mesh); + setPullBoundaryConditions(solid_system, mesh, 0.05); auto physics = makeDifferentiablePhysics(system, "physics"); @@ -190,13 +183,15 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); - auto [solid, internal_variables] = - buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); + auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + internal_variable_fields); + auto internal_variable_system = + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, solid_fields); - auto system = combineSystems(staggered_solver, solid, internal_variables); + auto system = combineSystems(staggered_solver, solid_system, internal_variable_system); - setDamageCoupling(solid, internal_variables, mesh); - setPullBoundaryConditions(solid, mesh, 0.05); + setDamageCoupling(solid_system, internal_variable_system, mesh); + setPullBoundaryConditions(solid_system, mesh, 0.05); auto physics = makeDifferentiablePhysics(system, "physics_relaxed"); for (int step = 1; step <= 3; ++step) { @@ -213,19 +208,21 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "body_force_test_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); - auto [solid, internal_variables] = - buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields); + auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + internal_variable_fields); + auto internal_variable_system = + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, solid_fields); - auto system = combineSystems(coupled_solver, solid, internal_variables); + auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); - setDamageCoupling(solid, internal_variables, mesh); + setDamageCoupling(solid_system, internal_variable_system, mesh); // Fix bottom face - solid->disp_bc->setFixedVectorBCs(mesh->domain("bottom")); + solid_system->setDisplacementBC(mesh->domain("bottom")); // Apply a gravity-like body force in the -z direction double body_force_mag = -0.01; - solid->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { + solid_system->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { tensor f{}; f[2] = body_force_mag; return f; @@ -233,7 +230,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) // Apply a traction on the top face in the +z direction double traction_mag = 0.005; - solid->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { + solid_system->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { tensor t{}; t[2] = traction_mag; return t; diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 9aa05e64fa..61f9decc1c 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -68,8 +68,7 @@ struct ThermalStaticFixture : public testing::Test { return 2.0 * k * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); }); - thermal_system->temperature_bc->template setScalarBCs<2>(mesh->entireBoundary(), - [](double /*t*/, tensor /*X*/) { return 0.0; }); + thermal_system->setTemperatureBC(mesh->entireBoundary(), [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); auto [new_states, reactions] = makeAdvancer(thermal_system) @@ -166,8 +165,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) return 2.0 * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); }); - thermal_system->temperature_bc->template setScalarBCs<2>(mesh->entireBoundary(), - [](double /*t*/, tensor /*X*/) { return 0.0; }); + thermal_system->setTemperatureBC(mesh->entireBoundary(), [](double /*t*/, tensor /*X*/) { return 0.0; }); TimeInfo t_info(0.0, 1.0); auto [new_states, reactions] = makeAdvancer(thermal_system) diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 76a6e89abf..f1cfca3258 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -30,7 +30,8 @@ #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" -#include "smith/differentiable_numerics/thermo_mechanical_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp" #include "smith/differentiable_numerics/state_variable_system.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/multiphysics_time_integrator.hpp" @@ -146,70 +147,36 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // Phase 2: build each system. // Solid receives thermal and alpha coupling. - auto solid = buildSolidMechanicsSystem(std::make_shared(solid_block_solver), - SolidMechanicsOptions{}, solid_fields, thermal_fields, - internal_variable_fields); + auto solid_system = buildSolidMechanicsSystem(std::make_shared(solid_block_solver), + SolidMechanicsOptions{}, solid_fields, thermal_fields, + internal_variable_fields); - auto thermal = buildThermalSystem(std::make_shared(thermal_block_solver), - ThermalOptions{}, thermal_fields, solid_fields); + auto thermal_system = buildThermalSystem(std::make_shared(thermal_block_solver), + ThermalOptions{}, thermal_fields, solid_fields); - auto internal_variables = - buildInternalVariableSystem(std::make_shared(internal_variable_block_solver), - InternalVariableOptions{}, internal_variable_fields, solid_fields); + auto internal_variable_system = buildInternalVariableSystem( + std::make_shared(internal_variable_block_solver), internal_variable_fields, solid_fields); // Phase 3: register material integrands. auto material = SimpleThermoelasticMaterial{}; - auto disp_rule = solid->disp_time_rule; - auto temp_rule = thermal->temperature_time_rule; - auto alpha_rule = internal_variables->internal_variable_time_rule; - - solid->solid_weak_form->addBodyIntegral( - mesh_->entireBodyName(), [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, - auto temperature, auto temperature_old, auto alpha, auto alpha_old) { - auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto T = temp_rule->value(t_info, temperature, temperature_old); - auto alpha_current = alpha_rule->value(t_info, alpha, alpha_old); - - SimpleThermoelasticMaterial::State state{}; - auto response = material(t_info, state, get(u_current), get(v_current), get(T), - get(T), get(alpha_current)); - auto pk = get<0>(response); - return smith::tuple{get(a_current) * material.density, pk}; - }); - - thermal->thermal_weak_form->addBodyIntegral( - mesh_->entireBodyName(), - [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { - auto [T_current, T_dot] = temp_rule->interpolate(t_info, T, T_old); - auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - - SimpleThermoelasticMaterial::State state{}; - auto response = material(t_info, state, get(u_current), get(v_current), - get(T_current), get(T_current)); - auto C_v = get<1>(response); - auto s0 = get<2>(response); - auto q0 = get<3>(response); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; - }); - - setCoupledInternalVariableMaterial(internal_variables, solid, StrainDrivenInternalVariableMaterial{}, - mesh_->entireBodyName()); + setCoupledThermoMechanicsInternalVariableMaterial(solid_system, thermal_system, internal_variable_system, material, + StrainDrivenInternalVariableMaterial{}, mesh_->entireBodyName()); // Phase 4: boundary conditions. - solid->setDisplacementBC(mesh_->domain("left")); - thermal->setTemperatureBC(mesh_->domain("left")); + solid_system->setDisplacementBC(mesh_->domain("left")); + thermal_system->setTemperatureBC(mesh_->domain("left")); // Compressive traction on right face. // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old, alpha_ss, alpha_old) - solid->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, - auto /*temp_old*/, auto /*alpha_ss*/, auto /*alpha_old*/) { + solid_system->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, + auto /*temp_ss*/, auto /*temp_old*/, auto /*alpha_ss*/, auto /*alpha_old*/) { auto t = 0.0 * X; t[0] = -0.005; return t; }); // Phase 5: combine and solve. - auto combined = combineSystems(solid, thermal, internal_variables); + auto coupled_system = combineSystems(solid_system, thermal_system, internal_variable_system); double dt = 1.0, time = 0.0; auto shape_disp = field_store->getShapeDisp(); @@ -219,7 +186,7 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) for (size_t step = 0; step < 2; ++step) { std::tie(states, reactions) = - makeAdvancer(combined)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); + makeAdvancer(coupled_system)->advanceState(smith::TimeInfo(time, dt, step), shape_disp, states, params); time += dt; } diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 6759a078ed..39947ee5b4 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -46,9 +46,8 @@ struct ThermalSystem : public SystemBase { static_assert(TemperatureTimeRule::num_states == 2, "ThermalSystem requires a 2-state time integration rule"); /// Thermal weak form: (temp, temp_old, coupling_fields..., params...) - using ThermalWeakFormType = FunctionalWeakForm< - dim, H1, - typename detail::TimeRuleParamsWithCoupling, Coupling>::type>; + using ThermalWeakFormType = + FunctionalWeakForm, detail::TimeRuleParams, Coupling>>; std::shared_ptr thermal_weak_form; ///< Thermal weak form. std::shared_ptr temperature_bc; ///< Temperature boundary conditions. @@ -80,6 +79,34 @@ struct ThermalSystem : public SystemBase { }); } + /** + * @brief Set thermal material and a coincident body heat source from a single callable. + * + * The callable is invoked once per quadrature point and must return + * `smith::tuple{heat_capacity, heat_flux, heat_source}`. Used by coupled physics (e.g. + * thermo-mechanics) where one material evaluation produces all three contributions and we + * want to avoid re-evaluating the material for each piece. + * + * Residual contribution: `(heat_capacity * dT/dt - heat_source, -heat_flux)`. + * + * @tparam MaterialType The thermal material type. + * @param material The material model instance returning {C_v, q, s}. + * @param domain_name The name of the domain to apply the material to. + */ + template + void setMaterialAndHeatSource(const MaterialType& material, const std::string& domain_name) + { + auto captured_temp_rule = temperature_time_rule; + + thermal_weak_form->addBodyIntegral( + domain_name, [=](auto t_info, auto /*X*/, auto temperature, auto temperature_old, auto... params) { + auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); + auto [heat_capacity, heat_flux, heat_source] = + material(t_info, get(T_current), get(T_current), params...); + return smith::tuple{heat_capacity * get(T_dot) - heat_source, -heat_flux}; + }); + } + /** * @brief Add a body heat source to the thermal system (with DependsOn). * @param depends_on Selects which primal and parameter fields the contribution depends on. @@ -248,7 +275,7 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl * * Usage: * @code - * auto thermal = buildThermalSystem( + * auto thermal_system = buildThermalSystem( * solver, opts, thermal_fields, solid_fields); * @endcode */ @@ -272,7 +299,7 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio * * Usage: * @code - * auto thermal = buildThermalSystem( + * auto thermal_system = buildThermalSystem( * solver, opts, thermal_fields, solid_fields); * @endcode */ @@ -293,7 +320,7 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio * * Usage: * @code - * auto thermal = buildThermalSystem( + * auto thermal_system = buildThermalSystem( * nonlin_opts, lin_opts, field_store, opts, param_fields, solid_fields); * @endcode */ diff --git a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp similarity index 84% rename from src/smith/differentiable_numerics/thermo_mechanical_system.hpp rename to src/smith/differentiable_numerics/thermo_mechanics_system.hpp index a407b407d0..1dcf985587 100644 --- a/src/smith/differentiable_numerics/thermo_mechanical_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: (BSD-3-Clause) /** - * @file thermo_mechanical_system.hpp + * @file thermo_mechanics_system.hpp * @brief Helper to wire a coupled thermo-mechanical material to a SolidMechanicsSystem and ThermalSystem. */ @@ -97,28 +97,18 @@ void setCoupledThermoMechanicsMaterial( solid->setMaterial(solid_material, domain_name); - thermal->setMaterial( + thermal->setMaterialAndHeatSource( [=](const TimeInfo& t_info, auto temperature, auto grad_temperature, auto disp, auto disp_old, auto v_old, auto a_old, auto... params) { auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); typename MaterialType::State state{}; auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( material, t_info, state, get(u), get(v), temperature, grad_temperature, params...); - return smith::tuple{C_v, q0}; + // Material's s0 sits on the LHS (residual = C_v*T_dot + s0 - q·∇v); negate to fit the + // physical-heat-source convention used by setMaterialAndHeatSource. + return smith::tuple{C_v, q0, -s0}; }, domain_name); - - thermal->thermal_weak_form->addBodyIntegral(domain_name, [=](const TimeInfo& t_info, auto /*X*/, auto temperature, - auto temperature_old, auto disp, auto disp_old, - auto v_old, auto a_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - typename MaterialType::State state{}; - auto [pk, C_v, s0, q0] = - detail::evaluateCoupledThermoMechanicsMaterial(material, t_info, state, get(u), get(v), - get(T_current), get(T_current), params...); - return smith::tuple{s0, smith::zero{}}; - }); } } // namespace smith diff --git a/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp new file mode 100644 index 0000000000..b87268bb16 --- /dev/null +++ b/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp @@ -0,0 +1,77 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + +#include "smith/differentiable_numerics/solid_mechanics_system.hpp" +#include "smith/differentiable_numerics/thermal_system.hpp" +#include "smith/differentiable_numerics/state_variable_system.hpp" +#include "smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp" + +namespace smith { + +/** + * @brief Set a coupled thermo-mechanical internal variable material on the solid, thermal, and internal variable systems. + * + * @tparam SolidSystem Type of the solid mechanics system. + * @tparam ThermalSystem Type of the thermal system. + * @tparam InternalVariableSystem Type of the internal variable system. + * @tparam ThermoMechMaterial Type of the thermo-mechanical material integrand. + * @tparam InternalVarEvolution Type of the internal variable evolution integrand. + * + * @param solid_system The solid mechanics system. + * @param thermal_system The thermal system. + * @param internal_variable_system The internal variable system. + * @param thermo_mech_material The material model for stress, heat capacity, and heat flux. + * @param internal_var_evolution The ODE residual for the internal variable. + * @param domain_name The domain name to apply the material to. + */ +template +void setCoupledThermoMechanicsInternalVariableMaterial( + std::shared_ptr solid_system, std::shared_ptr thermal_system, + std::shared_ptr internal_variable_system, + ThermoMechMaterial thermo_mech_material, InternalVarEvolution internal_var_evolution, + const std::string& domain_name) +{ + auto disp_rule = solid_system->disp_time_rule; + auto temp_rule = thermal_system->temperature_time_rule; + + solid_system->solid_weak_form->addBodyIntegral( + domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, + auto temperature, auto temperature_old, auto alpha, auto alpha_old) { + auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, u, u_old, v_old, a_old); + auto [T_current, T_dot] = temp_rule->interpolate(t_info, temperature, temperature_old); + auto [pk, Cv, s0, q0] = thermo_mech_material( + t_info, get(alpha), get(u_current), get(v_current), + get(T_current), get(T_current), get(alpha_old)); + return smith::tuple{get(a_current) * thermo_mech_material.density, pk}; + }); + + thermal_system->thermal_weak_form->addBodyIntegral( + domain_name, + [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { + auto [T_current, T_dot] = temp_rule->interpolate(t_info, T, T_old); + auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + + // Alpha is not directly in thermal weak form coupling right now based on test structure, + // but typically it's evaluated via thermo_mech_material. Wait, the test does not pass alpha + // to thermal weak form. So we can use a dummy alpha if it's not passed, or it should be passed? + // Let's copy exactly what the test had. + auto [pk, C_v, s0, q0] = thermo_mech_material(t_info, 0.0, get(u_current), get(v_current), + get(T_current), get(T_current), 0.0); + return smith::tuple{C_v * get(T_dot) - s0, -q0}; + }); + + // For the internal variables, the test uses a helper `setCoupledInternalVariableMaterial`, but the prompt + // says the signature takes `internal_var_evolution`, which is an ODE residual. + // Wait, the prompt says we should call: + // setCoupledThermoMechanicsInternalVariableMaterial(solid, thermal, internal_vars, thermo_mech_mat, internal_var_evo, domain); + // So we just register the internal_var_evolution using `addEvolution`! + setCoupledInternalVariableMaterial(internal_variable_system, solid_system, internal_var_evolution, domain_name); +} + +} // namespace smith diff --git a/src/smith/numerics/solver_config.hpp b/src/smith/numerics/solver_config.hpp index 6cfd77c80d..acd7e6ab1b 100644 --- a/src/smith/numerics/solver_config.hpp +++ b/src/smith/numerics/solver_config.hpp @@ -452,6 +452,9 @@ struct LinearSolverOptions { /// Schur approximation type SchurApproxType schur_approx_type = SchurApproxType::DiagInv; + + /// Whether to use Hypre's elasticity-specific AMG options (requires byVDIM ordering) + bool amg_elasticity = false; }; // _linear_options_end From a640dd085975d8ca40a12dae2f7fe52d3783129f Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sun, 26 Apr 2026 16:46:21 -0600 Subject: [PATCH 44/67] Address some more interface cleanups. --- .../composable_thermo_mechanics_advanced.cpp | 14 ++--- improvements.md | 59 ------------------- .../nonlinear_block_solver.cpp | 5 +- .../nonlinear_solve.cpp | 8 ++- ...mo_mechanics_with_internal_vars_system.hpp | 40 +++++++------ 5 files changed, 37 insertions(+), 89 deletions(-) delete mode 100644 improvements.md diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 668bcf8ec5..2922fb8b13 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -120,17 +120,15 @@ int main(int argc, char* argv[]) // _qoi_start auto fetch_qoi_fields = [&]() { - return std::vector{field_store->getField("displacement"), - field_store->getField("temperature")}; + return std::vector{field_store->getField("displacement"), field_store->getField("temperature")}; }; smith::FunctionalObjective> qoi("thermo_mechanical_energy_proxy", mesh, smith::spaces(fetch_qoi_fields())); - qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), - [](const smith::TimeInfo&, auto /*X*/, auto U, auto Theta) { - auto u = smith::get(U); - auto theta = smith::get(Theta); - return 0.5 * u[0] * u[0] + 0.05 * theta * theta; - }); + qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](double, auto /*X*/, auto U, auto Theta) { + auto u = smith::get(U); + auto theta = smith::get(Theta); + return 0.5 * u[0] * u[0] + 0.05 * theta * theta; + }); auto qoi_state = 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), fetch_qoi_fields(), diff --git a/improvements.md b/improvements.md deleted file mode 100644 index e4d253b705..0000000000 --- a/improvements.md +++ /dev/null @@ -1,59 +0,0 @@ -1. Done. Removed brittle state-index assumptions in [composable_solid_mechanics.cpp](/usr/WS2/tupek2/dev/smith/examples/solid_mechanics/composable_solid_mechanics.cpp). -2. Done. Put `TimeInfo` class on separate line. -3. Done. State ordering now comes from field names, not hard-coded indices. -4. Done. Removed shims like `outputFields()` and `qoiFields()`. -5. Done. Renamed `TimeInfoYoungsModulusNeoHookean`. -6. Done. Renamed `TimeInfoGreenSaintVenantThermoelasticMaterial`. -7. Done. `outputFields()` layer removed as unneeded. -8. Done. `qoiFields()` layer removed as unneeded. -9. Done. `TimeInfo` now flows through base `FunctionalObjective` and base `FunctionalWeakForm` interfaces; `TimeDiscretizedObjective` and `TimeDiscretizedWeakForm` layers are removed. -10. No item 10. -11. Done. Renamed `custom_solver` to `coupled_solver`; advanced example now uses `(0,1)`. -12. Done. `appendRemappedStages` renamed to `appendStagesWithBlockMapping`; test now uses `combined_solver` and `StaticTimeIntegrationRule`. -13. Done. `TransientFreefallWithConsistentBoundaryConditions` now applies time-dependent freefall displacement BCs and checks cycle-zero acceleration solve against the matching initial acceleration. -13.b. Lets make the test dynamic/transient again to also test the time integration is exactly integrating acceleration. If there are instabilities, we need to figure those out. Consider how to simplify the new boundary condition changes in the last commit. -14. In `_internal_vars` test, inline `auto [solid, internal_variables] = buildSystems(solid_solver, internal_variable_solver, solid_fields, internal_variable_fields);`. Rename `solid` to `solid_system` and `internal_variables` to `internal_variable_system`. -15. Like solid system, use `thermal_system->setTemperatureBcs`; make interface consistent with plurals. -16. Was `src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp` replaced by different test? -17. In `StronglyCoupledThreeSystems`, find better way to handle material setup. -18. Is `MonolithicCombinedSystem` still used? Probably delete it. -19. What is `mergeSystems` versus `combineSystems`? -20. Remove `findFieldStore`. This seems overly complex. Just grab first fields field store inline where needed. -21. Review `collectPhysicsFromPack` and `collectParamsFromPack`. What are they doing? -22. Is `TimeRuleParamsWithCoupling` used? -23. In `DifferentiablePhysics::dual(...)`, there is debug print. Change to `SLIC_ERROR` if important. -24. Logic here is confusing. Make clear main exception is acceleration solve. -25. Is all complication in `getBoundaryConditionManagers` needed? -26. Figure out how `outputField` in `fieldStore` is determined and whether it can be simplified. -27. Explain and simplify `MultiphysicsTimeIntegrator::advanceState`. -28. `makeAdvancer` should not take `cycle_zero_system` or `post_solve_systems`; get them off system. -29. Consider elasticity option for `HypreAMG`. Maybe optional configuration. See `NonlinearBlockSolver::completeSetup`. -30. Are `TimeRuleParams` still used? Can `detail::TimeRuleParamsWithCoupling` be renamed back to that and always use coupling? -31. Why is `CycleZeroSolidWeakFormType` adding many new `H1` fields? Does `AppendCouplingToParams` put last arguments first? -32. `DependsOn` for `SolidSystem` (`addTraction`, `addBody`, `addPressure`, etc.) should be indexed starting at displacement, then velocity, and so on. `DependsOn<0>` should only give displacement. `DependsOn<0,1,2,3>` should be displacement, velocity, acceleration, and `param_0`. -33. Make sure `ThermalSystem` stays in sync with this. -34. Return value from all systems should be variable named `physics_system`. Update unit tests, examples, and code comments, such as in `state_variable_system.hpp`. -35. Remove these overloads: - -```cpp -template -/// @brief Register an internal-variable material or evolution law on a domain. -void setMaterial(const MaterialType& material, const std::string& domain_name) -{ - addEvolution(domain_name, material); -} - -template -/// @brief Backward-compatible alias for `addEvolution`. -void addStateEvolution(const std::string& domain_name, EvolutionType evolution_law) -{ - addEvolution(domain_name, evolution_law); -} -``` - -36. Remove `InternalVariableOptions`. Also remove `StateVariableOptions` and constructor using it. -37. There seem to be duplicated `buildInternalVariableSystem`. -38. Explain `appendRemappedStages`. Can it get better name? `Remapped` means something else here, especially with finite-element field remaps. -39. What is `CoupledSolidThermoMechanicsMaterialAdapter` for? Delete it and use intended interface directly. -40. Review `test_linear_solver_none.cpp`. Make sure it still makes sense. -41. Revert changes to `src/smith/physics/materials/green_saint_venant_thermoelastic.hpp`. diff --git a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp index ea570dfa9c..b9d3964425 100644 --- a/src/smith/differentiable_numerics/nonlinear_block_solver.cpp +++ b/src/smith/differentiable_numerics/nonlinear_block_solver.cpp @@ -243,7 +243,10 @@ std::vector NonlinearBlockSolver::solveAdjoi auto block_jac = std::make_unique(block_offsets); for (int i = 0; i < num_rows; ++i) { for (int j = 0; j < num_rows; ++j) { - block_jac->SetBlock(i, j, jacobian_transposed[static_cast(i)][static_cast(j)].get()); + auto& J = jacobian_transposed[static_cast(i)][static_cast(j)]; + if (J) { + block_jac->SetBlock(i, j, J.get()); + } } } diff --git a/src/smith/differentiable_numerics/nonlinear_solve.cpp b/src/smith/differentiable_numerics/nonlinear_solve.cpp index 63ca060739..e600f6cec6 100644 --- a/src/smith/differentiable_numerics/nonlinear_solve.cpp +++ b/src/smith/differentiable_numerics/nonlinear_solve.cpp @@ -250,7 +250,9 @@ std::vector block_solve(const std::vector& residual_evals for (size_t row_i = 0; row_i < num_rows; ++row_i) { for (size_t col_j = 0; col_j < num_rows; ++col_j) { - input_fields[row_i][block_indices[row_i][col_j]] = diagonal_fields[col_j]; + if (block_indices[row_i][col_j] != invalid_block_index) { + input_fields[row_i][block_indices[row_i][col_j]] = diagonal_fields[col_j]; + } } } @@ -349,7 +351,9 @@ std::vector block_solve(const std::vector& residual_evals // No sensitivity needed for primal fields for (size_t row_i = 0; row_i < num_rows; ++row_i) { for (size_t col_j = 0; col_j < num_rows; ++col_j) { - field_sensitivities[row_i][block_indices[row_i][col_j]] = nullptr; + if (block_indices[row_i][col_j] != invalid_block_index) { + field_sensitivities[row_i][block_indices[row_i][col_j]] = nullptr; + } } } diff --git a/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp index b87268bb16..54ea967e2a 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp @@ -14,7 +14,8 @@ namespace smith { /** - * @brief Set a coupled thermo-mechanical internal variable material on the solid, thermal, and internal variable systems. + * @brief Set a coupled thermo-mechanical internal variable material on the solid, thermal, and internal variable + * systems. * * @tparam SolidSystem Type of the solid mechanics system. * @tparam ThermalSystem Type of the thermal system. @@ -29,31 +30,31 @@ namespace smith { * @param internal_var_evolution The ODE residual for the internal variable. * @param domain_name The domain name to apply the material to. */ -template -void setCoupledThermoMechanicsInternalVariableMaterial( - std::shared_ptr solid_system, std::shared_ptr thermal_system, - std::shared_ptr internal_variable_system, - ThermoMechMaterial thermo_mech_material, InternalVarEvolution internal_var_evolution, - const std::string& domain_name) +template +void setCoupledThermoMechanicsInternalVariableMaterial(std::shared_ptr solid_system, + std::shared_ptr thermal_system, + std::shared_ptr internal_variable_system, + ThermoMechMaterial thermo_mech_material, + InternalVarEvolution internal_var_evolution, + const std::string& domain_name) { auto disp_rule = solid_system->disp_time_rule; auto temp_rule = thermal_system->temperature_time_rule; solid_system->solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, - auto temperature, auto temperature_old, auto alpha, auto alpha_old) { + domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, + auto temperature_old, auto alpha, auto alpha_old) { auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, u, u_old, v_old, a_old); auto [T_current, T_dot] = temp_rule->interpolate(t_info, temperature, temperature_old); - auto [pk, Cv, s0, q0] = thermo_mech_material( - t_info, get(alpha), get(u_current), get(v_current), - get(T_current), get(T_current), get(alpha_old)); + auto [pk, Cv, s0, q0] = + thermo_mech_material(t_info, get(alpha), get(u_current), get(v_current), + get(T_current), get(T_current), get(alpha_old)); return smith::tuple{get(a_current) * thermo_mech_material.density, pk}; }); thermal_system->thermal_weak_form->addBodyIntegral( - domain_name, - [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { + domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { auto [T_current, T_dot] = temp_rule->interpolate(t_info, T, T_old); auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); @@ -61,16 +62,17 @@ void setCoupledThermoMechanicsInternalVariableMaterial( // but typically it's evaluated via thermo_mech_material. Wait, the test does not pass alpha // to thermal weak form. So we can use a dummy alpha if it's not passed, or it should be passed? // Let's copy exactly what the test had. - auto [pk, C_v, s0, q0] = thermo_mech_material(t_info, 0.0, get(u_current), get(v_current), - get(T_current), get(T_current), 0.0); + auto [pk, C_v, s0, q0] = + thermo_mech_material(t_info, 0.0, get(u_current), get(v_current), + get(T_current), get(T_current), 0.0); return smith::tuple{C_v * get(T_dot) - s0, -q0}; }); // For the internal variables, the test uses a helper `setCoupledInternalVariableMaterial`, but the prompt // says the signature takes `internal_var_evolution`, which is an ODE residual. // Wait, the prompt says we should call: - // setCoupledThermoMechanicsInternalVariableMaterial(solid, thermal, internal_vars, thermo_mech_mat, internal_var_evo, domain); - // So we just register the internal_var_evolution using `addEvolution`! + // setCoupledThermoMechanicsInternalVariableMaterial(solid, thermal, internal_vars, thermo_mech_mat, internal_var_evo, + // domain); So we just register the internal_var_evolution using `addEvolution`! setCoupledInternalVariableMaterial(internal_variable_system, solid_system, internal_var_evolution, domain_name); } From a232080d93d94d15eac3231acb65739e54c15629 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 27 Apr 2026 12:17:52 -0600 Subject: [PATCH 45/67] Try to avoid duplication in weak form setup. --- .../composable_solid_mechanics.cpp | 2 +- .../combined_system.hpp | 55 ++++-------------- .../multiphysics_time_integrator.cpp | 29 +++++----- .../multiphysics_time_integrator.hpp | 16 +++--- .../solid_mechanics_system.hpp | 34 ++++------- .../state_variable_system.hpp | 12 +--- .../differentiable_numerics/system_base.hpp | 38 ++++++++++++- .../differentiable_numerics/system_solver.cpp | 25 +++++--- .../test_multiphysics_time_integrator.cpp | 4 +- .../tests/test_solid_dynamics.cpp | 26 +++++---- ...st_thermo_mechanics_with_internal_vars.cpp | 9 +++ .../thermal_system.hpp | 10 +--- ...mo_mechanics_with_internal_vars_system.hpp | 57 +++++++------------ 13 files changed, 148 insertions(+), 169 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 4e105c50e2..85669cf879 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -143,7 +143,7 @@ int main(int argc, char* argv[]) // _bc_end // _run_start - if (solid_system->cycle_zero_system == nullptr) { + if (solid_system->cycle_zero_systems.empty()) { throw std::runtime_error("Expected cycle-zero solve for implicit dynamics."); } diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index d63a355c7f..eaf48cab31 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -102,7 +102,6 @@ auto combineSystems(std::shared_ptr... subs) int max_stagger_iters = 1; bool exact_staggered_steps = false; - std::vector> cycle_zero_subs; std::vector> post_solve_systems; std::vector> subsystem_global_block_indices; @@ -119,15 +118,11 @@ auto combineSystems(std::shared_ptr... subs) max_stagger_iters = std::max(max_stagger_iters, sub->solver->maxStaggeredIterations()); exact_staggered_steps = exact_staggered_steps || sub->solver->exactStaggeredSteps(); } - if constexpr (requires { sub->cycle_zero_system; }) { - if (sub->cycle_zero_system) { - cycle_zero_subs.push_back(sub->cycle_zero_system); - } - } - if constexpr (requires { sub->post_solve_systems; }) { - post_solve_systems.insert(post_solve_systems.end(), sub->post_solve_systems.begin(), - sub->post_solve_systems.end()); + for (auto& cz_sys : sub->cycle_zero_systems) { + combined->cycle_zero_systems.push_back(cz_sys); } + post_solve_systems.insert(post_solve_systems.end(), sub->post_solve_systems.begin(), + sub->post_solve_systems.end()); }(subs), ...); @@ -138,25 +133,6 @@ auto combineSystems(std::shared_ptr... subs) combined->solver->appendStagesWithBlockMapping(*sub->solver, subsystem_global_block_indices[i]); } - std::shared_ptr cycle_zero_combined = nullptr; - if (!cycle_zero_subs.empty()) { - auto cycle_zero = std::make_shared(); - cycle_zero->field_store = field_store; - cycle_zero->solver = std::make_shared(1, true); - for (auto& sub : cycle_zero_subs) { - std::vector global_block_indices; - cycle_zero->subsystems.push_back(sub); - for (auto& wf : sub->weak_forms) { - global_block_indices.push_back(cycle_zero->weak_forms.size()); - cycle_zero->weak_forms.push_back(wf); - } - SLIC_ERROR_IF(!sub->solver, "Combined cycle-zero subsystem must have a solver"); - cycle_zero->solver->appendStagesWithBlockMapping(*sub->solver, global_block_indices); - } - cycle_zero_combined = cycle_zero; - } - - combined->cycle_zero_system = cycle_zero_combined; combined->post_solve_systems = post_solve_systems; return combined; @@ -183,7 +159,7 @@ std::shared_ptr combineSystems(std::shared_ptr solver, auto field_store = std::get<0>(std::forward_as_tuple(subs...))->field_store; std::vector> wfs; - std::vector> cycle_zero_wfs; + std::vector> cycle_zero_systems; std::vector> post_solve_systems; ( @@ -191,27 +167,16 @@ std::shared_ptr combineSystems(std::shared_ptr solver, for (auto& wf : sub->weak_forms) { wfs.push_back(wf); } - if constexpr (requires { sub->cycle_zero_system; }) { - if (sub->cycle_zero_system) { - for (auto& cycle_zero_wf : sub->cycle_zero_system->weak_forms) { - cycle_zero_wfs.push_back(cycle_zero_wf); - } - } - } - if constexpr (requires { sub->post_solve_systems; }) { - post_solve_systems.insert(post_solve_systems.end(), sub->post_solve_systems.begin(), - sub->post_solve_systems.end()); + for (auto& cz_sys : sub->cycle_zero_systems) { + cycle_zero_systems.push_back(cz_sys); } + post_solve_systems.insert(post_solve_systems.end(), sub->post_solve_systems.begin(), + sub->post_solve_systems.end()); }(subs), ...); auto combined = std::make_shared(field_store, solver, wfs); - std::shared_ptr cycle_zero_combined = nullptr; - if (!cycle_zero_wfs.empty()) { - cycle_zero_combined = std::make_shared(field_store, solver, cycle_zero_wfs); - } - - combined->cycle_zero_system = cycle_zero_combined; + combined->cycle_zero_systems = cycle_zero_systems; combined->post_solve_systems = post_solve_systems; return combined; diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index 01fe396670..5178d08525 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -17,9 +17,11 @@ namespace smith { MultiphysicsTimeIntegrator::MultiphysicsTimeIntegrator(std::shared_ptr system, - std::shared_ptr cycle_zero_system, + std::vector> cycle_zero_systems, std::vector> post_solve_systems) - : system_(system), cycle_zero_system_(cycle_zero_system), post_solve_systems_(std::move(post_solve_systems)) + : system_(system), + cycle_zero_systems_(std::move(cycle_zero_systems)), + post_solve_systems_(std::move(post_solve_systems)) { for (size_t i = 0; i < system_->weak_forms.size(); ++i) { const std::string wf_name = system_->weak_forms[i]->name(); @@ -59,17 +61,18 @@ std::pair, std::vector> MultiphysicsTimeI return rule_and_mapping.first && rule_and_mapping.first->requiresInitialAccelerationSolve(); }); - if (time_info.cycle() == 0 && cycle_zero_system_ && requires_cycle_zero_solve) { - auto cycle_zero_unknowns = cycle_zero_system_->solve(time_info); - - SLIC_ERROR_ROOT_IF(cycle_zero_unknowns.size() != cycle_zero_system_->weak_forms.size(), - "Cycle zero system result count does not match number of cycle-zero weak forms"); - for (size_t i = 0; i < cycle_zero_system_->weak_forms.size(); ++i) { - std::string test_field_name = - system_->field_store->getWeakFormReaction(cycle_zero_system_->weak_forms[i]->name()); - size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); - current_states[test_field_state_idx] = cycle_zero_unknowns[i]; - system_->field_store->setField(test_field_state_idx, cycle_zero_unknowns[i]); + if (time_info.cycle() == 0 && !cycle_zero_systems_.empty() && requires_cycle_zero_solve) { + for (const auto& cz_sys : cycle_zero_systems_) { + auto cycle_zero_unknowns = cz_sys->solve(time_info); + + SLIC_ERROR_ROOT_IF(cycle_zero_unknowns.size() != cz_sys->weak_forms.size(), + "Cycle zero system result count does not match number of cycle-zero weak forms"); + for (size_t i = 0; i < cz_sys->weak_forms.size(); ++i) { + std::string test_field_name = system_->field_store->getWeakFormReaction(cz_sys->weak_forms[i]->name()); + size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); + current_states[test_field_state_idx] = cycle_zero_unknowns[i]; + system_->field_store->setField(test_field_state_idx, cycle_zero_unknowns[i]); + } } } diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 44386a960a..3a224b5edd 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -30,11 +30,11 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { /** * @brief Construct a multiphysics advancer around main and auxiliary systems. * @param system Main system solved every timestep. - * @param cycle_zero_system Optional startup system solved before first regular step. + * @param cycle_zero_systems Optional startup systems solved independently before first regular step. * @param post_solve_systems Optional systems solved after the main step. */ MultiphysicsTimeIntegrator(std::shared_ptr system, - std::shared_ptr cycle_zero_system = nullptr, + std::vector> cycle_zero_systems = {}, std::vector> post_solve_systems = {}); /// @brief Register a system to be solved after the main solve and reaction computation. @@ -54,7 +54,7 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { private: std::shared_ptr system_; - std::shared_ptr cycle_zero_system_; + std::vector> cycle_zero_systems_; std::vector> post_solve_systems_; std::map main_unknown_name_to_local_idx_; @@ -63,20 +63,20 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { /** * @brief Build a `MultiphysicsTimeIntegrator` from system-owned or explicit auxiliary systems. * - * Missing optional arguments fall back to `system->cycle_zero_system` and + * Missing optional arguments fall back to `system->cycle_zero_systems` and * `system->post_solve_systems`. */ inline std::shared_ptr makeAdvancer( - std::shared_ptr system, std::shared_ptr cycle_zero_system = nullptr, + std::shared_ptr system, std::vector> cycle_zero_systems = {}, std::vector> post_solve_systems = {}) { - if (cycle_zero_system == nullptr) { - cycle_zero_system = system->cycle_zero_system; + if (cycle_zero_systems.empty()) { + cycle_zero_systems = system->cycle_zero_systems; } if (post_solve_systems.empty()) { post_solve_systems = system->post_solve_systems; } - return std::make_shared(std::move(system), std::move(cycle_zero_system), + return std::make_shared(std::move(system), std::move(cycle_zero_systems), std::move(post_solve_systems)); } diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 4bf896363c..1a08817b7c 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -454,13 +454,8 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons using SystemType = SolidMechanicsSystem; std::string force_name = field_store->prefix("reactions"); - auto solid_weak_form = std::apply( - [&](auto&... coupling_fields) { - return std::make_shared( - force_name, field_store->getMesh(), field_store->getField(disp_type.name).get()->space(), - field_store->createSpaces(force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, - accel_old_type, coupling_fields...)); - }, + auto solid_weak_form = detail::buildWeakFormWithCoupling( + field_store, force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); @@ -474,32 +469,23 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons auto accel_as_unknown = accel_old_type; accel_as_unknown.is_unknown = true; FieldType> disp_cz_input(disp_type.name); - sys->cycle_zero_solid_weak_form = std::apply( - [&](auto&... coupling_fields) { - return std::make_shared( - cycle_zero_name, field_store->getMesh(), field_store->getField(accel_old_type.name).get()->space(), - field_store->createSpaces(cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, - accel_as_unknown, coupling_fields...)); - }, - coupling.fields); + sys->cycle_zero_solid_weak_form = + detail::buildWeakFormWithCoupling( + field_store, cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, + coupling.fields); field_store->markWeakFormInternal(cycle_zero_name); field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); auto cycle_zero_solver = detail::makeCycleZeroSolver(solver, *field_store->getMesh()); - sys->cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form}); + sys->cycle_zero_systems.push_back(makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form})); } if (has_stress_output) { FieldType> stress_type(field_store->prefix("stress"), true); FieldType> disp_as_input(disp_type.name); std::string stress_name = field_store->prefix("stress_projection"); - sys->stress_weak_form = std::apply( - [&](auto&... coupling_fields) { - return std::make_shared( - stress_name, field_store->getMesh(), field_store->getField(stress_type.name).get()->space(), - field_store->createSpaces(stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, - velo_old_type, accel_old_type, coupling_fields...)); - }, - coupling.fields); + sys->stress_weak_form = detail::buildWeakFormWithCoupling( + field_store, stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, velo_old_type, + accel_old_type, coupling.fields); NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 429da445f4..f17b7595d6 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -167,15 +167,9 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co using SystemType = InternalVariableSystem; std::string internal_variable_residual_name = field_store->prefix("state_residual"); - auto internal_variable_weak_form = std::apply( - [&](auto&... coupling_fields) { - return std::make_shared( - internal_variable_residual_name, field_store->getMesh(), - field_store->getField(state_type.name).get()->space(), - field_store->createSpaces(internal_variable_residual_name, state_type.name, state_type, state_old_type, - coupling_fields...)); - }, - coupling.fields); + auto internal_variable_weak_form = + detail::buildWeakFormWithCoupling( + field_store, internal_variable_residual_name, state_type.name, state_type, state_old_type, coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{internal_variable_weak_form}); diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index 6df524ad27..49a2a26bfb 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -54,9 +54,11 @@ struct SystemBase { std::vector> weak_forms; ///< Weak forms solved together by this system. // --- infrastructure --- - std::shared_ptr field_store; ///< Field store managing the system's fields. - std::shared_ptr solver; ///< The solver for the system. - std::shared_ptr cycle_zero_system; ///< Optional startup solve executed before first timestep. + std::shared_ptr field_store; ///< Field store managing the system's fields. + std::shared_ptr solver; ///< The solver for the system. + std::vector> + cycle_zero_systems; ///< Optional startup solves executed before first timestep. Each entry is solved + ///< independently; cycle-zero solves do not couple across subsystems. std::vector> post_solve_systems; ///< Optional systems solved after main state update. /// @brief Construct an empty system shell. @@ -102,3 +104,33 @@ inline std::shared_ptr makeSystem(std::shared_ptr field_ } } // namespace smith + +namespace smith { +namespace detail { + +template +auto buildWeakFormWithCouplingImpl(const FieldStorePtr& field_store, const std::string& weak_form_name, + const std::string& unknown_field_name, const std::tuple& args_tuple, + std::index_sequence) +{ + auto coupling_fields_tuple = std::get(args_tuple); + return std::apply( + [&](const auto&... coupling_fields) { + return std::make_shared(weak_form_name, field_store->getMesh(), + field_store->getField(unknown_field_name).get()->space(), + field_store->createSpaces(weak_form_name, unknown_field_name, + std::get(args_tuple)..., coupling_fields...)); + }, + coupling_fields_tuple); +} + +template +auto buildWeakFormWithCoupling(const FieldStorePtr& field_store, const std::string& weak_form_name, + const std::string& unknown_field_name, const Args&... args) +{ + return buildWeakFormWithCouplingImpl(field_store, weak_form_name, unknown_field_name, std::tie(args...), + std::make_index_sequence{}); +} + +} // namespace detail +} // namespace smith diff --git a/src/smith/differentiable_numerics/system_solver.cpp b/src/smith/differentiable_numerics/system_solver.cpp index 8c06e2a557..5d234a8c20 100644 --- a/src/smith/differentiable_numerics/system_solver.cpp +++ b/src/smith/differentiable_numerics/system_solver.cpp @@ -13,6 +13,8 @@ #include #include +#include +#include #include #include @@ -102,6 +104,16 @@ std::vector SystemSolver::solve(const std::vector& residu // Working copy of states, updated in-place as stages solve std::vector> current_states = states; + // Pre-compute name -> (row, slot) routing so the propagation loop avoids O(N*M) string compares + // on every staggered iteration. Field-name identity within current_states is invariant across + // the iteration loop: only values are replaced, never the underlying name. + std::unordered_map>> field_routing; + for (size_t r = 0; r < num_residuals; ++r) { + for (size_t slot = 0; slot < current_states[r].size(); ++slot) { + field_routing[current_states[r][slot].get()->name()].emplace_back(r, slot); + } + } + // Helper lambda to assemble input pointers, evaluate residual, and zero essential BCs auto eval_residual_and_zero_bcs = [&](size_t global_row) { std::vector input_ptrs; @@ -161,8 +173,8 @@ std::vector SystemSolver::solve(const std::vector& residu stage.solver.get(), stage_bc_managers); // Propagate updated fields to every residual input that references the solved field. - // Match by field name, not by block_indices: coupling fields appear as fixed inputs in - // other rows and therefore do not have a valid unknown-block entry there. + // Match by field name (looked up via the pre-computed routing map): coupling fields appear + // as fixed inputs in other rows and therefore do not have a valid unknown-block entry there. // Apply relaxation: x_new = omega * x_solved + (1 - omega) * x_k. for (size_t i = 0; i < num_stage_blocks; ++i) { size_t global_col = stage.block_indices[i]; @@ -173,11 +185,10 @@ std::vector SystemSolver::solve(const std::vector& residu new_state = weighted_average(new_state, old_state, stage.relaxation_factor); } - for (size_t r = 0; r < num_residuals; ++r) { - for (auto& row_state : current_states[r]) { - if (row_state.get()->name() == new_state.get()->name()) { - row_state = new_state; - } + auto it = field_routing.find(new_state.get()->name()); + if (it != field_routing.end()) { + for (const auto& [r, slot] : it->second) { + current_states[r][slot] = new_state; } } } diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index a1c27ba537..962d8d1d73 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -180,7 +180,7 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroUsesBcsForReactionFieldNotUnknownZero) cycle_zero_system->weak_forms = {cycle_zero_wf}; cycle_zero_system->solver = cycle_zero_solver; - MultiphysicsTimeIntegrator advancer(main_system, cycle_zero_system); + MultiphysicsTimeIntegrator advancer(main_system, {cycle_zero_system}); auto [new_states, reactions] = advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); @@ -236,7 +236,7 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) cycle_zero_system->weak_forms = {cycle_zero_wf}; cycle_zero_system->solver = cycle_zero_solver; - MultiphysicsTimeIntegrator advancer(main_system, cycle_zero_system); + MultiphysicsTimeIntegrator advancer(main_system, {cycle_zero_system}); auto [new_states, reactions] = advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index c4a9d7d935..beca82e41c 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -98,7 +98,7 @@ struct SolidMechanicsMeshFixture : public testing::Test { }; // Verifies the cycle-zero contract: rules that report requiresInitialAccelerationSolve() -// produce a non-null cycle_zero_system; rules that don't (QuasiStatic) produce nullptr. +// produce a non-empty cycle_zero_systems; rules that don't (QuasiStatic) produce empty. TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { { @@ -106,7 +106,7 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{}, field_store); - EXPECT_NE(solid_system->cycle_zero_system, nullptr) + EXPECT_EQ(solid_system->cycle_zero_systems.size(), 1u) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { @@ -114,7 +114,7 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{}, field_store); - EXPECT_EQ(solid_system->cycle_zero_system, nullptr) << "QuasiStatic has no initial acceleration solve"; + EXPECT_TRUE(solid_system->cycle_zero_systems.empty()) << "QuasiStatic has no initial acceleration solve"; } } @@ -129,10 +129,11 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverUsesOwnedSingleStepPolicy) auto solid_fields = registerSolidMechanicsFields(field_store); auto solid_system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); - ASSERT_NE(solid_system->cycle_zero_system, nullptr); - ASSERT_NE(solid_system->cycle_zero_system->solver, nullptr); - EXPECT_EQ(solid_system->cycle_zero_system->solver->maxStaggeredIterations(), 1); - EXPECT_TRUE(solid_system->cycle_zero_system->solver->exactStaggeredSteps()); + ASSERT_EQ(solid_system->cycle_zero_systems.size(), 1u); + const auto& cz = solid_system->cycle_zero_systems[0]; + ASSERT_NE(cz->solver, nullptr); + EXPECT_EQ(cz->solver->maxStaggeredIterations(), 1); + EXPECT_TRUE(cz->solver->exactStaggeredSteps()); } TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver) @@ -142,10 +143,11 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver auto solid_fields = registerSolidMechanicsFields(field_store); auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); - ASSERT_NE(solid_system->cycle_zero_system, nullptr); - ASSERT_NE(solid_system->cycle_zero_system->solver, nullptr); - EXPECT_EQ(solid_system->cycle_zero_system->solver->maxStaggeredIterations(), 1); - EXPECT_FALSE(solid_system->cycle_zero_system->solver->exactStaggeredSteps()); + ASSERT_EQ(solid_system->cycle_zero_systems.size(), 1u); + const auto& cz = solid_system->cycle_zero_systems[0]; + ASSERT_NE(cz->solver, nullptr); + EXPECT_EQ(cz->solver->maxStaggeredIterations(), 1); + EXPECT_FALSE(cz->solver->exactStaggeredSteps()); } TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) @@ -284,7 +286,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi return u; }); - ASSERT_NE(solid_system->cycle_zero_system, nullptr); + ASSERT_FALSE(solid_system->cycle_zero_systems.empty()); const double dt = 1.0e-3; const size_t num_steps = 4; diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index f1cfca3258..4630ac1579 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -78,6 +78,15 @@ struct SimpleThermoelasticMaterial { return smith::tuple{pk, heat_capacity, heat_source, q0}; } + template + SMITH_HOST_DEVICE auto operator()(const TimeInfo& t_info, StateType state, GradUType grad_u, GradVType grad_v, + ThetaType theta, GradThetaType grad_theta, + smith::tuple alpha, Params... params) const + { + return (*this)(t_info, state, grad_u, grad_v, theta, grad_theta, smith::get(alpha), params...); + } + template SMITH_HOST_DEVICE auto operator()(const TimeInfo& t_info, StateType state, GradUType grad_u, GradVType grad_v, diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 39947ee5b4..ba50f40a26 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -246,14 +246,8 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl using SystemType = ThermalSystem; std::string thermal_flux_name = field_store->prefix("thermal_flux"); - auto thermal_weak_form = std::apply( - [&](auto&... coupling_fields) { - return std::make_shared( - thermal_flux_name, field_store->getMesh(), field_store->getField(temperature_type.name).get()->space(), - field_store->createSpaces(thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, - coupling_fields...)); - }, - coupling.fields); + auto thermal_weak_form = detail::buildWeakFormWithCoupling( + field_store, thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, coupling.fields); auto sys = std::make_shared(field_store, solver, std::vector>{thermal_weak_form}); diff --git a/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp index 54ea967e2a..0f3a9505cb 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_with_internal_vars_system.hpp @@ -10,12 +10,28 @@ #include "smith/differentiable_numerics/thermal_system.hpp" #include "smith/differentiable_numerics/state_variable_system.hpp" #include "smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp" +#include "smith/differentiable_numerics/thermo_mechanics_system.hpp" namespace smith { /** - * @brief Set a coupled thermo-mechanical internal variable material on the solid, thermal, and internal variable - * systems. + * @brief Wire a coupled thermo-mechanical internal-variable material across solid, thermal, and internal-variable + * systems by composing the existing two-system helpers. + * + * Preconditions on the systems passed in: + * - @p solid_system was built with @c (thermal_fields, internal_variable_fields) coupling, in that order. The + * leading 4 tail arguments are then @c (T_solve_state, T_old, alpha_solve_state, alpha_old). The thermo-mech + * material must accept @c (t_info, state, grad_u, grad_v, theta, grad_theta, alpha, ...) — alpha is forwarded + * from the trailing variadic params. + * - @p thermal_system was built with @c (solid_fields, internal_variable_fields) coupling. The thermo-mech material + * must accept @c (t_info, state, grad_u, grad_v, theta, grad_theta, alpha, ...). + * - @p internal_variable_system was built with @c solid_fields coupling. The evolution callable must accept + * @c (t_info, alpha, alpha_dot, grad_u, ...). + * - The internal-variable time rule's @c value() must equal its current state (true for backward Euler), so the + * raw @c alpha solve-state may be forwarded into the thermo-mech material in place of an interpolated alpha. + * + * Routing through @c solid->setMaterial and @c thermal->setMaterialAndHeatSource means cycle-zero and + * stress-output paths automatically receive the coupled stress contribution. * * @tparam SolidSystem Type of the solid mechanics system. * @tparam ThermalSystem Type of the thermal system. @@ -26,7 +42,7 @@ namespace smith { * @param solid_system The solid mechanics system. * @param thermal_system The thermal system. * @param internal_variable_system The internal variable system. - * @param thermo_mech_material The material model for stress, heat capacity, and heat flux. + * @param thermo_mech_material The material model for stress, heat capacity, heat source, and heat flux. * @param internal_var_evolution The ODE residual for the internal variable. * @param domain_name The domain name to apply the material to. */ @@ -39,40 +55,7 @@ void setCoupledThermoMechanicsInternalVariableMaterial(std::shared_ptrdisp_time_rule; - auto temp_rule = thermal_system->temperature_time_rule; - - solid_system->solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto temperature, - auto temperature_old, auto alpha, auto alpha_old) { - auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - auto [T_current, T_dot] = temp_rule->interpolate(t_info, temperature, temperature_old); - auto [pk, Cv, s0, q0] = - thermo_mech_material(t_info, get(alpha), get(u_current), get(v_current), - get(T_current), get(T_current), get(alpha_old)); - return smith::tuple{get(a_current) * thermo_mech_material.density, pk}; - }); - - thermal_system->thermal_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto T, auto T_old, auto disp, auto disp_old, auto v_old, auto a_old) { - auto [T_current, T_dot] = temp_rule->interpolate(t_info, T, T_old); - auto [u_current, v_current, a_current] = disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); - - // Alpha is not directly in thermal weak form coupling right now based on test structure, - // but typically it's evaluated via thermo_mech_material. Wait, the test does not pass alpha - // to thermal weak form. So we can use a dummy alpha if it's not passed, or it should be passed? - // Let's copy exactly what the test had. - auto [pk, C_v, s0, q0] = - thermo_mech_material(t_info, 0.0, get(u_current), get(v_current), - get(T_current), get(T_current), 0.0); - return smith::tuple{C_v * get(T_dot) - s0, -q0}; - }); - - // For the internal variables, the test uses a helper `setCoupledInternalVariableMaterial`, but the prompt - // says the signature takes `internal_var_evolution`, which is an ODE residual. - // Wait, the prompt says we should call: - // setCoupledThermoMechanicsInternalVariableMaterial(solid, thermal, internal_vars, thermo_mech_mat, internal_var_evo, - // domain); So we just register the internal_var_evolution using `addEvolution`! + setCoupledThermoMechanicsMaterial(solid_system, thermal_system, thermo_mech_material, domain_name); setCoupledInternalVariableMaterial(internal_variable_system, solid_system, internal_var_evolution, domain_name); } From df6dffad7147a2681185d1b56b4f8c4b9b904793 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 27 Apr 2026 14:02:55 -0700 Subject: [PATCH 46/67] Fix docs. --- src/smith/differentiable_numerics/coupling_params.hpp | 7 +++++-- src/smith/differentiable_numerics/system_base.hpp | 2 ++ src/smith/physics/functional_weak_form.hpp | 8 ++++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 555a2e7399..4c0cdb787f 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -214,16 +214,19 @@ void registerParamsIfNeeded(std::shared_ptr fs, const Pack& pack) template struct TimeRuleParamsHelper; +/// @brief Maps `CouplingParams` packs to weak-form time-rule parameters. template struct TimeRuleParamsHelper> { - using type = smith::TimeRuleParams; + using type = smith::TimeRuleParams; ///< Resolved weak-form time-rule parameter list. }; +/// @brief Maps `PhysicsFields` packs to weak-form time-rule parameters. template struct TimeRuleParamsHelper> { - using type = smith::TimeRuleParams; + using type = smith::TimeRuleParams; ///< Resolved weak-form time-rule parameter list. }; +/// @brief Convenience alias selecting time-rule parameters for coupling pack type. template using TimeRuleParams = typename TimeRuleParamsHelper::type; diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index 49a2a26bfb..7421ec828d 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -108,6 +108,7 @@ inline std::shared_ptr makeSystem(std::shared_ptr field_ namespace smith { namespace detail { +/// @brief Expands coupling tuple into trailing weak-form space arguments. template auto buildWeakFormWithCouplingImpl(const FieldStorePtr& field_store, const std::string& weak_form_name, const std::string& unknown_field_name, const std::tuple& args_tuple, @@ -124,6 +125,7 @@ auto buildWeakFormWithCouplingImpl(const FieldStorePtr& field_store, const std:: coupling_fields_tuple); } +/// @brief Builds weak form using regular args plus final coupling pack argument. template auto buildWeakFormWithCoupling(const FieldStorePtr& field_store, const std::string& weak_form_name, const std::string& unknown_field_name, const Args&... args) diff --git a/src/smith/physics/functional_weak_form.hpp b/src/smith/physics/functional_weak_form.hpp index 18c33a8e1e..26cb4a70d8 100644 --- a/src/smith/physics/functional_weak_form.hpp +++ b/src/smith/physics/functional_weak_form.hpp @@ -434,6 +434,7 @@ class FunctionalWeakForm, protected: template + /// @brief Forwards body integrand with dependency list covering all input parameters. void addBodyIntegralWithAllParams(std::string body_name, BodyIntegralType integrand, std::integer_sequence) { @@ -441,6 +442,7 @@ class FunctionalWeakForm, } template + /// @brief Forwards body source with dependency list covering all input parameters. void addBodySourceWithAllParams(std::string body_name, BodyLoadType load_function, std::integer_sequence) { @@ -448,6 +450,7 @@ class FunctionalWeakForm, } template + /// @brief Forwards boundary integrand with dependency list covering all input parameters. void addBoundaryIntegralWithAllParams(std::string boundary_name, BoundaryIntegrandType integrand, std::integer_sequence) { @@ -455,6 +458,7 @@ class FunctionalWeakForm, } template + /// @brief Forwards boundary flux with dependency list covering all input parameters. void addBoundaryFluxWithAllParams(std::string boundary_name, BoundaryFluxType flux_function, std::integer_sequence) { @@ -462,6 +466,7 @@ class FunctionalWeakForm, } template + /// @brief Forwards interior-boundary integrand with dependency list covering all input parameters. void addInteriorBoundaryIntegralWithAllParams(std::string interior_name, InteriorIntegrandType integrand, std::integer_sequence) { @@ -469,6 +474,7 @@ class FunctionalWeakForm, } template + /// @brief Calls integrand with `TimeInfo` when available, else with scalar time. static auto invokeTimeAwareIntegrand(const IntegrandType& integrand, double time, double dt, size_t cycle, XType&& X, InputTypes&&... inputs) { @@ -482,6 +488,7 @@ class FunctionalWeakForm, } template + /// @brief Calls source with `TimeInfo` when available, else with scalar time. static auto invokeTimeAwareSource(const LoadFunctionType& load_function, const TimeInfo& t_info, XType&& X, InputTypes&&... inputs) { @@ -493,6 +500,7 @@ class FunctionalWeakForm, } template + /// @brief Calls flux with `TimeInfo` when available, else with scalar time. static auto invokeTimeAwareFlux(const FluxFunctionType& flux_function, const TimeInfo& t_info, XType&& X, NType&& n, InputTypes&&... inputs) { From e4a18125bb07d5579bedde79dd0c28ed2e742869 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 8 May 2026 20:13:53 -0600 Subject: [PATCH 47/67] Really refactor to use TimeInfo. --- .../inertia_relief/inertia_relief_example.cpp | 8 +-- .../composable_thermo_mechanics_advanced.cpp | 2 +- .../differentiable_numerics/CMakeLists.txt | 3 + .../lumped_mass_weak_form.hpp | 2 +- .../tests/test_combined_thermo_mechanics.cpp | 2 +- .../tests/test_porous_heat_sink.cpp | 6 +- src/smith/physics/functional_objective.hpp | 17 +----- src/smith/physics/functional_weak_form.hpp | 59 +++---------------- .../tests/test_functional_weak_form.cpp | 6 +- .../tests/test_kinematic_objective.cpp | 8 +-- 10 files changed, 28 insertions(+), 85 deletions(-) diff --git a/examples/inertia_relief/inertia_relief_example.cpp b/examples/inertia_relief/inertia_relief_example.cpp index c9676a897c..d9abebe15f 100644 --- a/examples/inertia_relief/inertia_relief_example.cpp +++ b/examples/inertia_relief/inertia_relief_example.cpp @@ -213,7 +213,7 @@ int main(int argc, char* argv[]) ObjectiveT mass_objective("mass constraining", mesh, param_space_ptrs); mass_objective.addBodyIntegral(smith::DependsOn<1>{}, mesh->entireBodyName(), - [](double /*t*/, auto /*X*/, auto RHO) { return get(RHO); }); + [](auto /*t_info*/, auto /*X*/, auto RHO) { return get(RHO); }); double mass = mass_objective.evaluate(time_info, shape_disp.get(), objective_states); smith::tensor initial_cg; // center of gravity @@ -221,9 +221,7 @@ int main(int argc, char* argv[]) for (int i = 0; i < dim; ++i) { auto cg_objective = std::make_shared("translation " + std::to_string(i), mesh, param_space_ptrs); cg_objective->addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), - [i](double - /*time*/, - auto X, auto U, auto RHO) { + [i](auto /*t_info*/, auto X, auto U, auto RHO) { return (get(X)[i] + get(U)[i]) * get(RHO); }); initial_cg[i] = cg_objective->evaluate(time_info, shape_disp.get(), objective_states) / mass; @@ -235,7 +233,7 @@ int main(int argc, char* argv[]) auto center_rotation_objective = std::make_shared("rotation" + std::to_string(i), mesh, param_space_ptrs); center_rotation_objective->addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), - [i, initial_cg](double /*time*/, auto X, auto U, auto RHO) { + [i, initial_cg](auto /*t_info*/, auto X, auto U, auto RHO) { auto u = get(U); auto x = get(X) + u; auto dx = x - initial_cg; diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 2922fb8b13..fe4a793c37 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -124,7 +124,7 @@ int main(int argc, char* argv[]) }; smith::FunctionalObjective> qoi("thermo_mechanical_energy_proxy", mesh, smith::spaces(fetch_qoi_fields())); - qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](double, auto /*X*/, auto U, auto Theta) { + qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](auto, auto /*X*/, auto U, auto Theta) { auto u = smith::get(U); auto theta = smith::get(Theta); return 0.5 * u[0] * u[0] + 0.05 * theta * theta; diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index 8b36d9cdb4..c9421cd26d 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -41,6 +41,9 @@ set(differentiable_numerics_headers state_variable_system.hpp thermal_system.hpp thermo_mechanics_system.hpp + thermo_mechanics_with_internal_vars_system.hpp + time_info_solid_materials.hpp + time_info_thermo_mechanical_materials.hpp coupling_params.hpp combined_system.hpp system_base.hpp diff --git a/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp b/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp index 5799d53e2b..fbea61b3be 100644 --- a/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp +++ b/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp @@ -27,7 +27,7 @@ auto createSolidMassWeakForm(const std::string& physics_name, std::shared_ptr>; auto weak_form = std::make_shared(physics_name, mesh, lumped_field.space(), typename WeakFormT::SpacesT{&density.space()}); - weak_form->addBodyIntegral(smith::DependsOn<0>{}, mesh->entireBodyName(), [](double /*time*/, auto /*X*/, auto Rho) { + weak_form->addBodyIntegral(smith::DependsOn<0>{}, mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto Rho) { if constexpr (lumped_dim == 1) { return smith::tuple{get(Rho), tensor{}}; } else { diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 6c1456db1c..9e8895b1ae 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -247,7 +247,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) "staggered_qoi", mesh_, spaces({coupled_system->field_store->getField("displacement"), coupled_system->field_store->getField("temperature")})); - qoi.addBodyIntegral(DependsOn<0, 1>{}, mesh_->entireBodyName(), [](double, auto, auto U, auto Theta) { + qoi.addBodyIntegral(DependsOn<0, 1>{}, mesh_->entireBodyName(), [](auto, auto, auto U, auto Theta) { auto u = get(U); auto theta = get(Theta); return 0.5 * u[0] * u[0] + 0.05 * theta * theta; diff --git a/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp b/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp index 3944016934..a1fec16736 100644 --- a/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp +++ b/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp @@ -185,14 +185,14 @@ TEST_P(BlockPreconditionerTest, BlockSolve) gamma.get()->project(gamma_coef); T1_form.addBodyIntegral(DependsOn<0, 1, 2>{}, mesh->entireBodyName(), - [sigma, a, q_n](double /* t */, auto /* x */, auto T_1, auto T_2, auto gamma_) { + [sigma, a, q_n](auto /* t_info */, auto /* x */, auto T_1, auto T_2, auto gamma_) { auto [T_1_val, dT_1_dX] = T_1; auto [T_2_val, dT_2_dX] = T_2; auto [gamma_val, dgamma_dX] = gamma_; return smith::tuple{a(gamma_val) * q_n(T_1_val, T_2_val), sigma(gamma_val) * dT_1_dX}; }); T2_form.addBodyIntegral(DependsOn<0, 1, 2>{}, mesh->entireBodyName(), - [kappa, a, q_n](double /* t */, auto /* x */, auto T_1, auto T_2, auto gamma_) { + [kappa, a, q_n](auto /* t_info */, auto /* x */, auto T_1, auto T_2, auto gamma_) { auto [T_1_val, dT_1_dX] = T_1; auto [T_2_val, dT_2_dX] = T_2; auto [gamma_val, dgamma_dX] = gamma_; @@ -208,7 +208,7 @@ TEST_P(BlockPreconditionerTest, BlockSolve) mesh->addDomainOfBoundaryElements("heat_spreader", by_attr<2>(2)); T1_form.addBoundaryIntegral(DependsOn<>{}, "heat_spreader", - [heatsink_options = heatsink_options](double, auto) { return heatsink_options.q_app; }); + [heatsink_options = heatsink_options](auto, auto) { return heatsink_options.q_app; }); // Block Preconditioner Options smith::LinearSolverOptions linear_options; diff --git a/src/smith/physics/functional_objective.hpp b/src/smith/physics/functional_objective.hpp index 6deb094c23..fb07fe59e5 100644 --- a/src/smith/physics/functional_objective.hpp +++ b/src/smith/physics/functional_objective.hpp @@ -76,7 +76,7 @@ class FunctionalObjective, std::integer_ objective_->AddDomainIntegral( smith::Dimension{}, smith::DependsOn{}, [dt, cycle, qfunction](double time, auto X, auto... params) { - return invokeTimeAwareIntegrand(qfunction, time, *dt, *cycle, X, params...); + return qfunction(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(body_name)); } @@ -97,7 +97,7 @@ class FunctionalObjective, std::integer_ objective_->AddBoundaryIntegral( smith::Dimension{}, smith::DependsOn{}, [dt, cycle, qfunction](double time, auto X, auto... params) { - return invokeTimeAwareIntegrand(qfunction, time, *dt, *cycle, X, params...); + return qfunction(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(boundary_name)); } @@ -139,19 +139,6 @@ class FunctionalObjective, std::integer_ } private: - template - static auto invokeTimeAwareIntegrand(const FuncOfTimeSpaceAndParams& qfunction, double time, double dt, size_t cycle, - XType&& X, ParamTypes&&... params) - { - if constexpr (requires { - qfunction(TimeInfo(time, dt, cycle), std::forward(X), std::forward(params)...); - }) { - return qfunction(TimeInfo(time, dt, cycle), std::forward(X), std::forward(params)...); - } else { - return qfunction(time, std::forward(X), std::forward(params)...); - } - } - /// @brief Utility to evaluate residual using all fields in vector template auto evaluateObjective(std::integer_sequence, double time, ConstFieldPtr shape_disp, diff --git a/src/smith/physics/functional_weak_form.hpp b/src/smith/physics/functional_weak_form.hpp index 26cb4a70d8..af818ac253 100644 --- a/src/smith/physics/functional_weak_form.hpp +++ b/src/smith/physics/functional_weak_form.hpp @@ -115,14 +115,14 @@ class FunctionalWeakForm, weak_form_->AddDomainIntegral( Dimension{}, DependsOn{}, [dt, cycle, integrand](double time, auto X, auto... inputs) { - return invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, inputs...); + return integrand(TimeInfo(time, *dt, *cycle), X, inputs...); }, mesh_->domain(body_name)); v_dot_weak_form_residual_->AddDomainIntegral( Dimension{}, DependsOn<0, 1 + active_parameters...>{}, [dt, cycle, integrand](double time, auto X, auto V, auto... inputs) { - auto orig_tuple = invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, inputs...); + auto orig_tuple = integrand(TimeInfo(time, *dt, *cycle), X, inputs...); return smith::inner(get(V), get(orig_tuple)) + smith::inner(get(V), get(orig_tuple)); }, @@ -160,8 +160,7 @@ class FunctionalWeakForm, void addBodySource(DependsOn depends_on, std::string body_name, BodyLoadType load_function) { addBodyIntegral(depends_on, body_name, [load_function](const TimeInfo& t_info, auto X, auto... inputs) { - return smith::tuple{-invokeTimeAwareSource(load_function, t_info, get(X), get(inputs)...), - smith::zero{}}; + return smith::tuple{-load_function(t_info, get(X), get(inputs)...), smith::zero{}}; }); } @@ -204,14 +203,14 @@ class FunctionalWeakForm, weak_form_->AddBoundaryIntegral( Dimension{}, DependsOn{}, [dt, cycle, integrand](double time, auto X, auto... params) { - return invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); + return integrand(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(boundary_name)); v_dot_weak_form_residual_->AddBoundaryIntegral( Dimension{}, DependsOn<0, 1 + active_parameters...>{}, [dt, cycle, integrand](double time, auto X, auto V, auto... params) { - auto orig_surface_flux = invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); + auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle), X, params...); return smith::inner(get(V), orig_surface_flux); }, mesh_->domain(boundary_name)); @@ -252,7 +251,7 @@ class FunctionalWeakForm, weak_form_->AddInteriorFaceIntegral( Dimension{}, DependsOn{}, [dt, cycle, integrand](double time, auto X, auto... params) { - return invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); + return integrand(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(interior_name)); @@ -260,7 +259,7 @@ class FunctionalWeakForm, Dimension{}, DependsOn<0, 1 + active_parameters...>{}, [dt, cycle, integrand](double time, auto X, auto V, auto... params) { auto [V1, V2] = V; - auto orig_surface_flux = invokeTimeAwareIntegrand(integrand, time, *dt, *cycle, X, params...); + auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle), X, params...); auto [flux_pos, flux_neg] = orig_surface_flux; return smith::inner(V1, flux_pos) + smith::inner(V2, flux_neg); }, @@ -302,7 +301,7 @@ class FunctionalWeakForm, { addBoundaryIntegral(depends_on, boundary_name, [flux_function](const TimeInfo& t_info, auto X, auto... inputs) { auto n = cross(get(X)); - return -invokeTimeAwareFlux(flux_function, t_info, get(X), normalize(n), get(inputs)...); + return -flux_function(t_info, get(X), normalize(n), get(inputs)...); }); } @@ -473,48 +472,6 @@ class FunctionalWeakForm, this->template addInteriorBoundaryIntegral(DependsOn{}, interior_name, integrand); } - template - /// @brief Calls integrand with `TimeInfo` when available, else with scalar time. - static auto invokeTimeAwareIntegrand(const IntegrandType& integrand, double time, double dt, size_t cycle, XType&& X, - InputTypes&&... inputs) - { - if constexpr (requires { - integrand(TimeInfo(time, dt, cycle), std::forward(X), std::forward(inputs)...); - }) { - return integrand(TimeInfo(time, dt, cycle), std::forward(X), std::forward(inputs)...); - } else { - return integrand(time, std::forward(X), std::forward(inputs)...); - } - } - - template - /// @brief Calls source with `TimeInfo` when available, else with scalar time. - static auto invokeTimeAwareSource(const LoadFunctionType& load_function, const TimeInfo& t_info, XType&& X, - InputTypes&&... inputs) - { - if constexpr (requires { load_function(t_info, std::forward(X), std::forward(inputs)...); }) { - return load_function(t_info, std::forward(X), std::forward(inputs)...); - } else { - return load_function(t_info.time(), std::forward(X), std::forward(inputs)...); - } - } - - template - /// @brief Calls flux with `TimeInfo` when available, else with scalar time. - static auto invokeTimeAwareFlux(const FluxFunctionType& flux_function, const TimeInfo& t_info, XType&& X, NType&& n, - InputTypes&&... inputs) - { - if constexpr (requires { - flux_function(t_info, std::forward(X), std::forward(n), - std::forward(inputs)...); - }) { - return flux_function(t_info, std::forward(X), std::forward(n), std::forward(inputs)...); - } else { - return flux_function(t_info.time(), std::forward(X), std::forward(n), - std::forward(inputs)...); - } - } - /// @brief Helper to validate input spaces recursively (for constructor) template void validateInputSpaces(const SpacesT& input_mfem_spaces) const diff --git a/src/smith/physics/tests/test_functional_weak_form.cpp b/src/smith/physics/tests/test_functional_weak_form.cpp index 14d9b96b3f..dc32706636 100644 --- a/src/smith/physics/tests/test_functional_weak_form.cpp +++ b/src/smith/physics/tests/test_functional_weak_form.cpp @@ -99,11 +99,11 @@ struct WeakFormFixture : public testing::Test { mesh->addDomainOfBoundaryElements(surface_name, smith::by_attr(1)); f_weak_form->addBoundaryFlux(smith::DependsOn<>{}, surface_name, - [](double /*t*/, auto /*x*/, auto n) { return 1.0 * n; }); + [](auto /*t_info*/, auto /*x*/, auto n) { return 1.0 * n; }); f_weak_form->addBodySource(smith::DependsOn<0>{}, mesh->entireBodyName(), - [](double /*t*/, auto /*x*/, auto u) { return u; }); + [](auto /*t_info*/, auto /*x*/, auto u) { return u; }); f_weak_form->addBodySource(smith::DependsOn<>{}, mesh->entireBodyName(), - [](double /*t*/, auto x) { return 0.5 * x; }); + [](auto /*t_info*/, auto x) { return 0.5 * x; }); // initialize fields for testing diff --git a/src/smith/physics/tests/test_kinematic_objective.cpp b/src/smith/physics/tests/test_kinematic_objective.cpp index 60e4378cab..50f8ce9163 100644 --- a/src/smith/physics/tests/test_kinematic_objective.cpp +++ b/src/smith/physics/tests/test_kinematic_objective.cpp @@ -91,7 +91,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { ObjectiveT mass_objective("mass constraining", mesh, param_space_ptrs); mass_objective.addBodyIntegral(smith::DependsOn<1>{}, mesh->entireBodyName(), - [](double /*time*/, auto /*X*/, auto RHO) { return get(RHO); }); + [](auto /*t_info*/, auto /*X*/, auto RHO) { return get(RHO); }); double mass = mass_objective.evaluate(time_info, shape_disp.get(), objective_states); @@ -101,9 +101,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { auto cg_objective = std::make_shared("translation" + std::to_string(i), mesh, param_space_ptrs); cg_objective->addBodyIntegral( smith::DependsOn<0, 1>{}, mesh->entireBodyName(), - [i](double - /*time*/, - auto X, auto U, + [i](auto /*t_info*/, auto X, auto U, auto RHO) { return (get(X)[i] + get(U)[i]) * get(RHO); }); initial_cg[i] = cg_objective->evaluate(time_info, shape_disp.get(), objective_states) / mass; constraint_evaluators.push_back(cg_objective); @@ -113,7 +111,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { auto center_rotation_objective = std::make_shared("rotation" + std::to_string(i), mesh, param_space_ptrs); center_rotation_objective->addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), - [i, initial_cg](double /*time*/, auto X, auto U, auto RHO) { + [i, initial_cg](auto /*t_info*/, auto X, auto U, auto RHO) { auto u = get(U); auto x = get(X) + u; auto dx = x - initial_cg; From 7503dd1e6d366e30a59ab1501e2505721aec23fa Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sat, 9 May 2026 18:45:30 -0600 Subject: [PATCH 48/67] Cleanup the interface a bit more to hide field_store when appropriate. --- .../composable_solid_mechanics.cpp | 75 +++++++++---------- .../coupling_params.hpp | 18 +++++ .../differentiable_physics.cpp | 21 ++++++ .../differentiable_physics.hpp | 9 +++ .../dirichlet_boundary_conditions.cpp | 21 ++++-- .../dirichlet_boundary_conditions.hpp | 20 +++-- .../differentiable_numerics/field_store.cpp | 53 ++++++++----- .../differentiable_numerics/field_store.hpp | 42 +++-------- .../solid_mechanics_system.hpp | 58 ++++++-------- .../test_multiphysics_time_integrator.cpp | 55 ++++++++++++++ .../tests/test_solid_dynamics.cpp | 36 +++------ .../tests/test_thermal_static.cpp | 12 +-- .../thermal_system.hpp | 56 +++++--------- .../tests/test_kinematic_objective.cpp | 6 +- 14 files changed, 268 insertions(+), 214 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 85669cf879..199bc4f204 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -89,8 +89,6 @@ int main(int argc, char* argv[]) .max_iterations = 15, .print_level = 0}; - auto field_store = std::make_shared(mesh, 100); - smith::SolidMechanicsOptions output_options{.enable_stress_output = true, .output_cauchy_stress = true}; auto param_fields = smith::registerParameterFields(smith::FieldType>("youngs_modulus")); // _solver_end @@ -98,30 +96,11 @@ int main(int argc, char* argv[]) // _build_start auto solid_system = smith::buildSolidMechanicsSystem( - nonlinear_options, linear_options, output_options, field_store, param_fields); + nonlinear_options, linear_options, output_options, mesh, param_fields); constexpr double E = 100.0; constexpr double nu = 0.25; solid_system->setMaterial(YoungsModulusNeoHookeanWithTimeInfo{.density = 1.0, .nu = nu}, mesh->entireBodyName()); - field_store->getParameterFields()[0].get()->setFromFieldFunction([=](smith::tensor) { return E; }); - - auto initial_displacement = [](smith::tensor X) { - auto displacement = 0.0 * X; - displacement[0] = 1.0e-3 * X[0]; - return displacement; - }; - auto initial_velocity = [](smith::tensor X) { - auto velocity = 0.0 * X; - velocity[1] = 2.0e-2 * X[0]; - return velocity; - }; - - field_store->getField("displacement_solve_state").get()->setFromFieldFunction(initial_displacement); - field_store->getField("displacement").get()->setFromFieldFunction(initial_displacement); - field_store->getField("velocity").get()->setFromFieldFunction(initial_velocity); - field_store->getField("acceleration").get()->setFromFieldFunction([](smith::tensor) { - return smith::tensor{}; - }); // _build_end // _bc_start @@ -137,23 +116,42 @@ int main(int argc, char* argv[]) return traction; }); - auto output_states = field_store->getOutputFieldStates(); - auto writer = smith::createParaviewWriter(*mesh, output_states, "paraview_composable_solid_mechanics", - smith::ParaviewWriter::Options{.write_duals = false}); - // _bc_end - - // _run_start if (solid_system->cycle_zero_systems.empty()) { throw std::runtime_error("Expected cycle-zero solve for implicit dynamics."); } auto physics = smith::makeDifferentiablePhysics(solid_system, "composable_solid_mechanics"); - auto initial_states = physics->getInitialFieldStates(); + physics->getFieldParam("param_youngs_modulus").get()->setFromFieldFunction([=](smith::tensor) { + return E; + }); + + auto initial_displacement = [](smith::tensor X) { + auto displacement = 0.0 * X; + displacement[0] = 1.0e-3 * X[0]; + return displacement; + }; + auto initial_velocity = [](smith::tensor X) { + auto velocity = 0.0 * X; + velocity[1] = 2.0e-2 * X[0]; + return velocity; + }; + + physics->getInitialFieldState("displacement_solve_state").get()->setFromFieldFunction(initial_displacement); + physics->getInitialFieldState("displacement").get()->setFromFieldFunction(initial_displacement); + physics->getInitialFieldState("velocity").get()->setFromFieldFunction(initial_velocity); + physics->getInitialFieldState("acceleration").get()->setFromFieldFunction([](smith::tensor) { + return smith::tensor{}; + }); + + auto writer = + smith::createParaviewWriter(*mesh, physics->getFieldStatesAndParamStates(), "paraview_composable_solid_mechanics", + smith::ParaviewWriter::Options{.write_duals = false}); + // _bc_end + + // _run_start using DispSpace = smith::H1; - const auto displacement_index = field_store->getFieldIndex("displacement"); - const auto velocity_index = field_store->getFieldIndex("velocity"); - const auto qoi_fields = - std::vector{initial_states[displacement_index], initial_states[velocity_index]}; + const auto qoi_fields = std::vector{physics->getInitialFieldState("displacement"), + physics->getInitialFieldState("velocity")}; smith::FunctionalObjective> qoi("solid_dynamic_energy_proxy", mesh, smith::spaces(qoi_fields)); qoi.addBodyIntegral( @@ -166,8 +164,7 @@ int main(int argc, char* argv[]) constexpr double dt = 0.25; constexpr int num_steps = 3; auto current_qoi_fields = [&]() { - return std::vector{physics->getFieldStates()[displacement_index], - physics->getFieldStates()[velocity_index]}; + return std::vector{physics->getFieldState("displacement"), physics->getFieldState("velocity")}; }; auto qoi_state = 0.0 * smith::evaluateObjective(qoi, physics->getShapeDispFieldState(), qoi_fields, @@ -184,9 +181,9 @@ int main(int argc, char* argv[]) std::cout << "QoI value: " << qoi_state.get() << '\n'; qoi_state.data_store().back_prop(); auto shape_displacement = physics->getShapeDispFieldState(); - auto initial_displacement_state = initial_states[displacement_index]; - auto initial_velocity_state = initial_states[velocity_index]; - auto youngs_modulus_state = field_store->getParameterFields()[0]; + auto initial_displacement_state = physics->getInitialFieldState("displacement"); + auto initial_velocity_state = physics->getInitialFieldState("velocity"); + auto youngs_modulus_state = physics->getFieldParam("param_youngs_modulus"); std::cout << "dQoI/d(shape) norm: " << shape_displacement.get_dual()->Norml2() << '\n'; std::cout << "dQoI/d(youngs_modulus) norm: " << youngs_modulus_state.get_dual()->Norml2() << '\n'; std::cout << "dQoI/d(initial displacement) norm: " << initial_displacement_state.get_dual()->Norml2() << '\n'; @@ -201,7 +198,7 @@ int main(int argc, char* argv[]) // _run_end // _output_start - writer.write(physics->cycle(), physics->time(), field_store->getOutputFieldStates()); + writer.write(physics->cycle(), physics->time(), physics->getFieldStatesAndParamStates()); std::cout << "ParaView output: paraview_composable_solid_mechanics\n"; // _output_end diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 4c0cdb787f..180e21095f 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -19,6 +19,8 @@ #pragma once +#include + #include "smith/differentiable_numerics/field_store.hpp" #include "smith/differentiable_numerics/system_base.hpp" @@ -136,6 +138,22 @@ template inline constexpr bool has_time_rule_v>> = !std::is_same_v::time_rule_type, NoTimeRule>; +template +inline constexpr bool is_field_store_ptr_v = std::is_same_v, std::shared_ptr>; + +template +inline constexpr bool is_mesh_ptr_v = std::is_same_v, std::shared_ptr>; + +template +auto getOrCreateFieldStore(T source, std::string prefix = "", size_t storage_size = 100) +{ + if constexpr (is_field_store_ptr_v) { + return source; + } else { + return std::make_shared(source, storage_size, prefix); + } +} + // ------------------------------------------------------------------------- // Helpers for variadic build functions // ------------------------------------------------------------------------- diff --git a/src/smith/differentiable_numerics/differentiable_physics.cpp b/src/smith/differentiable_numerics/differentiable_physics.cpp index 9e95173fbb..46892b2042 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.cpp +++ b/src/smith/differentiable_numerics/differentiable_physics.cpp @@ -320,6 +320,27 @@ std::vector DifferentiablePhysics::getFieldStatesAndParamStates() co return fields; } +FieldState DifferentiablePhysics::getInitialFieldState(const std::string& state_name) const +{ + SLIC_ERROR_IF(state_name_to_field_index_.find(state_name) == state_name_to_field_index_.end(), + std::format("Could not find initial field named {0}", state_name)); + return initial_field_states_[state_name_to_field_index_.at(state_name)]; +} + +FieldState DifferentiablePhysics::getFieldState(const std::string& state_name) const +{ + SLIC_ERROR_IF(state_name_to_field_index_.find(state_name) == state_name_to_field_index_.end(), + std::format("Could not find field named {0}", state_name)); + return field_states_[state_name_to_field_index_.at(state_name)]; +} + +FieldState DifferentiablePhysics::getFieldParam(const std::string& param_name) const +{ + SLIC_ERROR_IF(param_name_to_field_index_.find(param_name) == param_name_to_field_index_.end(), + std::format("Could not find parameter named {0}", param_name)); + return field_params_[param_name_to_field_index_.at(param_name)]; +} + FieldState DifferentiablePhysics::getShapeDispFieldState() const { return *field_shape_displacement_; } void DifferentiablePhysics::initializeReactionStates() diff --git a/src/smith/differentiable_numerics/differentiable_physics.hpp b/src/smith/differentiable_numerics/differentiable_physics.hpp index 623861c8c7..1ff8dbf1b5 100644 --- a/src/smith/differentiable_numerics/differentiable_physics.hpp +++ b/src/smith/differentiable_numerics/differentiable_physics.hpp @@ -139,13 +139,22 @@ class DifferentiablePhysics : public BasePhysics { /// @return Copies of the tracked initial state fields. std::vector getInitialFieldStates() const { return initial_field_states_; } + /// @brief Get a tracked initial state field by name. + FieldState getInitialFieldState(const std::string& state_name) const; + /// @brief Get the current primal state fields. /// @return Copies of the tracked state fields at the current cycle. std::vector getFieldStates() const { return field_states_; } + /// @brief Get a tracked current state field by name. + FieldState getFieldState(const std::string& state_name) const; + /// @brief Get all the parameter FieldStates std::vector getFieldParams() const { return field_params_; } + /// @brief Get a tracked parameter field by name. + FieldState getFieldParam(const std::string& param_name) const; + /// @brief Get the tracked state fields followed by the tracked parameter fields. /// @return A concatenated vector of state fields then parameter fields. std::vector getFieldStatesAndParamStates() const; diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp index 15006e8b24..fce104e9aa 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.cpp @@ -75,7 +75,7 @@ class SecondTimeDerivativeVectorCoefficient : public mfem::VectorCoefficient { DirichletBoundaryConditions::DirichletBoundaryConditions(const mfem::ParMesh& mfem_mesh, mfem::ParFiniteElementSpace& space) - : bcs_(mfem_mesh), space_(space) + : mfem_mesh_(mfem_mesh), bcs_(mfem_mesh), space_(space) { } @@ -84,17 +84,24 @@ DirichletBoundaryConditions::DirichletBoundaryConditions(const Mesh& mesh, mfem: { } -void DirichletBoundaryConditions::setSecondTimeDerivativeBCsMatchingDofs(const BoundaryConditionManager& source) +const BoundaryConditionManager& DirichletBoundaryConditions::getSecondDerivativeManager() const { - for (const auto& bc : source.essentials()) { + second_derivative_manager_.emplace(mfem_mesh_); + rebuildSecondDerivativeManager(*second_derivative_manager_); + return *second_derivative_manager_; +} + +void DirichletBoundaryConditions::rebuildSecondDerivativeManager(BoundaryConditionManager& target) const +{ + for (const auto& bc : bcs_.essentials()) { if (is_vector_valued(bc.coefficient())) { - auto accel_coef = std::make_shared( + auto deriv_coef = std::make_shared( get>(bc.coefficient())); - bcs_.addEssentialByTrueDofs(bc.getTrueDofList(), accel_coef, space_); + target.addEssentialByTrueDofs(bc.getTrueDofList(), deriv_coef, space_); } else { - auto accel_coef = std::make_shared( + auto deriv_coef = std::make_shared( get>(bc.coefficient())); - bcs_.addEssential(bc.getLocalDofList(), accel_coef, space_, bc.component()); + target.addEssential(bc.getLocalDofList(), deriv_coef, space_, bc.component()); } } } diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp index bdca440568..69d4e4299f 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp @@ -12,6 +12,8 @@ #pragma once +#include + #include "smith/physics/boundary_conditions/boundary_condition_manager.hpp" namespace smith { @@ -158,20 +160,24 @@ class DirichletBoundaryConditions { setFixedVectorBCs(domain, components); } - /// @brief Return the smith BoundaryConditionManager + /// @brief Return the value-level smith BoundaryConditionManager. const smith::BoundaryConditionManager& getBoundaryConditionManager() const { return bcs_; } - /// @brief Constrain the same DOFs as @p source, prescribing the time second derivative of the source values. + /// @brief Return the BC manager whose prescribed values are the second time derivative of the + /// value-level BC, computed by one-sided three-point forward FD with step + /// @c h = 1e-4 * max(1,|t|). /// - /// Used for the cycle-zero acceleration BC: the constrained DOF set mirrors the displacement BC - /// (same mesh nodes, same components), and the prescribed acceleration is approximated by a - /// forward finite difference of the displacement boundary condition in time. - /// Must be called after the user has finished calling @c set*BCs on @p source. - void setSecondTimeDerivativeBCsMatchingDofs(const BoundaryConditionManager& source); + /// Rebuilt on each call from current value-level essentials, so late additions are reflected. + const smith::BoundaryConditionManager& getSecondDerivativeManager() const; private: + void rebuildSecondDerivativeManager(BoundaryConditionManager& target) const; + + const mfem::ParMesh& mfem_mesh_; ///< for constructing derivative-level managers smith::BoundaryConditionManager bcs_; ///< boundary condition manager that does the heavy lifting mfem::ParFiniteElementSpace& space_; ///< save the space for the field which will be constrained + + mutable std::optional second_derivative_manager_; }; } // namespace smith diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 22367dc2a3..4db7b5ba0f 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -31,12 +31,6 @@ std::shared_ptr FieldStore::addBoundaryConditions(F return std::make_shared(mesh_->mfemParMesh(), field->space()); } -void FieldStore::shareBoundaryConditions(const std::string& name, - std::shared_ptr source_bc) -{ - zero_mirror_sources_[name] = std::move(source_bc); -} - void FieldStore::addWeakFormUnknownArg(std::string weak_form_name, std::string argument_name, size_t argument_index) { FieldLabel argument_name_and_index{.field_name = argument_name, .field_index_in_residual = argument_index}; @@ -133,27 +127,50 @@ std::vector> FieldStore::indexMap(const std::vector FieldStore::getBoundaryConditionManagers( - const std::vector& weak_form_names) + const std::vector& weak_form_names) const { + struct BoundaryConditionRef { + std::string primary_name; + bool use_second_derivative; + }; + std::map field_to_primary; + for (const auto& [_rule, mapping] : time_integration_rules_) { + if (!mapping.primary_name.empty()) { + field_to_primary[mapping.primary_name] = {mapping.primary_name, false}; + } + if (!mapping.history_name.empty()) { + field_to_primary[mapping.history_name] = {mapping.primary_name, false}; + } + if (!mapping.ddot_name.empty()) { + field_to_primary[mapping.ddot_name] = {mapping.primary_name, true}; + } + } + std::vector bcs; for (const auto& wf_name : weak_form_names) { const std::string reaction_name = getWeakFormReaction(wf_name); - // Lazily materialize any pending zero-mirror BCs on first access. - auto mirror_it = zero_mirror_sources_.find(reaction_name); - if (mirror_it != zero_mirror_sources_.end()) { - auto mirrored_bc = addBoundaryConditions(getField(reaction_name).get()); - mirrored_bc->setSecondTimeDerivativeBCsMatchingDofs(mirror_it->second->getBoundaryConditionManager()); - boundary_conditions_[reaction_name] = std::move(mirrored_bc); - zero_mirror_sources_.erase(mirror_it); + // Direct DBC entry takes precedence (e.g. an independent unknown like stress with its own BC). + auto direct = boundary_conditions_.find(reaction_name); + if (direct != boundary_conditions_.end()) { + bcs.push_back(&direct->second->getBoundaryConditionManager()); + continue; } - auto it = boundary_conditions_.find(reaction_name); - if (it != boundary_conditions_.end()) { - bcs.push_back(&it->second->getBoundaryConditionManager()); - } else { + // Otherwise resolve via the time-integration mapping that owns this reaction field. + auto ref_it = field_to_primary.find(reaction_name); + if (ref_it == field_to_primary.end()) { bcs.push_back(nullptr); + continue; + } + auto primary_it = boundary_conditions_.find(ref_it->second.primary_name); + if (primary_it == boundary_conditions_.end()) { + bcs.push_back(nullptr); + continue; } + const auto& dbc = *primary_it->second; + bcs.push_back(ref_it->second.use_second_derivative ? &dbc.getSecondDerivativeManager() + : &dbc.getBoundaryConditionManager()); } return bcs; } diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index bd9d3e3bb4..8602256cc6 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -337,31 +337,20 @@ struct FieldStore { /** * @brief Get the boundary condition managers for the given weak forms, one per residual row. * - * For each weak form in @p weak_form_names the reaction (test) field name is looked up, and - * the corresponding @c BoundaryConditionManager is returned. If no BC was registered for - * that field, @c nullptr is returned for that slot (the solver skips null entries). + * For each weak form in @p weak_form_names the reaction (test) field name is looked up. The + * returned manager is selected by consulting the registered @c TimeIntegrationMapping s: + * - reaction = primary or history slot -> value-level BC manager + * - reaction = second-derivative (ddot) slot -> second-derivative BC manager + * - reaction has its own DBC entry not tied to a mapping -> that DBC's value manager + * - otherwise -> @c nullptr (solver skips null entries) * - * Zero-mirror BCs registered via @c shareBoundaryConditions are materialized lazily on the - * first call to this method (so the user's @c set*BCs calls on the source BC are visible). + * The second-derivative manager is rebuilt on each call, so late value-BC additions are reflected. * * @param weak_form_names Ordered list of weak form names whose BCs are needed. * @return std::vector One entry per weak form, in order. */ std::vector getBoundaryConditionManagers( - const std::vector& weak_form_names); - - /** - * @brief Register a zero-valued mirror BC for @p name, sharing the constrained DOF set of @p source_bc. - * - * Used for fields whose constrained DOFs must match a reference field (e.g. acceleration mirrors - * displacement), but whose prescribed BC value is always zero. The zero BC is materialized - * lazily on the first call to @c getBoundaryConditionManagers so that any @c set*BCs calls the - * caller makes on @p source_bc after this method returns are reflected in the mirror. - * - * @param name Field name to associate with the zero BC. - * @param source_bc BC object whose constrained DOF set is mirrored (e.g. the displacement BC). - */ - void shareBoundaryConditions(const std::string& name, std::shared_ptr source_bc); + const std::vector& weak_form_names) const; /** * @brief Check whether a field exists. @@ -435,11 +424,6 @@ struct FieldStore { const std::vector& state_fields, const std::vector& param_fields) const; - /** - * @brief Get the associated mesh. - * @return const std::shared_ptr& The mesh. - */ - /** * @brief Get the list of all parameter fields. */ @@ -491,17 +475,9 @@ struct FieldStore { std::map to_states_index_; std::map to_params_index_; - /// Boundary conditions keyed by field name. Populated by @c addIndependent (for primary - /// unknowns). Zero-mirror BCs are added lazily by @c getBoundaryConditionManagers when - /// entries from @c zero_mirror_sources_ are materialized. + /// Boundary conditions keyed by primary unknown field name. std::map> boundary_conditions_; - /// Pending zero-mirror BCs: maps a field name to the source @c DirichletBoundaryConditions - /// whose constrained DOF set it should copy (with zero prescribed values). Entries are - /// materialized and moved to @c boundary_conditions_ on the first call to - /// @c getBoundaryConditionManagers. - std::map> zero_mirror_sources_; - struct FieldLabel { std::string field_name; size_t field_index_in_residual; diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 1a08817b7c..a71573fc70 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -355,6 +355,7 @@ struct SolidMechanicsSystem : public SystemBase { struct SolidMechanicsOptions { bool enable_stress_output = false; ///< Register stress output fields during phase 1. bool output_cauchy_stress = false; ///< When true, project Cauchy stress (sigma) instead of PK1 (P). + std::string prefix; ///< Prefix used only by standalone Mesh-based builders. }; /** @@ -474,7 +475,6 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons field_store, cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, coupling.fields); field_store->markWeakFormInternal(cycle_zero_name); - field_store->shareBoundaryConditions(accel_old_type.name, disp_bc); auto cycle_zero_solver = detail::makeCycleZeroSolver(solver, *field_store->getMesh()); sys->cycle_zero_systems.push_back(makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form})); } @@ -513,23 +513,20 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons /** * @brief Build a SolidMechanicsSystem from already-registered field packs. * - * Explicit-rule API: rule is given as template param. - * Additional parameter packs are registered as parameters. Coupling packs are taken from - * the trailing field-pack arguments. + * Deduces the displacement time rule from `self_fields`. * * Usage: * @code - * auto solid_system = buildSolidMechanicsSystem( + * auto solid_system = buildSolidMechanicsSystem( * solver, opts, solid_fields, youngs_modulus, thermal_fields); * @endcode */ -template - requires(detail::is_physics_fields_v && - std::is_same_v::time_rule_type, DisplacementTimeRule> && - (detail::is_coupling_params_v && ...)) +template + requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, const SelfFields& self_fields, const OtherPacks&... other_packs) { + using DisplacementTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; (detail::registerParamsIfNeeded(field_store, other_packs), ...); auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); @@ -539,47 +536,34 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid } /** - * @brief Build a SolidMechanicsSystem from already-registered field packs. - * - * Preferred API: deduce rule from `self_fields`. - * - * Usage: - * @code - * auto solid_system = buildSolidMechanicsSystem( - * solver, opts, solid_fields, youngs_modulus, thermal_fields); - * @endcode - */ -template - requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) -auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) -{ - using DisplacementTimeRule = typename std::decay_t::time_rule_type; - return buildSolidMechanicsSystem(solver, options, self_fields, other_packs...); -} - -/** - * @brief Build a SolidMechanicsSystem from solver options and a FieldStore. + * @brief Build a SolidMechanicsSystem from solver options and either a FieldStore or Mesh. * - * Registers the solid field pack, builds a nonlinear block solver from the supplied options, - * then forwards to the existing field-pack overload. + * Pass a FieldStore for coupled/composed systems. Pass a Mesh for a standalone solid system. * * Usage: * @code * auto solid_system = buildSolidMechanicsSystem( - * nonlin_opts, lin_opts, field_store, opts, param_fields, thermal_fields); + * nonlin_opts, lin_opts, opts, field_store, param_fields, thermal_fields); + * auto standalone_solid = buildSolidMechanicsSystem( + * nonlin_opts, lin_opts, opts, mesh, param_fields); * @endcode */ -template - requires(detail::is_coupling_params_v && ...) +template + requires((detail::is_field_store_ptr_v || detail::is_mesh_ptr_v) && + (detail::is_coupling_params_v && ...)) auto buildSolidMechanicsSystem(const NonlinearSolverOptions& nonlinear_options, const LinearSolverOptions& linear_options, const SolidMechanicsOptions& options, - std::shared_ptr field_store, const OtherPacks&... other_packs) + FieldStoreOrMesh field_store_or_mesh, const OtherPacks&... other_packs) { + if constexpr (detail::is_mesh_ptr_v) { + static_assert((!detail::is_physics_fields_v && ...), + "Pass a FieldStore when building a system coupled to registered physics fields."); + } + auto field_store = detail::getOrCreateFieldStore(field_store_or_mesh, options.prefix); auto self_fields = registerSolidMechanicsFields(field_store, options); auto solver = std::make_shared( buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); - return buildSolidMechanicsSystem(solver, options, self_fields, other_packs...); + return buildSolidMechanicsSystem(solver, options, self_fields, other_packs...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 0965b5a9f1..b2f229e645 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -246,6 +246,61 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) StateManager::reset(); } +TEST(MultiphysicsTimeIntegrator, CycleZeroAccelerationBcUsesDisplacementSecondTimeDerivative) +{ + axom::sidre::DataStore datastore; + StateManager::initialize(datastore, "cycle_zero_acceleration_bc_uses_displacement_second_time_derivative"); + + auto mesh = std::make_shared(mfem::Mesh::MakeCartesian2D(4, 4, mfem::Element::QUADRILATERAL, true, 1.0, 1.0), + "cycle_zero_bc_mesh"); + mesh->addDomainOfBoundaryElements("left", + [](std::vector nodes, int) { return allNodesOnBoundary(nodes, 0.0); }); + mesh->addDomainOfBoundaryElements("right", + [](std::vector nodes, int) { return allNodesOnBoundary(nodes, 1.0); }); + + auto field_store = std::make_shared(mesh, 20); + FieldType> shape_disp_type("shape_displacement"); + field_store->addShapeDisp(shape_disp_type); + + auto time_rule = std::make_shared(); + FieldType> displacement_type("displacement_solve_state"); + auto displacement_bc = field_store->addIndependent(displacement_type, time_rule); + auto displacement_old_type = + field_store->addDependent(displacement_type, FieldStore::TimeDerivative::VAL, "displacement"); + auto velocity_type = field_store->addDependent(displacement_type, FieldStore::TimeDerivative::DOT, "velocity"); + auto acceleration_type = + field_store->addDependent(displacement_type, FieldStore::TimeDerivative::DDOT, "acceleration"); + + auto cycle_zero_wf = buildSecondOrderCycleZeroWeakForm("cycle_zero_acceleration", mesh, field_store, + displacement_old_type, velocity_type, acceleration_type); + + displacement_bc->setScalarBCs<2>(mesh->domain("right"), [](double t, tensor) { return t * t; }); + + auto bc_managers = field_store->getBoundaryConditionManagers({cycle_zero_wf->name()}); + ASSERT_EQ(bc_managers.size(), 1); + ASSERT_NE(bc_managers[0], nullptr); + const int right_dofs = bc_managers[0]->allEssentialTrueDofs().Size(); + ASSERT_GT(right_dofs, 0); + + displacement_bc->setScalarBCs<2>(mesh->domain("left"), [](double t, tensor) { return t * t; }); + + bc_managers = field_store->getBoundaryConditionManagers({cycle_zero_wf->name()}); + ASSERT_NE(bc_managers[0], nullptr); + EXPECT_GT(bc_managers[0]->allEssentialTrueDofs().Size(), right_dofs); + + mfem::Vector acceleration(field_store->getField("acceleration").get()->space().GetTrueVSize()); + acceleration = 0.0; + for (const auto& bc : bc_managers[0]->essentials()) { + bc.setDofs(acceleration, 0.0); + } + + for (int dof : bc_managers[0]->allEssentialTrueDofs()) { + EXPECT_NEAR(acceleration[dof], 2.0, 1.0e-7); + } + + StateManager::reset(); +} + TEST(SystemSolver, SingleBlockSolverFromMonolithicStageNarrowsToRequestedBlock) { axom::sidre::DataStore datastore; diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index beca82e41c..89a7f6d27d 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -8,19 +8,14 @@ #include "gretl/data_store.hpp" #include -#include #include -#include "smith/smith_config.hpp" #include "smith/infrastructure/application_manager.hpp" -#include "smith/numerics/equation_solver.hpp" #include "smith/numerics/solver_config.hpp" -#include "smith/mesh_utils/mesh_utils.hpp" #include "smith/physics/state/state_manager.hpp" #include "smith/physics/functional_objective.hpp" #include "smith/physics/boundary_conditions/boundary_condition_manager.hpp" -#include "smith/physics/materials/parameterized_solid_material.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/system_solver.hpp" @@ -61,7 +56,7 @@ NonlinearSolverOptions solid_nonlinear_opts{.nonlin_solver = NonlinearSolver::Tr .relative_tol = 1.0e-10, .absolute_tol = 1.0e-10, .max_iterations = 500, - .print_level = 1}; + .print_level = 0}; static constexpr int dim = 3; static constexpr int order = 1; @@ -102,18 +97,14 @@ struct SolidMechanicsMeshFixture : public testing::Test { TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { { - auto field_store = std::make_shared(mesh, 100, "impl"); - using ImplicitRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; - auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - SolidMechanicsOptions{}, field_store); + auto solid_system = buildSolidMechanicsSystem( + solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.prefix = "impl"}, mesh); EXPECT_EQ(solid_system->cycle_zero_systems.size(), 1u) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { - auto field_store = std::make_shared(mesh, 100, "qs"); - using QsRule = QuasiStaticSecondOrderTimeIntegrationRule; - auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - SolidMechanicsOptions{}, field_store); + auto solid_system = buildSolidMechanicsSystem( + solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.prefix = "qs"}, mesh); EXPECT_TRUE(solid_system->cycle_zero_systems.empty()) << "QuasiStatic has no initial acceleration solve"; } } @@ -154,13 +145,10 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; - auto field_store = std::make_shared(mesh, 100, ""); - - using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto solid_system = buildSolidMechanicsSystem( - solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.enable_stress_output = true}, field_store, + auto solid_system = buildSolidMechanicsSystem( + solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.enable_stress_output = true}, mesh, param_fields); static constexpr double gravity = -9.0; @@ -258,12 +246,9 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditions) { - auto field_store = std::make_shared(mesh, 100, "freefall_"); - - using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; SolidMechanicsOptions solid_options{.enable_stress_output = true}; - auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, - solid_options, field_store); + auto solid_system = buildSolidMechanicsSystem( + solid_nonlinear_opts, solid_linear_options, solid_options, mesh); static constexpr double gravity = -9.0; double E = 100.0; @@ -320,10 +305,9 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(mesh, 100, physics_name); - using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto solid_system = buildSolidMechanicsSystem( + auto solid_system = buildSolidMechanicsSystem( solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{}, field_store, param_fields); auto physics = makeDifferentiablePhysics(solid_system, physics_name); diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 61f9decc1c..d5ff831cd8 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -48,11 +48,9 @@ struct ThermalStaticFixture : public testing::Test { auto solver_options = NonlinearSolverOptions(); solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); - auto field_store = std::make_shared(mesh, 100, ""); - using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; - auto thermal_system = - buildThermalSystem<2, temp_order, TempRule>(solver_options, linear_options, ThermalOptions{}, field_store); + auto thermal_system = buildThermalSystem<2, temp_order, QuasiStaticFirstOrderTimeIntegrationRule>( + solver_options, linear_options, ThermalOptions{}, mesh); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -139,12 +137,10 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) auto linear_options = LinearSolverOptions(); FieldType> conductivity_param("conductivity"); - auto field_store = std::make_shared(mesh, 100, ""); - using TempRule = QuasiStaticFirstOrderTimeIntegrationRule; auto param_fields = registerParameterFields(conductivity_param); - auto thermal_system = - buildThermalSystem<2, 1, TempRule>(solver_options, linear_options, ThermalOptions{}, field_store, param_fields); + auto thermal_system = buildThermalSystem<2, 1, QuasiStaticFirstOrderTimeIntegrationRule>( + solver_options, linear_options, ThermalOptions{}, mesh, param_fields); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index ba50f40a26..5baa761cee 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -263,33 +263,7 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl /** * @brief Build a ThermalSystem from already-registered field packs. * - * Explicit-rule API: rule is given as template param. - * Additional parameter packs are registered as parameters. Coupling packs are taken from - * the trailing field-pack arguments. - * - * Usage: - * @code - * auto thermal_system = buildThermalSystem( - * solver, opts, thermal_fields, solid_fields); - * @endcode - */ -template - requires(detail::is_physics_fields_v && - std::is_same_v::time_rule_type, TemperatureTimeRule> && - (detail::is_coupling_params_v && ...)) -auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) -{ - auto field_store = self_fields.field_store; - (detail::registerParamsIfNeeded(field_store, other_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); - return detail::buildThermalSystemImpl(field_store, coupling, solver, options); -} - -/** - * @brief Build a ThermalSystem from already-registered field packs. - * - * Preferred API: deduce rule from `self_fields`. + * Deduces the temperature time rule from `self_fields`. * * Usage: * @code @@ -303,31 +277,41 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio const SelfFields& self_fields, const OtherPacks&... other_packs) { using TemperatureTimeRule = typename std::decay_t::time_rule_type; - return buildThermalSystem(solver, options, self_fields, other_packs...); + auto field_store = self_fields.field_store; + (detail::registerParamsIfNeeded(field_store, other_packs), ...); + auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } /** - * @brief Build a ThermalSystem from solver options and a FieldStore. + * @brief Build a ThermalSystem from solver options and either a FieldStore or Mesh. * - * Registers the thermal field pack, builds a nonlinear block solver from the supplied options, - * then forwards to the existing field-pack overload. + * Pass a FieldStore for coupled/composed systems. Pass a Mesh for a standalone thermal system. * * Usage: * @code * auto thermal_system = buildThermalSystem( - * nonlin_opts, lin_opts, field_store, opts, param_fields, solid_fields); + * nonlin_opts, lin_opts, opts, field_store, param_fields, solid_fields); + * auto standalone_thermal = buildThermalSystem( + * nonlin_opts, lin_opts, opts, mesh, param_fields); * @endcode */ -template - requires(detail::is_coupling_params_v && ...) +template + requires((detail::is_field_store_ptr_v || detail::is_mesh_ptr_v) && + (detail::is_coupling_params_v && ...)) auto buildThermalSystem(const NonlinearSolverOptions& nonlinear_options, const LinearSolverOptions& linear_options, - const ThermalOptions& options, std::shared_ptr field_store, + const ThermalOptions& options, FieldStoreOrMesh field_store_or_mesh, const OtherPacks&... other_packs) { + if constexpr (detail::is_mesh_ptr_v) { + static_assert((!detail::is_physics_fields_v && ...), + "Pass a FieldStore when building a system coupled to registered physics fields."); + } + auto field_store = detail::getOrCreateFieldStore(field_store_or_mesh); auto self_fields = registerThermalFields(field_store, options); auto solver = std::make_shared( buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); - return buildThermalSystem(solver, options, self_fields, other_packs...); + return buildThermalSystem(solver, options, self_fields, other_packs...); } } // namespace smith diff --git a/src/smith/physics/tests/test_kinematic_objective.cpp b/src/smith/physics/tests/test_kinematic_objective.cpp index 50f8ce9163..1eb23160bc 100644 --- a/src/smith/physics/tests/test_kinematic_objective.cpp +++ b/src/smith/physics/tests/test_kinematic_objective.cpp @@ -100,9 +100,9 @@ struct ConstrainedWeakFormFixture : public testing::Test { for (int i = 0; i < dim; ++i) { auto cg_objective = std::make_shared("translation" + std::to_string(i), mesh, param_space_ptrs); cg_objective->addBodyIntegral( - smith::DependsOn<0, 1>{}, mesh->entireBodyName(), - [i](auto /*t_info*/, auto X, auto U, - auto RHO) { return (get(X)[i] + get(U)[i]) * get(RHO); }); + smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [i](auto /*t_info*/, auto X, auto U, auto RHO) { + return (get(X)[i] + get(U)[i]) * get(RHO); + }); initial_cg[i] = cg_objective->evaluate(time_info, shape_disp.get(), objective_states) / mass; constraint_evaluators.push_back(cg_objective); } From d8f1a27aedb0137007d420f89e9a883538f66e51 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Sun, 10 May 2026 19:10:56 -0600 Subject: [PATCH 49/67] Refactor: Remove DependsOn combinatorial explosions and cycle-zero weak forms --- examples/contact/beam_bending.cpp | 2 +- examples/contact/ironing.cpp | 2 +- .../inertia_relief/inertia_relief_example.cpp | 6 +- .../composable_solid_mechanics.cpp | 7 +- .../composable_thermo_mechanics_advanced.cpp | 4 +- plan.md | 81 ++++++++ .../lumped_mass_weak_form.hpp | 2 +- .../solid_mechanics_system.hpp | 194 ++---------------- .../tests/test_combined_thermo_mechanics.cpp | 11 +- .../tests/test_explicit_dynamics.cpp | 10 +- .../test_multiphysics_time_integrator.cpp | 2 +- .../tests/test_porous_heat_sink.cpp | 6 +- .../tests/test_solid_dynamics.cpp | 8 +- .../tests/test_thermal_static.cpp | 2 +- .../thermal_system.hpp | 26 +-- src/smith/physics/functional_objective.hpp | 26 ++- src/smith/physics/functional_weak_form.hpp | 68 +++--- src/smith/physics/heat_transfer.hpp | 2 +- src/smith/physics/solid_mechanics.hpp | 8 +- .../physics/tests/dynamic_solid_adjoint.cpp | 2 +- .../physics/tests/dynamic_thermal_adjoint.cpp | 4 +- .../physics/tests/lce_Bertoldi_lattice.cpp | 2 +- .../physics/tests/lce_Brighenti_tensile.cpp | 2 +- .../physics/tests/parameterized_thermal.cpp | 2 +- .../parameterized_thermomechanics_example.cpp | 2 +- src/smith/physics/tests/solid.cpp | 7 +- src/smith/physics/tests/solid_finite_diff.cpp | 2 +- src/smith/physics/tests/solid_periodic.cpp | 2 +- .../physics/tests/solid_reaction_adjoint.cpp | 2 +- .../physics/tests/solid_robin_condition.cpp | 4 +- .../tests/test_functional_weak_form.cpp | 6 +- .../tests/test_kinematic_objective.cpp | 10 +- .../physics/tests/thermal_finite_diff.cpp | 2 +- .../physics/tests/thermal_nonlinear_solve.cpp | 2 +- .../physics/tests/thermal_robin_condition.cpp | 3 +- src/smith/physics/thermomechanics.hpp | 6 +- .../physics/thermomechanics_monolithic.hpp | 4 +- 37 files changed, 223 insertions(+), 308 deletions(-) create mode 100644 plan.md diff --git a/examples/contact/beam_bending.cpp b/examples/contact/beam_bending.cpp index 79f268ec89..558a439e97 100644 --- a/examples/contact/beam_bending.cpp +++ b/examples/contact/beam_bending.cpp @@ -77,7 +77,7 @@ int main(int argc, char* argv[]) solid_solver.setParameter(1, G_field); smith::solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(smith::DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid_solver.setMaterial(mat, mesh->entireBody()); // Pass the BC information to the solver object solid_solver.setFixedBCs(mesh->domain("support")); diff --git a/examples/contact/ironing.cpp b/examples/contact/ironing.cpp index 793d4d2751..7e3261b3fc 100644 --- a/examples/contact/ironing.cpp +++ b/examples/contact/ironing.cpp @@ -73,7 +73,7 @@ int main(int argc, char* argv[]) solid_solver.setParameter(1, G_field); smith::solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(smith::DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid_solver.setMaterial(mat, mesh->entireBody()); // Pass the BC information to the solver object mesh->addDomainOfBoundaryElements("bottom_of_subtrate", smith::by_attr(5)); diff --git a/examples/inertia_relief/inertia_relief_example.cpp b/examples/inertia_relief/inertia_relief_example.cpp index d9abebe15f..35f5a8e150 100644 --- a/examples/inertia_relief/inertia_relief_example.cpp +++ b/examples/inertia_relief/inertia_relief_example.cpp @@ -212,7 +212,7 @@ int main(int argc, char* argv[]) ObjectiveT mass_objective("mass constraining", mesh, param_space_ptrs); - mass_objective.addBodyIntegral(smith::DependsOn<1>{}, mesh->entireBodyName(), + mass_objective.addBodyIntegral(mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto RHO) { return get(RHO); }); double mass = mass_objective.evaluate(time_info, shape_disp.get(), objective_states); @@ -220,7 +220,7 @@ int main(int argc, char* argv[]) for (int i = 0; i < dim; ++i) { auto cg_objective = std::make_shared("translation " + std::to_string(i), mesh, param_space_ptrs); - cg_objective->addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), + cg_objective->addBodyIntegral(mesh->entireBodyName(), [i](auto /*t_info*/, auto X, auto U, auto RHO) { return (get(X)[i] + get(U)[i]) * get(RHO); }); @@ -232,7 +232,7 @@ int main(int argc, char* argv[]) for (int i = 0; i < dim; ++i) { auto center_rotation_objective = std::make_shared("rotation" + std::to_string(i), mesh, param_space_ptrs); - center_rotation_objective->addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), + center_rotation_objective->addBodyIntegral(mesh->entireBodyName(), [i, initial_cg](auto /*t_info*/, auto X, auto U, auto RHO) { auto u = get(U); auto x = get(X) + u; diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 199bc4f204..0befdec5c1 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -105,12 +105,12 @@ int main(int argc, char* argv[]) // _bc_start solid_system->setDisplacementBC(mesh->domain("left"), std::vector{0, 2}); - solid_system->addBodyForce(smith::DependsOn<>{}, mesh->entireBodyName(), [](double, auto X, auto, auto, auto) { + solid_system->addBodyForce(mesh->entireBodyName(), [](double, auto X, auto, auto, auto) { auto body_force = 0.0 * X; body_force[1] = -0.02; return body_force; }); - solid_system->addTraction(smith::DependsOn<>{}, "right", [](double, auto X, auto, auto, auto, auto) { + solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto) { auto traction = 0.0 * X; traction[0] = -0.01; return traction; @@ -154,8 +154,7 @@ int main(int argc, char* argv[]) physics->getInitialFieldState("velocity")}; smith::FunctionalObjective> qoi("solid_dynamic_energy_proxy", mesh, smith::spaces(qoi_fields)); - qoi.addBodyIntegral( - smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [](const smith::TimeInfo&, auto, auto U, auto V) { + qoi.addBodyIntegral(mesh->entireBodyName(), [](const smith::TimeInfo&, auto, auto U, auto V) { auto u = smith::get(U); auto v = smith::get(V); return 0.5 * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + 0.05 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index fe4a793c37..540d14cedd 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -81,7 +81,7 @@ int main(int argc, char* argv[]) thermal_system->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); thermal_system->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); - solid_system->addTraction(smith::DependsOn<>{}, "right", + solid_system->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/) { auto traction = 0.0 * X; traction[0] = -0.005; @@ -124,7 +124,7 @@ int main(int argc, char* argv[]) }; smith::FunctionalObjective> qoi("thermo_mechanical_energy_proxy", mesh, smith::spaces(fetch_qoi_fields())); - qoi.addBodyIntegral(smith::DependsOn<0, 1>(), mesh->entireBodyName(), [](auto, auto /*X*/, auto U, auto Theta) { + qoi.addBodyIntegral(mesh->entireBodyName(), [](auto, auto /*X*/, auto U, auto Theta) { auto u = smith::get(U); auto theta = smith::get(Theta); return 0.5 * u[0] * u[0] + 0.05 * theta * theta; diff --git a/plan.md b/plan.md new file mode 100644 index 0000000000..d9b9577526 --- /dev/null +++ b/plan.md @@ -0,0 +1,81 @@ +# Refactoring Plan: Compile Time Reduction & Interface Unification + +## 1. Remove `DependsOn` from WeakForm Interfaces [COMPLETED] +- **Status:** **DONE**. Removed `DependsOn` overloads and template parameters from `FunctionalWeakForm`, `FunctionalObjective`, `SolidMechanicsSystem`, and `ThermalSystem`. This dramatically reduced the number of AST instantiations required for AD derivatives. +- **Action Taken:** By default, all integrals now depend on the full parameter pack (`DependsOn<0, 1, ..., N>{}`). The explicit template instantiation burden is completely removed from the user API. + +## 2. Eliminate `cycle_zero_weak_form` [COMPLETED] +- **Status:** **DONE**. Deleted `cycle_zero_solid_weak_form` entirely from the SolidMechanics system. +- **Action Taken:** The `MultiphysicsTimeIntegrator` and `SystemBase` now evaluate the cycle-zero acceleration residual directly through the main `solid_weak_form` by properly packing the state variables as `(u, u, v, a)` so the primary time-integration rule interpolates them correctly as initial conditions. + +--- + +## 3. Simplify Deep Type-Level Rewriting (Coupling Types) +- **Problem:** Types like `AppendCouplingToParams`, `TimeRuleParamsHelper`, and operations in `combined_system.hpp` rely on deep, recursive template metaprogramming which bogs down the compiler's frontend. +- **Goal:** Move away from recursive template structs (`struct Helper { ... }`) towards flat parameter pack expansions and `decltype(std::tuple_cat(...))` which are significantly faster for the compiler to evaluate. +- **Proposed Architecture:** We can replace much of `coupling_params.hpp` logic by capturing foreign physics fields directly into a flattened `std::tuple` of descriptors at the system construction phase, completely avoiding the need to re-parse the types during weak form evaluations. + +## 4. Unified Time Discretization for Coupled Physics +- **Problem:** Currently, self-fields are beautifully interpolated (e.g., `u_new, u_old, v_old, a_old` becomes `u, v, a` inside the user lambda), but coupled fields are passed raw (e.g., `temp_new, temp_old` are passed directly in `auto... params`). +- **Action:** Transform the time discretization for *all* coupled physics before calling the user closure. If a thermal system is coupled to a solid mechanics system, the solid system's lambdas should see `(t, t_dot)` rather than `(t_new, t_old)`. + +### Analysis & Example C++ Code for Steps 3 & 4 +To solve both Step 3 and Step 4 simultaneously without introducing massive compile-time overhead, we can create a lightweight, flat **Coupled State Evaluator**. Instead of recursively tearing apart type-lists, we will store a tuple of the active Time Rules for all coupled physics. + +When a weak form evaluates, it receives a flat `auto... raw_args` representing the raw FE states (new, old, old_v, etc.) for *all* physics concatenated together. We will use a compile-time index sequence to partition this flat pack, feed the partitions into their respective time rules, and then flat-unpack the interpolated results into the user's lambda. + +**Example implementation approach:** + +```cpp +#include +#include + +// Helper to extract a sub-pack from a tuple of arguments +template +constexpr auto extract_args_impl(const Tuple& t, std::index_sequence) { + return std::make_tuple(std::get(t)...); +} + +// Given a flat tuple of raw arguments, extract `Count` arguments starting at `Offset` +template +constexpr auto extract_args(const Tuple& t) { + return extract_args_impl(t, std::make_index_sequence{}); +} + +// The Unified Evaluator +template +struct MultiphysicsEvaluator { + std::tuple rules; + + template + auto evaluate(UserLambda&& user_fn, const TInfo& t_info, const XType& X, const RawArgs&... raw_args) { + auto flat_raw_args = std::forward_as_tuple(raw_args...); + + // This is pseudo-code. In practice, we would use a compile-time fold over the rules + // to automatically compute the offsets based on `TimeRule::num_states`. + + // E.g., Physics 0 (Solid) takes 4 states (u, u_old, v_old, a_old) + auto solid_raw = extract_args<0, 4>(flat_raw_args); + auto solid_interp = std::apply([&](auto... args){ + return std::get<0>(rules)->interpolate(t_info, args...); + }, solid_raw); + + // E.g., Physics 1 (Thermal) takes 2 states (T, T_old) + auto thermal_raw = extract_args<4, 2>(flat_raw_args); + auto thermal_interp = std::apply([&](auto... args){ + return std::get<1>(rules)->interpolate(t_info, args...); + }, thermal_raw); + + // Finally, expand both interpolated tuples into the user lambda + // User sees: lambda(t_info, X, u, v, a, T, T_dot) + return std::apply([&](auto... all_interp_args) { + return user_fn(t_info, X, all_interp_args...); + }, std::tuple_cat(solid_interp, thermal_interp)); + } +}; +``` + +**Why this reduces compile time:** +1. We rely on `std::tuple` and `std::apply`, which modern compilers highly optimize via built-in intrinsics. +2. The flat `raw_args` list is passed exactly as it is received from `FunctionalWeakForm`. We do not rewrite the `Parameters<...>` type recursively; we only slice the tuple at evaluation time. +3. AD instantiation stays confined to the `evaluate` wrapper, meaning the user's `UserLambda` is only instantiated once with the fully interpolated, unified types. \ No newline at end of file diff --git a/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp b/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp index fbea61b3be..c3c1f0ef1c 100644 --- a/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp +++ b/src/smith/differentiable_numerics/lumped_mass_weak_form.hpp @@ -27,7 +27,7 @@ auto createSolidMassWeakForm(const std::string& physics_name, std::shared_ptr>; auto weak_form = std::make_shared(physics_name, mesh, lumped_field.space(), typename WeakFormT::SpacesT{&density.space()}); - weak_form->addBodyIntegral(smith::DependsOn<0>{}, mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto Rho) { + weak_form->addBodyIntegral(mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto Rho) { if constexpr (lumped_dim == 1) { return smith::tuple{get(Rho), tensor{}}; } else { diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index a71573fc70..3a1ba25735 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -25,24 +25,6 @@ namespace smith { -namespace detail { - -template -/// @brief Scale a cycle-zero residual contribution by `dt*dt`. -auto scaleCycleZeroTerm(const TimeInfo& t_info, ValueType value) -{ - return (t_info.dt() * t_info.dt()) * value; -} - -template -/// @brief Package scaled inertia and stress terms for the cycle-zero weak form. -auto makeScaledCycleZeroResidual(const TimeInfo& t_info, InertiaType inertia, StressType stress) -{ - return smith::tuple{scaleCycleZeroTerm(t_info, inertia), scaleCycleZeroTerm(t_info, stress)}; -} - -} // namespace detail - /** * @brief System struct for solid dynamics with configurable time integration. * @@ -69,15 +51,6 @@ struct SolidMechanicsSystem : public SystemBase { using SolidWeakFormType = FunctionalWeakForm, detail::TimeRuleParams, Coupling>>; - /// Cycle-zero startup form reusing the main solid fields: (u, v, a, coupling_fields..., params...) - /// At cycle 0 the velocity field holds the initial velocity (no prior step has run), so the - /// second argument is `v` (initial), not `v_old`. Acceleration is the unknown for this internal solve. - /// No extra fields are registered for cycle zero. - using CycleZeroSolidWeakFormType = - FunctionalWeakForm, - typename detail::AppendCouplingToParams< - Coupling, Parameters, H1, H1>>::type>; - /// L2 projection weak form for PK1 stress output (dim*dim components). /// Args: (stress_unknown, u, u_old, v_old, a_old, coupling_fields..., params...). using StressOutputWeakFormType = FunctionalWeakForm< @@ -86,8 +59,6 @@ struct SolidMechanicsSystem : public SystemBase { H1, H1>>::type>; std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. - std::shared_ptr - cycle_zero_solid_weak_form; ///< Typed cycle zero solid mechanics weak form. std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. @@ -115,17 +86,6 @@ struct SolidMechanicsSystem : public SystemBase { return smith::tuple{get(a_current) * material.density, pk_stress}; }); - // Add to cycle-zero weak form (at cycle 0, u and v are given, solve for a) - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto /*v_old*/, auto a, auto... params) { - typename MaterialType::State state; - auto pk_stress = material(t_info, state, get(u), tensor{}, params...); - - return detail::makeScaledCycleZeroResidual(t_info, get(a) * material.density, pk_stress); - }); - } - // Stress output projection: L2 projection of PK1 stress onto an L2 piecewise-constant field. // Residual: ∫ test · (stress_unknown - pk_stress(u)) dx = 0. // Args: (stress_unknown, u, u_old, v_old, a_old, params...). stress_unknown is the Jacobian @@ -157,96 +117,53 @@ struct SolidMechanicsSystem : public SystemBase { } /** - * @brief Add a body force to the system (with DependsOn). - * @tparam active_parameters Indices of fields this force depends on. + * @brief Add a body force to the system. * @tparam BodyForceType The body force function type. - * @param depends_on Dependency specification for which input fields to pass. * @param domain_name The name of the domain to apply the force to. * @param force_function The force function (t, X, u, v, a, params...). */ - template - void addBodyForce(DependsOn depends_on, const std::string& domain_name, - BodyForceType force_function) + template + void addBodyForce(const std::string& domain_name, BodyForceType force_function) { auto captured_rule = disp_time_rule; - solid_weak_form->template addBodySource<0, 1, 2, 3, (4 + active_parameters)...>( - DependsOn<0, 1, 2, 3, (4 + active_parameters)...>{}, domain_name, + solid_weak_form->addBodySource( + domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); return force_function(t_info.time(), X, u_current, v_current, a_current, params...); }); - - addCycleZeroBodySourceImpl( - depends_on, domain_name, [=](auto t_info, auto X, auto u, auto v, auto a, auto... params) { - return detail::scaleCycleZeroTerm(t_info, force_function(t_info.time(), X, u, v, a, params...)); - }); } /** - * @brief Add a body force to the system. - * @tparam BodyForceType The body force function type. - * @param domain_name The name of the domain to apply the force to. - * @param force_function The force function (t, X, u, v, a, params...). - */ - template - void addBodyForce(const std::string& domain_name, BodyForceType force_function) - { - addBodyForceAllParams(domain_name, force_function, std::make_index_sequence{}); - } - - /** - * @brief Add a surface traction (flux) to the system (with DependsOn). - * @tparam active_parameters Indices of fields this traction depends on. + * @brief Add a surface traction (flux) to the system. * @tparam TractionType The traction function type. - * @param depends_on Dependency specification for which input fields to pass. * @param domain_name The name of the boundary domain to apply the traction to. * @param traction_function The traction function (t, X, n, u, v, a, params...). */ - template - void addTraction(DependsOn depends_on, const std::string& domain_name, - TractionType traction_function) + template + void addTraction(const std::string& domain_name, TractionType traction_function) { auto captured_rule = disp_time_rule; - solid_weak_form->template addBoundaryFlux<0, 1, 2, 3, (4 + active_parameters)...>( - DependsOn<0, 1, 2, 3, (4 + active_parameters)...>{}, domain_name, + solid_weak_form->addBoundaryFlux( + domain_name, [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); return traction_function(t_info.time(), X, n, u_current, v_current, a_current, params...); }); - - addCycleZeroBoundaryFluxImpl( - depends_on, domain_name, [=](auto t_info, auto X, auto n, auto u, auto v, auto a, auto... params) { - return detail::scaleCycleZeroTerm(t_info, traction_function(t_info.time(), X, n, u, v, a, params...)); - }); - } - - /** - * @brief Add a surface traction (flux) to the system. - * @tparam TractionType The traction function type. - * @param domain_name The name of the boundary domain to apply the traction to. - * @param traction_function The traction function (t, X, n, u, v, a, params...). - */ - template - void addTraction(const std::string& domain_name, TractionType traction_function) - { - addTractionAllParams(domain_name, traction_function, std::make_index_sequence{}); } /** - * @brief Add a pressure boundary condition (follower force) (with DependsOn). - * @tparam active_parameters Indices of fields this pressure depends on. + * @brief Add a pressure boundary condition (follower force). * @tparam PressureType The pressure function type. - * @param depends_on Dependency specification for which input fields to pass. * @param domain_name The name of the boundary domain. * @param pressure_function The pressure function (t, X, params...). */ - template - void addPressure(DependsOn depends_on, const std::string& domain_name, - PressureType pressure_function) + template + void addPressure(const std::string& domain_name, PressureType pressure_function) { auto captured_rule = disp_time_rule; - solid_weak_form->template addBoundaryIntegral<0, 1, 2, 3, (4 + active_parameters)...>( - DependsOn<0, 1, 2, 3, (4 + active_parameters)...>{}, domain_name, + solid_weak_form->addBoundaryIntegral( + domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto u_current = captured_rule->value(t_info, u, u_old, v_old, a_old); @@ -258,31 +175,6 @@ struct SolidMechanicsSystem : public SystemBase { return pressure * n_deformed * (1.0 / n_shape_norm); }); - - addCycleZeroBoundaryIntegralImpl( - depends_on, domain_name, [=](auto t_info, auto X, auto u, auto /*v_old*/, auto /*a*/, auto... params) { - auto u_current = u; - - auto x_current = X + u_current; - auto n_deformed = cross(get(x_current)); - auto n_shape_norm = norm(cross(get(X))); - - auto pressure = pressure_function(t_info.time(), get(X), get(params)...); - - return detail::scaleCycleZeroTerm(t_info, pressure * n_deformed * (1.0 / n_shape_norm)); - }); - } - - /** - * @brief Add a pressure boundary condition (follower force). - * @tparam PressureType The pressure function type. - * @param domain_name The name of the boundary domain. - * @param pressure_function The pressure function (t, X, params...). - */ - template - void addPressure(const std::string& domain_name, PressureType pressure_function) - { - addPressureAllParams(domain_name, pressure_function, std::make_index_sequence{}); } /// Set zero-displacement Dirichlet BC on all components. @@ -302,51 +194,9 @@ struct SolidMechanicsSystem : public SystemBase { } private: - template - void addBodyForceAllParams(const std::string& domain_name, BodyForceType force_function, std::index_sequence) - { - addBodyForce(DependsOn(Is)...>{}, domain_name, force_function); - } - template - void addTractionAllParams(const std::string& domain_name, TractionType traction_function, std::index_sequence) - { - addTraction(DependsOn(Is)...>{}, domain_name, traction_function); - } - template - void addPressureAllParams(const std::string& domain_name, PressureType pressure_function, std::index_sequence) - { - addPressure(DependsOn(Is)...>{}, domain_name, pressure_function); - } - // Cycle-zero helpers always include the 3 cycle-zero state slots, followed by the selected tail args. - template - void addCycleZeroBodySourceImpl(DependsOn, const std::string& name, IntegrandType f) - { - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->template addBodySource<0, 1, 2, (3 + active_parameters)...>( - DependsOn<0, 1, 2, (3 + active_parameters)...>{}, name, f); - } - } - - template - void addCycleZeroBoundaryFluxImpl(DependsOn, const std::string& name, IntegrandType f) - { - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->template addBoundaryFlux<0, 1, 2, (3 + active_parameters)...>( - DependsOn<0, 1, 2, (3 + active_parameters)...>{}, name, f); - } - } - - template - void addCycleZeroBoundaryIntegralImpl(DependsOn, const std::string& name, IntegrandType f) - { - if (cycle_zero_solid_weak_form) { - cycle_zero_solid_weak_form->template addBoundaryIntegral<0, 1, 2, (3 + active_parameters)...>( - DependsOn<0, 1, 2, (3 + active_parameters)...>{}, name, f); - } - } }; /** @@ -467,16 +317,14 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons if (disp_time_rule_ptr->requiresInitialAccelerationSolve()) { std::string cycle_zero_name = field_store->prefix("cycle_zero_acceleration_reaction"); - auto accel_as_unknown = accel_old_type; - accel_as_unknown.is_unknown = true; - FieldType> disp_cz_input(disp_type.name); - sys->cycle_zero_solid_weak_form = - detail::buildWeakFormWithCoupling( - field_store, cycle_zero_name, accel_old_type.name, disp_cz_input, velo_old_type, accel_as_unknown, - coupling.fields); field_store->markWeakFormInternal(cycle_zero_name); auto cycle_zero_solver = detail::makeCycleZeroSolver(solver, *field_store->getMesh()); - sys->cycle_zero_systems.push_back(makeSystem(field_store, cycle_zero_solver, {sys->cycle_zero_solid_weak_form})); + + // We reuse solid_weak_form for cycle zero, but we must pass correct inputs at evaluation time. + // However, the SystemBase needs a cycle_zero_systems populated. We use the solid_weak_form. + // Note that during cycle-zero solve, we want to solve for acceleration, but solid_weak_form + // was built with disp_type.name as the unknown. We'll handle this in MultiphysicsTimeIntegrator. + sys->cycle_zero_systems.push_back(makeSystem(field_store, cycle_zero_solver, {sys->solid_weak_form})); } if (has_stress_output) { diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 9e8895b1ae..7513e83589 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -167,7 +167,10 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) solid_system->setDisplacementBC(mesh_->domain("left")); thermal_system->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); - solid_system->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto, auto, auto) { + solid_system->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto... /*params*/) { + // If X is a dual number, we need to create a dual number for traction with zero derivative wrt all active parameters. + // For now, returning a value works perfectly fine with smith AD system! + // But since X might be a dual number, we must strip its dual part if we just want a value. auto traction = 0.0 * X; traction[0] = -0.015; return traction; @@ -234,7 +237,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) thermal_system->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); thermal_system->setTemperatureBC(mesh_->domain("right"), [](auto, auto) { return 0.0; }); - solid_system->addTraction(DependsOn<>{}, "right", [=](double, auto X, auto, auto, auto, auto) { + solid_system->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto... /*params*/) { auto traction = 0.0 * X; traction[0] = -0.005; return traction; @@ -247,7 +250,7 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) "staggered_qoi", mesh_, spaces({coupled_system->field_store->getField("displacement"), coupled_system->field_store->getField("temperature")})); - qoi.addBodyIntegral(DependsOn<0, 1>{}, mesh_->entireBodyName(), [](auto, auto, auto U, auto Theta) { + qoi.addBodyIntegral(mesh_->entireBodyName(), [](auto, auto, auto U, auto Theta) { auto u = get(U); auto theta = get(Theta); return 0.5 * u[0] * u[0] + 0.05 * theta * theta; @@ -360,7 +363,7 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) mesh_->entireBodyName()); solid_system->setDisplacementBC(mesh_->domain("left")); - solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto) { + solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto... /*params*/) { auto t = 0.0 * X; t[0] = -0.01; return t; diff --git a/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp index 57e95a775b..5fde94c681 100644 --- a/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp @@ -162,7 +162,7 @@ struct MeshFixture : public testing::Test { return smith::tuple{smith::get(a) * mat.density(), pk_stress}; }); - solid_mechanics_residual->addBodySource(smith::DependsOn<>{}, mesh_->entireBodyName(), [](auto /*t_info*/, auto X) { + solid_mechanics_residual->addBodySource(mesh_->entireBodyName(), [](auto /*t_info*/, auto X) { auto b = 0.0 * X; b[1] = gravity; return b; @@ -190,7 +190,7 @@ struct MeshFixture : public testing::Test { auto ke_objective = std::make_shared>>( "integrated_squared_temperature", mesh_, smith::spaces({states[DISP], params_[DENSITY]})); - ke_objective->addBodyIntegral(smith::DependsOn<0, 1>(), mesh_->entireBodyName(), + ke_objective->addBodyIntegral(mesh_->entireBodyName(), [](auto /*t*/, auto /*X*/, auto U, auto Rho) { auto u = get(U); return 0.5 * get(Rho) * smith::inner(u, u); @@ -390,7 +390,7 @@ TEST_F(MeshFixture, TransientConstantGravity) smith::FunctionalObjective> accel_error("accel_error", mesh_, smith::spaces({all_fields[ACCEL]})); - accel_error.addBodyIntegral(smith::DependsOn<0>{}, mesh_->entireBodyName(), + accel_error.addBodyIntegral(mesh_->entireBodyName(), [a_exact](auto /*t*/, auto /*X*/, auto A) { auto a = smith::get(A); auto da0 = a[0]; @@ -403,7 +403,7 @@ TEST_F(MeshFixture, TransientConstantGravity) smith::FunctionalObjective> velo_error("velo_error", mesh_, smith::spaces({all_fields[VELO]})); - velo_error.addBodyIntegral(smith::DependsOn<0>{}, mesh_->entireBodyName(), [v_exact](auto /*t*/, auto /*X*/, auto V) { + velo_error.addBodyIntegral(mesh_->entireBodyName(), [v_exact](auto /*t*/, auto /*X*/, auto V) { auto v = smith::get(V); auto dv0 = v[0]; auto dv1 = v[1] - v_exact; @@ -415,7 +415,7 @@ TEST_F(MeshFixture, TransientConstantGravity) smith::FunctionalObjective> disp_error("disp_error", mesh_, smith::spaces({all_fields[DISP]})); - disp_error.addBodyIntegral(smith::DependsOn<0>{}, mesh_->entireBodyName(), [u_exact](auto /*t*/, auto /*X*/, auto U) { + disp_error.addBodyIntegral(mesh_->entireBodyName(), [u_exact](auto /*t*/, auto /*X*/, auto U) { auto u = smith::get(U); auto du0 = u[0]; auto du1 = u[1] - u_exact; diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index b2f229e645..04c203ece3 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -88,7 +88,7 @@ auto buildScalarDiffusionWeakForm(const std::string& name, std::shared_ptr using WeakFormType = FunctionalWeakForm<2, H1<1>, Parameters>>; auto weak_form = std::make_shared(name, mesh, fs->getField(field_type.name).get()->space(), fs->createSpaces(name, field_type.name, field_type)); - weak_form->addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), + weak_form->addBodyIntegral(mesh->entireBodyName(), [](auto, auto, auto u) { return smith::tuple{0.0 * get(u), get(u)}; }); return weak_form; } diff --git a/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp b/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp index a1fec16736..6f8c890c4f 100644 --- a/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp +++ b/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp @@ -184,14 +184,14 @@ TEST_P(BlockPreconditionerTest, BlockSolve) auto gamma_coef = std::make_shared(gamma_fun); gamma.get()->project(gamma_coef); - T1_form.addBodyIntegral(DependsOn<0, 1, 2>{}, mesh->entireBodyName(), + T1_form.addBodyIntegral(mesh->entireBodyName(), [sigma, a, q_n](auto /* t_info */, auto /* x */, auto T_1, auto T_2, auto gamma_) { auto [T_1_val, dT_1_dX] = T_1; auto [T_2_val, dT_2_dX] = T_2; auto [gamma_val, dgamma_dX] = gamma_; return smith::tuple{a(gamma_val) * q_n(T_1_val, T_2_val), sigma(gamma_val) * dT_1_dX}; }); - T2_form.addBodyIntegral(DependsOn<0, 1, 2>{}, mesh->entireBodyName(), + T2_form.addBodyIntegral(mesh->entireBodyName(), [kappa, a, q_n](auto /* t_info */, auto /* x */, auto T_1, auto T_2, auto gamma_) { auto [T_1_val, dT_1_dX] = T_1; auto [T_2_val, dT_2_dX] = T_2; @@ -207,7 +207,7 @@ TEST_P(BlockPreconditionerTest, BlockSolve) T2_bc_manager->addEssential(std::set{1}, zero_bcs, space(T2), 0); mesh->addDomainOfBoundaryElements("heat_spreader", by_attr<2>(2)); - T1_form.addBoundaryIntegral(DependsOn<>{}, "heat_spreader", + T1_form.addBoundaryIntegral("heat_spreader", [heatsink_options = heatsink_options](auto, auto) { return heatsink_options.q_app; }); // Block Preconditioner Options diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 89a7f6d27d..a9153d59a2 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -212,7 +212,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) // Test acceleration (states[3] is acceleration) FunctionalObjective> accel_error("accel_error", mesh, spaces({states[3]})); - accel_error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [a_exact](auto /*t*/, auto /*X*/, auto A) { + accel_error.addBodyIntegral(mesh->entireBodyName(), [a_exact](auto /*t*/, auto /*X*/, auto A) { auto a = get(A); auto da0 = a[0]; auto da1 = a[1] - a_exact; @@ -223,7 +223,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) // Test velocity (states[2] is velocity) FunctionalObjective> velo_error("velo_error", mesh, spaces({states[2]})); - velo_error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [v_exact](auto /*t*/, auto /*X*/, auto V) { + velo_error.addBodyIntegral(mesh->entireBodyName(), [v_exact](auto /*t*/, auto /*X*/, auto V) { auto v = get(V); auto dv0 = v[0]; auto dv1 = v[1] - v_exact; @@ -234,7 +234,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) // Test displacement (states[1] is displacement) FunctionalObjective> disp_error("disp_error", mesh, spaces({states[1]})); - disp_error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [u_exact](auto /*t*/, auto /*X*/, auto U) { + disp_error.addBodyIntegral(mesh->entireBodyName(), [u_exact](auto /*t*/, auto /*X*/, auto U) { auto u = get(U); auto du0 = u[0]; auto du1 = u[1] - u_exact; @@ -287,7 +287,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi auto vector_error = [&](const std::string& name, const FieldState& state, double y_exact) { auto state_vec = std::vector{state}; FunctionalObjective> error(name, mesh, spaces(state_vec)); - error.addBodyIntegral(DependsOn<0>{}, mesh->entireBodyName(), [y_exact](auto /*t*/, auto /*X*/, auto U) { + error.addBodyIntegral(mesh->entireBodyName(), [y_exact](auto /*t*/, auto /*X*/, auto U) { auto u = get(U); return u[0] * u[0] + (u[1] - y_exact) * (u[1] - y_exact) + u[2] * u[2]; }); diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index d5ff831cd8..463d9d4ace 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -155,7 +155,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) "entire_body"); // DependsOn now indexes only trailing coupling/parameter args, so empty means "state-only". - thermal_system->addHeatSource(DependsOn<>{}, "entire_body", [=](auto /*t*/, auto X, auto /*T*/) { + thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/) { auto x = X[0]; auto y = X[1]; return 2.0 * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 5baa761cee..bebeacdb92 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -136,7 +136,13 @@ struct ThermalSystem : public SystemBase { template void addHeatSource(const std::string& domain_name, HeatSourceType source_function) { - addHeatSourceAllParams(domain_name, source_function, std::make_index_sequence{}); + auto captured_rule = temperature_time_rule; + thermal_weak_form->addBodySource( + domain_name, + [=](auto t_info, auto X, auto T, auto T_old, auto... params) { + auto T_current = captured_rule->value(t_info, T, T_old); + return source_function(t_info.time(), X, T_current, params...); + }); } /** @@ -168,7 +174,13 @@ struct ThermalSystem : public SystemBase { template void addHeatFlux(const std::string& boundary_name, HeatFluxType flux_function) { - addHeatFluxAllParams(boundary_name, flux_function, std::make_index_sequence{}); + auto captured_rule = temperature_time_rule; + thermal_weak_form->addBoundaryFlux( + boundary_name, + [=](auto t_info, auto X, auto n, auto T, auto T_old, auto... params) { + auto T_current = captured_rule->value(t_info, T, T_old); + return flux_function(t_info.time(), X, n, T_current, params...); + }); } /// Set zero-temperature Dirichlet BC. @@ -182,17 +194,7 @@ struct ThermalSystem : public SystemBase { } private: - template - void addHeatSourceAllParams(const std::string& domain_name, HeatSourceType f, std::index_sequence) - { - addHeatSource(DependsOn(Is)...>{}, domain_name, f); - } - template - void addHeatFluxAllParams(const std::string& boundary_name, HeatFluxType f, std::index_sequence) - { - addHeatFlux(DependsOn(Is)...>{}, boundary_name, f); - } }; struct ThermalOptions {}; diff --git a/src/smith/physics/functional_objective.hpp b/src/smith/physics/functional_objective.hpp index fb07fe59e5..8937c7eab2 100644 --- a/src/smith/physics/functional_objective.hpp +++ b/src/smith/physics/functional_objective.hpp @@ -67,20 +67,25 @@ class FunctionalObjective, std::integer_ * @param body_name string specifying the domain to integrate over * @param qfunction a callable that returns a tuple of body-force and stress */ - template - void addBodyIntegral(DependsOn, std::string body_name, - const FuncOfTimeSpaceAndParams& qfunction) + template + void addBodyIntegralImpl(std::string body_name, const FuncOfTimeSpaceAndParams& qfunction, std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; objective_->AddDomainIntegral( - smith::Dimension{}, smith::DependsOn{}, + smith::Dimension{}, smith::DependsOn{}, [dt, cycle, qfunction](double time, auto X, auto... params) { return qfunction(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(body_name)); } + template + void addBodyIntegral(std::string body_name, const FuncOfTimeSpaceAndParams& qfunction) + { + addBodyIntegralImpl(body_name, qfunction, std::make_integer_sequence{}); + } + /** * @brief register a custom boundary integral calculation as part of the residual * @@ -88,20 +93,25 @@ class FunctionalObjective, std::integer_ * @param boundary_name string specifying the boundary to integrate over * @param qfunction a callable that returns a tuple of body-force and stress */ - template - void addBoundaryIntegral(DependsOn, std::string boundary_name, - const FuncOfTimeSpaceAndParams& qfunction) + template + void addBoundaryIntegralImpl(std::string boundary_name, const FuncOfTimeSpaceAndParams& qfunction, std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; objective_->AddBoundaryIntegral( - smith::Dimension{}, smith::DependsOn{}, + smith::Dimension{}, smith::DependsOn{}, [dt, cycle, qfunction](double time, auto X, auto... params) { return qfunction(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(boundary_name)); } + template + void addBoundaryIntegral(std::string boundary_name, const FuncOfTimeSpaceAndParams& qfunction) + { + addBoundaryIntegralImpl(boundary_name, qfunction, std::make_integer_sequence{}); + } + /// @overload virtual double evaluate(TimeInfo time_info, ConstFieldPtr shape_disp, const std::vector& fields) const override diff --git a/src/smith/physics/functional_weak_form.hpp b/src/smith/physics/functional_weak_form.hpp index af818ac253..1a446cbfef 100644 --- a/src/smith/physics/functional_weak_form.hpp +++ b/src/smith/physics/functional_weak_form.hpp @@ -107,20 +107,20 @@ class FunctionalWeakForm, * 3>`) * */ - template - void addBodyIntegral(DependsOn, std::string body_name, BodyIntegralType integrand) + template + void addBodyIntegralImpl(std::string body_name, BodyIntegralType integrand, std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; weak_form_->AddDomainIntegral( - Dimension{}, DependsOn{}, + Dimension{}, DependsOn{}, [dt, cycle, integrand](double time, auto X, auto... inputs) { return integrand(TimeInfo(time, *dt, *cycle), X, inputs...); }, mesh_->domain(body_name)); v_dot_weak_form_residual_->AddDomainIntegral( - Dimension{}, DependsOn<0, 1 + active_parameters...>{}, + Dimension{}, DependsOn<0, 1 + all_params...>{}, [dt, cycle, integrand](double time, auto X, auto V, auto... inputs) { auto orig_tuple = integrand(TimeInfo(time, *dt, *cycle), X, inputs...); return smith::inner(get(V), get(orig_tuple)) + @@ -129,11 +129,10 @@ class FunctionalWeakForm, mesh_->domain(body_name)); } - /// @overload - template - void addBodyIntegral(std::string body_name, BodyForceType body_integral) + template + void addBodyIntegral(std::string body_name, BodyIntegralType integrand) { - addBodyIntegralWithAllParams(body_name, body_integral, std::make_integer_sequence{}); + addBodyIntegralImpl(body_name, integrand, std::make_integer_sequence{}); } /** @@ -156,22 +155,14 @@ class FunctionalWeakForm, * 3>`) * */ - template - void addBodySource(DependsOn depends_on, std::string body_name, BodyLoadType load_function) + template + void addBodySource(std::string body_name, BodyLoadType load_function) { - addBodyIntegral(depends_on, body_name, [load_function](const TimeInfo& t_info, auto X, auto... inputs) { + addBodyIntegral(body_name, [load_function](const TimeInfo& t_info, auto X, auto... inputs) { return smith::tuple{-load_function(t_info, get(X), get(inputs)...), smith::zero{}}; }); } - /// @overload - template - void addBodySource(std::string body_name, BodyLoadType load_function) - { - return addBodySourceWithAllParams(body_name, load_function, - std::make_integer_sequence{}); - } - /** * @brief Add a boundary integral term to the weak form * @@ -195,20 +186,20 @@ class FunctionalWeakForm, * 3>`) * */ - template - void addBoundaryIntegral(DependsOn, std::string boundary_name, BoundaryIntegrandType integrand) + template + void addBoundaryIntegralImpl(std::string boundary_name, BoundaryIntegrandType integrand, std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; weak_form_->AddBoundaryIntegral( - Dimension{}, DependsOn{}, + Dimension{}, DependsOn{}, [dt, cycle, integrand](double time, auto X, auto... params) { return integrand(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(boundary_name)); v_dot_weak_form_residual_->AddBoundaryIntegral( - Dimension{}, DependsOn<0, 1 + active_parameters...>{}, + Dimension{}, DependsOn<0, 1 + all_params...>{}, [dt, cycle, integrand](double time, auto X, auto V, auto... params) { auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle), X, params...); return smith::inner(get(V), orig_surface_flux); @@ -216,12 +207,10 @@ class FunctionalWeakForm, mesh_->domain(boundary_name)); } - /// @overload template void addBoundaryIntegral(std::string boundary_name, const BoundaryIntegrandType& integrand) { - addBoundaryIntegralWithAllParams(boundary_name, integrand, - std::make_integer_sequence{}); + addBoundaryIntegralImpl(boundary_name, integrand, std::make_integer_sequence{}); } /** @@ -242,21 +231,20 @@ class FunctionalWeakForm, * 3>`) * */ - template - void addInteriorBoundaryIntegral(DependsOn, std::string interior_name, - InteriorIntegrandType integrand) + template + void addInteriorBoundaryIntegralImpl(std::string interior_name, InteriorIntegrandType integrand, std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; weak_form_->AddInteriorFaceIntegral( - Dimension{}, DependsOn{}, + Dimension{}, DependsOn{}, [dt, cycle, integrand](double time, auto X, auto... params) { return integrand(TimeInfo(time, *dt, *cycle), X, params...); }, mesh_->domain(interior_name)); v_dot_weak_form_residual_->AddInteriorFaceIntegral( - Dimension{}, DependsOn<0, 1 + active_parameters...>{}, + Dimension{}, DependsOn<0, 1 + all_params...>{}, [dt, cycle, integrand](double time, auto X, auto V, auto... params) { auto [V1, V2] = V; auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle), X, params...); @@ -266,12 +254,10 @@ class FunctionalWeakForm, mesh_->domain(interior_name)); } - /// @overload template void addInteriorBoundaryIntegral(std::string interior_name, const InteriorIntegrandType& integrand) { - addInteriorBoundaryIntegralWithAllParams(interior_name, integrand, - std::make_integer_sequence{}); + addInteriorBoundaryIntegralImpl(interior_name, integrand, std::make_integer_sequence{}); } /** @@ -295,23 +281,15 @@ class FunctionalWeakForm, * 3>`) * */ - template - void addBoundaryFlux(DependsOn depends_on, std::string boundary_name, - BoundaryFluxType flux_function) + template + void addBoundaryFlux(std::string boundary_name, BoundaryFluxType flux_function) { - addBoundaryIntegral(depends_on, boundary_name, [flux_function](const TimeInfo& t_info, auto X, auto... inputs) { + addBoundaryIntegral(boundary_name, [flux_function](const TimeInfo& t_info, auto X, auto... inputs) { auto n = cross(get(X)); return -flux_function(t_info, get(X), normalize(n), get(inputs)...); }); } - /// @overload - template - void addBoundaryFlux(std::string boundary_name, const BoundaryFluxType& integrand) - { - addBoundaryFluxWithAllParams(boundary_name, integrand, std::make_integer_sequence{}); - } - /// @overload mfem::Vector residual(TimeInfo time_info, ConstFieldPtr shape_disp, const std::vector& fields, [[maybe_unused]] const std::vector& quad_fields = {}) const override diff --git a/src/smith/physics/heat_transfer.hpp b/src/smith/physics/heat_transfer.hpp index 29c1870138..56984f98ae 100644 --- a/src/smith/physics/heat_transfer.hpp +++ b/src/smith/physics/heat_transfer.hpp @@ -460,7 +460,7 @@ class HeatTransfer, std::integer_sequ template void setMaterial(const MaterialType& material, Domain& domain) { - setMaterial(DependsOn<>{}, material, domain); + setMaterial(material, domain); } /** diff --git a/src/smith/physics/solid_mechanics.hpp b/src/smith/physics/solid_mechanics.hpp index f6867ee72b..e0ed49cee7 100644 --- a/src/smith/physics/solid_mechanics.hpp +++ b/src/smith/physics/solid_mechanics.hpp @@ -526,7 +526,7 @@ class SolidMechanics, std::integer_se * boundary is used. * ~~~ {.cpp} * - * solid_mechanics.addCustomBoundaryIntegral(DependsOn<>{}, [](double t, auto position, auto displacement, auto + * solid_mechanics.addCustomBoundaryIntegral([](double t, auto position, auto displacement, auto * acceleration, auto shape){ auto [X, dX_dxi] = position; * * auto [u, du_dxi] = displacement; @@ -628,7 +628,7 @@ class SolidMechanics, std::integer_se * * double lambda = 500.0; * double mu = 500.0; - * solid_mechanics.addCustomDomainIntegral(DependsOn<>{}, [=](auto x, auto displacement, auto acceleration, auto + * solid_mechanics.addCustomDomainIntegral([=](auto x, auto displacement, auto acceleration, auto * shape_displacement){ auto du_dx = smith::get<1>(displacement); * * auto I = Identity(); @@ -740,7 +740,7 @@ class SolidMechanics, std::integer_se void setMaterial(const MaterialType& material, Domain& domain, std::shared_ptr> qdata = EmptyQData) { - setMaterial(DependsOn<>{}, material, domain, qdata); + setMaterial(material, domain, qdata); } /** @@ -923,7 +923,7 @@ class SolidMechanics, std::integer_se template void addBodyForce(BodyForceType body_force, Domain& domain) { - addBodyForce(DependsOn<>{}, body_force, domain); + addBodyForce(body_force, domain); } /** diff --git a/src/smith/physics/tests/dynamic_solid_adjoint.cpp b/src/smith/physics/tests/dynamic_solid_adjoint.cpp index 50e19abbfd..26936b5b49 100644 --- a/src/smith/physics/tests/dynamic_solid_adjoint.cpp +++ b/src/smith/physics/tests/dynamic_solid_adjoint.cpp @@ -505,7 +505,7 @@ struct BucklingSensitivityFixture : public ::testing::Test { nonlinear_opts, linear_opts, dyn_opts, physics_prefix + std::to_string(iter++), mesh, std::vector{kname, gname}, 0, 0.0, checkpoint_to_disk, false); - solid->setMaterial(smith::DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid->setMaterial(mat, mesh->entireBody()); return solid; } }; diff --git a/src/smith/physics/tests/dynamic_thermal_adjoint.cpp b/src/smith/physics/tests/dynamic_thermal_adjoint.cpp index 4eb24916b6..ae88e75e3e 100644 --- a/src/smith/physics/tests/dynamic_thermal_adjoint.cpp +++ b/src/smith/physics/tests/dynamic_thermal_adjoint.cpp @@ -93,7 +93,7 @@ std::unique_ptr createParameterizedHeatTransfer( FiniteElementState user_defined_conductivity(mesh->mfemParMesh(), H1

{}, "user_defined_conductivity"); user_defined_conductivity = 1.1; thermal->setParameter(0, user_defined_conductivity); - thermal->setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); + thermal->setMaterial(mat, mesh->entireBody()); thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, mesh->entireBody()); thermal->setTemperature([](const mfem::Vector&, double) { return 0.0; }); @@ -117,7 +117,7 @@ std::unique_ptr createParameterizedNonlinearHeatTrans user_defined_conductivity = 1.1; thermal->setParameter(0, user_defined_conductivity); - thermal->setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); + thermal->setMaterial(mat, mesh->entireBody()); thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, mesh->entireBody()); diff --git a/src/smith/physics/tests/lce_Bertoldi_lattice.cpp b/src/smith/physics/tests/lce_Bertoldi_lattice.cpp index d4ee2268e8..9570d597d3 100644 --- a/src/smith/physics/tests/lce_Bertoldi_lattice.cpp +++ b/src/smith/physics/tests/lce_Bertoldi_lattice.cpp @@ -120,7 +120,7 @@ TEST(LiquidCrystalElastomer, Bertoldi) // Set material LiquidCrystalElastomerBertoldi lceMat(density, young_modulus, possion_ratio, max_order_param, beta_param); - solid_solver.setMaterial(DependsOn{}, lceMat, mesh->entireBody()); + solid_solver.setMaterial(lceMat, mesh->entireBody()); // Boundary conditions: // Prescribe zero displacement at the supported end of the beam diff --git a/src/smith/physics/tests/lce_Brighenti_tensile.cpp b/src/smith/physics/tests/lce_Brighenti_tensile.cpp index a97d3c6d40..402b2dcb75 100644 --- a/src/smith/physics/tests/lce_Brighenti_tensile.cpp +++ b/src/smith/physics/tests/lce_Brighenti_tensile.cpp @@ -133,7 +133,7 @@ TEST(LiquidCrystalElastomer, Brighenti) LiquidCrystElastomerBrighenti::State initial_state{}; auto qdata = solid_solver.createQuadratureDataBuffer(initial_state); - solid_solver.setMaterial(DependsOn{}, mat, mesh->entireBody(), qdata); + solid_solver.setMaterial(mat, mesh->entireBody(), qdata); // prescribe symmetry conditions solid_solver.setFixedBCs(mesh->domain("xmin_face"), Component::X); diff --git a/src/smith/physics/tests/parameterized_thermal.cpp b/src/smith/physics/tests/parameterized_thermal.cpp index 2db1ca9c37..1e1765bfa5 100644 --- a/src/smith/physics/tests/parameterized_thermal.cpp +++ b/src/smith/physics/tests/parameterized_thermal.cpp @@ -74,7 +74,7 @@ TEST(Thermal, ParameterizedMaterial) // Construct a potentially user-defined parameterized material and send it to the thermal module heat_transfer::ParameterizedLinearIsotropicConductor mat; - thermal_solver.setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); + thermal_solver.setMaterial(mat, mesh->entireBody()); // Define the function for the initial temperature and boundary condition auto bdr_temp = [](const mfem::Vector& x, double) -> double { diff --git a/src/smith/physics/tests/parameterized_thermomechanics_example.cpp b/src/smith/physics/tests/parameterized_thermomechanics_example.cpp index b3bd15984f..2f7ac519c0 100644 --- a/src/smith/physics/tests/parameterized_thermomechanics_example.cpp +++ b/src/smith/physics/tests/parameterized_thermomechanics_example.cpp @@ -117,7 +117,7 @@ TEST(Thermomechanics, ParameterizedMaterial) double theta_ref = 0.0; ///< datum temperature for thermal expansion ParameterizedThermoelasticMaterial material{density, E, nu, theta_ref}; - simulation.setMaterial(DependsOn<0, 1>{}, material, mesh->entireBody()); + simulation.setMaterial(material, mesh->entireBody()); double deltaT = 1.0; FiniteElementState temperature(mesh->mfemParMesh(), H1

{}, "theta"); diff --git a/src/smith/physics/tests/solid.cpp b/src/smith/physics/tests/solid.cpp index e48bed601f..e1d6411228 100644 --- a/src/smith/physics/tests/solid.cpp +++ b/src/smith/physics/tests/solid.cpp @@ -185,7 +185,7 @@ void functional_parameterized_solid_test(double expected_disp_norm) solid_solver.setParameter(1, user_defined_shear_modulus); solid_mechanics::ParameterizedLinearIsotropicSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid_solver.setMaterial(mat, mesh->entireBody()); // Specify initial / boundary conditions mesh->addDomainOfBoundaryElements("essential_boundary", by_attr(1)); @@ -206,9 +206,8 @@ void functional_parameterized_solid_test(double expected_disp_norm) // add some nonexistent body forces / tractions to check that // these parameterized versions compile and run without error - solid_solver.addBodyForce( - DependsOn<0>{}, [](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, mesh->entireBody()); - solid_solver.addBodyForce(DependsOn<1>{}, ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, + solid_solver.addBodyForce([](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, mesh->entireBody()); + solid_solver.addBodyForce(ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, mesh->entireBody()); solid_solver.setTraction(DependsOn<1>{}, [](const auto& x, auto...) { return 0 * x; }, mesh->entireBoundary()); diff --git a/src/smith/physics/tests/solid_finite_diff.cpp b/src/smith/physics/tests/solid_finite_diff.cpp index 2c4ee797f5..839f0d9d63 100644 --- a/src/smith/physics/tests/solid_finite_diff.cpp +++ b/src/smith/physics/tests/solid_finite_diff.cpp @@ -83,7 +83,7 @@ TEST(SolidMechanics, FiniteDifferenceParameter) constexpr int bulk_parameter_index = 0; solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid_solver.setMaterial(mat, mesh->entireBody()); // Define a boundary attribute set and specify initial / boundary conditions mesh->addDomainOfBoundaryElements("essential_boundary", by_attr(1)); diff --git a/src/smith/physics/tests/solid_periodic.cpp b/src/smith/physics/tests/solid_periodic.cpp index 9d6c56c341..3de6216e15 100644 --- a/src/smith/physics/tests/solid_periodic.cpp +++ b/src/smith/physics/tests/solid_periodic.cpp @@ -81,7 +81,7 @@ void periodic_test(mfem::Element::Type element_type) solid_solver.setParameter(1, user_defined_shear_modulus); solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid_solver.setMaterial(mat, mesh->entireBody()); mesh->addDomainOfBoundaryElements("support", by_attr(2)); solid_solver.setFixedBCs(mesh->domain("support")); diff --git a/src/smith/physics/tests/solid_reaction_adjoint.cpp b/src/smith/physics/tests/solid_reaction_adjoint.cpp index 62d31c8f5d..fc40979a2d 100644 --- a/src/smith/physics/tests/solid_reaction_adjoint.cpp +++ b/src/smith/physics/tests/solid_reaction_adjoint.cpp @@ -64,7 +64,7 @@ std::unique_ptr createNonlinearSolidMechanicsSolver(std::sha solid->setParameter(0, user_defined_bulk_modulus); solid->setParameter(1, user_defined_shear_modulus); - solid->setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); + solid->setMaterial(mat, mesh->entireBody()); solid->addBodyForce( [](auto X, auto /* t */) { diff --git a/src/smith/physics/tests/solid_robin_condition.cpp b/src/smith/physics/tests/solid_robin_condition.cpp index da7c7e2fc0..09f03f2748 100644 --- a/src/smith/physics/tests/solid_robin_condition.cpp +++ b/src/smith/physics/tests/solid_robin_condition.cpp @@ -76,9 +76,7 @@ void functional_solid_test_robin_condition() mesh->addDomainOfBoundaryElements("tip", by_attr(2)); solid_solver.setDisplacementBCs(translated_in_x, mesh->domain("tip"), Component::X); - solid_solver.addCustomBoundaryIntegral( - DependsOn<>{}, - [](double /* t */, auto /*position*/, auto displacement, auto /*acceleration*/) { + solid_solver.addCustomBoundaryIntegral([](double /* t */, auto /*position*/, auto displacement, auto /*acceleration*/) { auto [u, du_dxi] = displacement; auto f = u * 3.0; return f; // define a displacement-proportional traction at the support diff --git a/src/smith/physics/tests/test_functional_weak_form.cpp b/src/smith/physics/tests/test_functional_weak_form.cpp index dc32706636..78c8a2ce54 100644 --- a/src/smith/physics/tests/test_functional_weak_form.cpp +++ b/src/smith/physics/tests/test_functional_weak_form.cpp @@ -98,11 +98,11 @@ struct WeakFormFixture : public testing::Test { std::string surface_name = "side"; mesh->addDomainOfBoundaryElements(surface_name, smith::by_attr(1)); - f_weak_form->addBoundaryFlux(smith::DependsOn<>{}, surface_name, + f_weak_form->addBoundaryFlux(surface_name, [](auto /*t_info*/, auto /*x*/, auto n) { return 1.0 * n; }); - f_weak_form->addBodySource(smith::DependsOn<0>{}, mesh->entireBodyName(), + f_weak_form->addBodySource(mesh->entireBodyName(), [](auto /*t_info*/, auto /*x*/, auto u) { return u; }); - f_weak_form->addBodySource(smith::DependsOn<>{}, mesh->entireBodyName(), + f_weak_form->addBodySource(mesh->entireBodyName(), [](auto /*t_info*/, auto x) { return 0.5 * x; }); // initialize fields for testing diff --git a/src/smith/physics/tests/test_kinematic_objective.cpp b/src/smith/physics/tests/test_kinematic_objective.cpp index 1eb23160bc..b509414e92 100644 --- a/src/smith/physics/tests/test_kinematic_objective.cpp +++ b/src/smith/physics/tests/test_kinematic_objective.cpp @@ -58,8 +58,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { SolidMaterial mat; mat.K = 1.0; mat.G = 0.5; - solid_mechanics_weak_form->addBodyIntegral( - smith::DependsOn<0>{}, mesh->entireBodyName(), + solid_mechanics_weak_form->addBodyIntegral(mesh->entireBodyName(), [mat](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto density) { typename SolidMaterial::State state; auto pk_stress = mat.pkStress(state, smith::get(u), density); @@ -90,7 +89,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { ObjectiveT::SpacesT param_space_ptrs{&input_fields[DISP]->space(), &input_fields[DENSITY]->space()}; ObjectiveT mass_objective("mass constraining", mesh, param_space_ptrs); - mass_objective.addBodyIntegral(smith::DependsOn<1>{}, mesh->entireBodyName(), + mass_objective.addBodyIntegral(mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto RHO) { return get(RHO); }); double mass = mass_objective.evaluate(time_info, shape_disp.get(), objective_states); @@ -99,8 +98,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { for (int i = 0; i < dim; ++i) { auto cg_objective = std::make_shared("translation" + std::to_string(i), mesh, param_space_ptrs); - cg_objective->addBodyIntegral( - smith::DependsOn<0, 1>{}, mesh->entireBodyName(), [i](auto /*t_info*/, auto X, auto U, auto RHO) { + cg_objective->addBodyIntegral(mesh->entireBodyName(), [i](auto /*t_info*/, auto X, auto U, auto RHO) { return (get(X)[i] + get(U)[i]) * get(RHO); }); initial_cg[i] = cg_objective->evaluate(time_info, shape_disp.get(), objective_states) / mass; @@ -110,7 +108,7 @@ struct ConstrainedWeakFormFixture : public testing::Test { for (int i = 0; i < dim; ++i) { auto center_rotation_objective = std::make_shared("rotation" + std::to_string(i), mesh, param_space_ptrs); - center_rotation_objective->addBodyIntegral(smith::DependsOn<0, 1>{}, mesh->entireBodyName(), + center_rotation_objective->addBodyIntegral(mesh->entireBodyName(), [i, initial_cg](auto /*t_info*/, auto X, auto U, auto RHO) { auto u = get(U); auto x = get(X) + u; diff --git a/src/smith/physics/tests/thermal_finite_diff.cpp b/src/smith/physics/tests/thermal_finite_diff.cpp index ceaaafe17d..0a252b42db 100644 --- a/src/smith/physics/tests/thermal_finite_diff.cpp +++ b/src/smith/physics/tests/thermal_finite_diff.cpp @@ -77,7 +77,7 @@ TEST(Thermal, FiniteDifference) // Construct a potentially user-defined parameterized material and send it to the thermal module heat_transfer::ParameterizedLinearIsotropicConductor mat; - thermal_solver.setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); + thermal_solver.setMaterial(mat, mesh->entireBody()); // Define a constant source term heat_transfer::ConstantSource source{1.0}; diff --git a/src/smith/physics/tests/thermal_nonlinear_solve.cpp b/src/smith/physics/tests/thermal_nonlinear_solve.cpp index c9ea761dcd..0e11b7de66 100644 --- a/src/smith/physics/tests/thermal_nonlinear_solve.cpp +++ b/src/smith/physics/tests/thermal_nonlinear_solve.cpp @@ -65,7 +65,7 @@ void functional_thermal_test_nonlinear() thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, mesh->entireBody()); // clang-format off - thermal_solver.addCustomBoundaryIntegral(smith::DependsOn<>{}, [&](auto, auto, auto temperature, auto) { + thermal_solver.addCustomBoundaryIntegral([&](auto, auto, auto temperature, auto) { static constexpr double radiateConstant = 5.0-7; static constexpr double T0 = 21.0; using std::pow; diff --git a/src/smith/physics/tests/thermal_robin_condition.cpp b/src/smith/physics/tests/thermal_robin_condition.cpp index 2d27a583d1..f7c2af2fcd 100644 --- a/src/smith/physics/tests/thermal_robin_condition.cpp +++ b/src/smith/physics/tests/thermal_robin_condition.cpp @@ -64,8 +64,7 @@ void functional_thermal_test_robin_condition() thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, mesh->entireBody()); // clang-format off - thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, - [](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { + thermal_solver.addCustomBoundaryIntegral([](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { auto [T, dT_dxi] = temperature; auto q = 5.0*(T-25.0); return q; // define a convective (temperature-proportional) heat flux diff --git a/src/smith/physics/thermomechanics.hpp b/src/smith/physics/thermomechanics.hpp index 1d21cd1286..f85e20b38b 100644 --- a/src/smith/physics/thermomechanics.hpp +++ b/src/smith/physics/thermomechanics.hpp @@ -379,9 +379,9 @@ class Thermomechanics : public BasePhysics { // note: these parameter indices are offset by 1 since, internally, this module uses the first parameter // to communicate the temperature and displacement field information to the other physics module // - thermal_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, ThermalMaterialInterface{material}, + thermal_.setMaterial(ThermalMaterialInterface{material}, domain); - solid_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, MechanicalMaterialInterface{material}, + solid_.setMaterial(MechanicalMaterialInterface{material}, domain, qdata); } @@ -390,7 +390,7 @@ class Thermomechanics : public BasePhysics { void setMaterial(const MaterialType& material, Domain& domain, std::shared_ptr> qdata = EmptyQData) { - setMaterial(DependsOn<>{}, material, domain, qdata); + setMaterial(material, domain, qdata); } /** diff --git a/src/smith/physics/thermomechanics_monolithic.hpp b/src/smith/physics/thermomechanics_monolithic.hpp index 4d9b0fe16f..6d348871bd 100644 --- a/src/smith/physics/thermomechanics_monolithic.hpp +++ b/src/smith/physics/thermomechanics_monolithic.hpp @@ -567,7 +567,7 @@ class ThermomechanicsMonolithic, template void setMaterial(const MaterialType& material, Domain& domain) { - setMaterial(DependsOn<>{}, material, domain); + setMaterial(material, domain); } /** @@ -651,7 +651,7 @@ class ThermomechanicsMonolithic, template void addBodyForce(BodyForceType body_force, Domain& domain) { - addBodyForce(DependsOn<>{}, body_force, domain); + addBodyForce(body_force, domain); } /// @overload From 18c6b8ac90e0977d9b541c5d8448b93f14c45369 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 11 May 2026 08:54:07 -0600 Subject: [PATCH 50/67] Trying to simplify zero_cycle stuff. --- .../differentiable_numerics/field_store.cpp | 19 ++- .../differentiable_numerics/field_store.hpp | 3 + .../multiphysics_time_integrator.cpp | 18 ++- .../solid_mechanics_system.hpp | 36 +++--- .../differentiable_numerics/system_base.cpp | 42 ++++++- .../differentiable_numerics/system_base.hpp | 4 + .../tests/test_combined_thermo_mechanics.cpp | 26 +++- .../tests/test_explicit_dynamics.cpp | 24 ++-- .../test_multiphysics_time_integrator.cpp | 111 ++++++++++++++++++ .../tests/test_porous_heat_sink.cpp | 20 ++-- .../tests/test_solid_dynamics.cpp | 24 ++++ .../test_solid_static_with_internal_vars.cpp | 4 +- .../tests/test_thermal_static.cpp | 4 +- ...st_thermo_mechanics_with_internal_vars.cpp | 3 +- .../thermal_system.hpp | 64 ++-------- .../time_integration_rule.hpp | 21 +++- src/smith/physics/common.hpp | 13 +- src/smith/physics/functional_objective.hpp | 6 +- src/smith/physics/functional_weak_form.hpp | 77 ++++++------ src/smith/physics/tests/solid.cpp | 3 +- .../physics/tests/solid_robin_condition.cpp | 4 +- .../tests/test_functional_weak_form.cpp | 6 +- .../tests/test_kinematic_objective.cpp | 8 +- .../physics/tests/thermal_nonlinear_solve.cpp | 2 +- .../physics/tests/thermal_robin_condition.cpp | 2 +- src/smith/physics/thermomechanics.hpp | 6 +- 26 files changed, 375 insertions(+), 175 deletions(-) diff --git a/src/smith/differentiable_numerics/field_store.cpp b/src/smith/differentiable_numerics/field_store.cpp index 4db7b5ba0f..20d0869ade 100644 --- a/src/smith/differentiable_numerics/field_store.cpp +++ b/src/smith/differentiable_numerics/field_store.cpp @@ -128,6 +128,17 @@ std::vector> FieldStore::indexMap(const std::vector FieldStore::getBoundaryConditionManagers( const std::vector& weak_form_names) const +{ + std::vector field_names; + field_names.reserve(weak_form_names.size()); + for (const auto& wf_name : weak_form_names) { + field_names.push_back(getWeakFormReaction(wf_name)); + } + return getBoundaryConditionManagersForFields(field_names); +} + +std::vector FieldStore::getBoundaryConditionManagersForFields( + const std::vector& field_names) const { struct BoundaryConditionRef { std::string primary_name; @@ -147,18 +158,16 @@ std::vector FieldStore::getBoundaryConditionMan } std::vector bcs; - for (const auto& wf_name : weak_form_names) { - const std::string reaction_name = getWeakFormReaction(wf_name); - + for (const auto& field_name : field_names) { // Direct DBC entry takes precedence (e.g. an independent unknown like stress with its own BC). - auto direct = boundary_conditions_.find(reaction_name); + auto direct = boundary_conditions_.find(field_name); if (direct != boundary_conditions_.end()) { bcs.push_back(&direct->second->getBoundaryConditionManager()); continue; } // Otherwise resolve via the time-integration mapping that owns this reaction field. - auto ref_it = field_to_primary.find(reaction_name); + auto ref_it = field_to_primary.find(field_name); if (ref_it == field_to_primary.end()) { bcs.push_back(nullptr); continue; diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 8602256cc6..ac100c9a27 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -352,6 +352,9 @@ struct FieldStore { std::vector getBoundaryConditionManagers( const std::vector& weak_form_names) const; + std::vector getBoundaryConditionManagersForFields( + const std::vector& field_names) const; + /** * @brief Check whether a field exists. * diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp index 5178d08525..7979726a51 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.cpp @@ -63,15 +63,23 @@ std::pair, std::vector> MultiphysicsTimeI if (time_info.cycle() == 0 && !cycle_zero_systems_.empty() && requires_cycle_zero_solve) { for (const auto& cz_sys : cycle_zero_systems_) { - auto cycle_zero_unknowns = cz_sys->solve(time_info); + TimeInfo cycle_zero_time_info(time_info.time() - time_info.dt(), time_info.dt(), time_info.cycle(), + TimeInfo::EvaluationMode::CycleZero); + auto cycle_zero_unknowns = cz_sys->solve(cycle_zero_time_info); SLIC_ERROR_ROOT_IF(cycle_zero_unknowns.size() != cz_sys->weak_forms.size(), "Cycle zero system result count does not match number of cycle-zero weak forms"); + SLIC_ERROR_ROOT_IF(!cz_sys->solve_result_field_names.empty() && + cz_sys->solve_result_field_names.size() != cz_sys->weak_forms.size(), + "Cycle zero solve_result_field_names size does not match number of weak forms"); for (size_t i = 0; i < cz_sys->weak_forms.size(); ++i) { - std::string test_field_name = system_->field_store->getWeakFormReaction(cz_sys->weak_forms[i]->name()); - size_t test_field_state_idx = system_->field_store->getFieldIndex(test_field_name); - current_states[test_field_state_idx] = cycle_zero_unknowns[i]; - system_->field_store->setField(test_field_state_idx, cycle_zero_unknowns[i]); + const std::string result_field_name = + cz_sys->solve_result_field_names.empty() + ? system_->field_store->getWeakFormReaction(cz_sys->weak_forms[i]->name()) + : cz_sys->solve_result_field_names[i]; + size_t result_field_state_idx = system_->field_store->getFieldIndex(result_field_name); + current_states[result_field_state_idx] = cycle_zero_unknowns[i]; + system_->field_store->setField(result_field_state_idx, cycle_zero_unknowns[i]); } } } diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 3a1ba25735..7ab5ec021b 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -11,6 +11,8 @@ #pragma once +#include + #include "smith/differentiable_numerics/field_store.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/dirichlet_boundary_conditions.hpp" @@ -58,7 +60,7 @@ struct SolidMechanicsSystem : public SystemBase { typename detail::AppendCouplingToParams, H1, H1, H1, H1>>::type>; - std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. + std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. @@ -127,8 +129,7 @@ struct SolidMechanicsSystem : public SystemBase { { auto captured_rule = disp_time_rule; solid_weak_form->addBodySource( - domain_name, - [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); return force_function(t_info.time(), X, u_current, v_current, a_current, params...); }); @@ -145,8 +146,7 @@ struct SolidMechanicsSystem : public SystemBase { { auto captured_rule = disp_time_rule; solid_weak_form->addBoundaryFlux( - domain_name, - [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + domain_name, [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); return traction_function(t_info.time(), X, n, u_current, v_current, a_current, params...); }); @@ -163,8 +163,7 @@ struct SolidMechanicsSystem : public SystemBase { { auto captured_rule = disp_time_rule; solid_weak_form->addBoundaryIntegral( - domain_name, - [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { + domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { auto u_current = captured_rule->value(t_info, u, u_old, v_old, a_old); auto x_current = X + u_current; @@ -194,9 +193,6 @@ struct SolidMechanicsSystem : public SystemBase { } private: - - - }; /** @@ -319,12 +315,20 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons std::string cycle_zero_name = field_store->prefix("cycle_zero_acceleration_reaction"); field_store->markWeakFormInternal(cycle_zero_name); auto cycle_zero_solver = detail::makeCycleZeroSolver(solver, *field_store->getMesh()); - - // We reuse solid_weak_form for cycle zero, but we must pass correct inputs at evaluation time. - // However, the SystemBase needs a cycle_zero_systems populated. We use the solid_weak_form. - // Note that during cycle-zero solve, we want to solve for acceleration, but solid_weak_form - // was built with disp_type.name as the unknown. We'll handle this in MultiphysicsTimeIntegrator. - sys->cycle_zero_systems.push_back(makeSystem(field_store, cycle_zero_solver, {sys->solid_weak_form})); + + auto cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->solid_weak_form}); + cycle_zero_system->solve_result_field_names = {accel_old_type.name}; + auto cycle_zero_inputs = std::vector{disp_old_type.name, disp_old_type.name, velo_old_type.name, + accel_old_type.name}; + auto append_if_state = [&](const auto& field) { + const auto& states = field_store->getStateFields(); + if (std::any_of(states.begin(), states.end(), [&](const auto& state) { return state.get()->name() == field.name; })) { + cycle_zero_inputs.push_back(field.name); + } + }; + std::apply([&](const auto&... coupling_fields) { (append_if_state(coupling_fields), ...); }, coupling.fields); + cycle_zero_system->solve_input_field_names = {cycle_zero_inputs}; + sys->cycle_zero_systems.push_back(cycle_zero_system); } if (has_stress_output) { diff --git a/src/smith/differentiable_numerics/system_base.cpp b/src/smith/differentiable_numerics/system_base.cpp index 0ef2198005..e13589eb90 100644 --- a/src/smith/differentiable_numerics/system_base.cpp +++ b/src/smith/differentiable_numerics/system_base.cpp @@ -19,11 +19,44 @@ std::vector SystemBase::solve(const TimeInfo& time_info) const std::vector> index_map = field_store->indexMap(weak_form_names); std::vector> inputs; + if (!solve_input_field_names.empty()) { + SLIC_ERROR_IF(solve_input_field_names.size() != weak_forms.size(), + "solve_input_field_names size must match weak_forms size"); + } for (size_t i = 0; i < weak_forms.size(); ++i) { - std::string wf_name = weak_forms[i]->name(); - std::vector fields_for_wk = field_store->getStates(wf_name); + std::vector fields_for_wk; + if (solve_input_field_names.empty()) { + fields_for_wk = field_store->getStates(weak_forms[i]->name()); + } else { + for (const auto& field_name : solve_input_field_names[i]) { + fields_for_wk.push_back(field_store->getField(field_name)); + } + } inputs.push_back(fields_for_wk); } + + std::vector bc_field_names; + if (!solve_result_field_names.empty()) { + SLIC_ERROR_IF(solve_result_field_names.size() != weak_forms.size(), + "solve_result_field_names size must match weak_forms size"); + bc_field_names = solve_result_field_names; + for (size_t row = 0; row < weak_forms.size(); ++row) { + for (size_t col = 0; col < weak_forms.size(); ++col) { + index_map[row][col] = invalid_block_index; + for (size_t arg = 0; arg < inputs[row].size(); ++arg) { + if (inputs[row][arg].get()->name() == solve_result_field_names[col]) { + index_map[row][col] = arg; + break; + } + } + } + SLIC_ERROR_IF(index_map[row][row] == invalid_block_index, "Requested solve result field '" + << solve_result_field_names[row] + << "' is not an argument of weak form '" + << weak_form_names[row] << "'"); + } + } + auto params = field_store->getParameterFields(); std::vector> wk_params(weak_forms.size(), params); @@ -31,8 +64,11 @@ std::vector SystemBase::solve(const TimeInfo& time_info) const for (auto& p : weak_forms) { weak_form_ptrs.push_back(p.get()); } + auto bc_managers = solve_result_field_names.empty() + ? field_store->getBoundaryConditionManagers(weak_form_names) + : field_store->getBoundaryConditionManagersForFields(bc_field_names); return solver->solve(weak_form_ptrs, index_map, field_store->getShapeDisp(), inputs, wk_params, time_info, - field_store->getBoundaryConditionManagers(weak_form_names)); + bc_managers); } std::vector SystemBase::computeReactions(const TimeInfo& time_info, diff --git a/src/smith/differentiable_numerics/system_base.hpp b/src/smith/differentiable_numerics/system_base.hpp index 7421ec828d..4a4d9e7ec1 100644 --- a/src/smith/differentiable_numerics/system_base.hpp +++ b/src/smith/differentiable_numerics/system_base.hpp @@ -60,6 +60,10 @@ struct SystemBase { cycle_zero_systems; ///< Optional startup solves executed before first timestep. Each entry is solved ///< independently; cycle-zero solves do not couple across subsystems. std::vector> post_solve_systems; ///< Optional systems solved after main state update. + std::vector + solve_result_field_names; ///< Optional per-weak-form fields to solve/update instead of reaction fields. + std::vector> + solve_input_field_names; ///< Optional per-weak-form input field ordering used during solve. /// @brief Construct an empty system shell. SystemBase() = default; diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 7513e83589..4607a28aab 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -168,9 +168,9 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) thermal_system->setTemperatureBC(mesh_->domain("left"), [](auto, auto) { return 1.0; }); solid_system->addTraction("right", [=](double, auto X, auto, auto, auto, auto, auto... /*params*/) { - // If X is a dual number, we need to create a dual number for traction with zero derivative wrt all active parameters. - // For now, returning a value works perfectly fine with smith AD system! - // But since X might be a dual number, we must strip its dual part if we just want a value. + // If X is a dual number, we need to create a dual number for traction with zero derivative wrt all active + // parameters. For now, returning a value works perfectly fine with smith AD system! But since X might be a dual + // number, we must strip its dual part if we just want a value. auto traction = 0.0 * X; traction[0] = -0.015; return traction; @@ -407,6 +407,26 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) EXPECT_FALSE(combined_system->post_solve_systems.empty()); } +TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesCycleZeroSystems) +{ + using DynamicDispRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; + + auto solid_fields = registerSolidMechanicsFields(field_store_); + auto thermal_fields = registerThermalFields(field_store_); + + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields, + thermal_fields); + auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), + ThermalOptions{}, thermal_fields, solid_fields); + + auto combined_system = combineSystems(solid_system, thermal_system); + + ASSERT_EQ(solid_system->cycle_zero_systems.size(), 1u); + EXPECT_EQ(combined_system->cycle_zero_systems.size(), solid_system->cycle_zero_systems.size()); + EXPECT_EQ(combined_system->cycle_zero_systems[0]->solve_result_field_names, std::vector{"acceleration"}); +} + } // namespace smith int main(int argc, char* argv[]) diff --git a/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp index 5fde94c681..716af10f5e 100644 --- a/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_explicit_dynamics.cpp @@ -162,7 +162,7 @@ struct MeshFixture : public testing::Test { return smith::tuple{smith::get(a) * mat.density(), pk_stress}; }); - solid_mechanics_residual->addBodySource(mesh_->entireBodyName(), [](auto /*t_info*/, auto X) { + solid_mechanics_residual->addBodySource(mesh_->entireBodyName(), [](auto /*t_info*/, auto X, auto... /*args*/) { auto b = 0.0 * X; b[1] = gravity; return b; @@ -190,11 +190,10 @@ struct MeshFixture : public testing::Test { auto ke_objective = std::make_shared>>( "integrated_squared_temperature", mesh_, smith::spaces({states[DISP], params_[DENSITY]})); - ke_objective->addBodyIntegral(mesh_->entireBodyName(), - [](auto /*t*/, auto /*X*/, auto U, auto Rho) { - auto u = get(U); - return 0.5 * get(Rho) * smith::inner(u, u); - }); + ke_objective->addBodyIntegral(mesh_->entireBodyName(), [](auto /*t*/, auto /*X*/, auto U, auto Rho) { + auto u = get(U); + return 0.5 * get(Rho) * smith::inner(u, u); + }); objective_ = ke_objective; // kinetic energy integrator for qoi @@ -390,13 +389,12 @@ TEST_F(MeshFixture, TransientConstantGravity) smith::FunctionalObjective> accel_error("accel_error", mesh_, smith::spaces({all_fields[ACCEL]})); - accel_error.addBodyIntegral(mesh_->entireBodyName(), - [a_exact](auto /*t*/, auto /*X*/, auto A) { - auto a = smith::get(A); - auto da0 = a[0]; - auto da1 = a[1] - a_exact; - return da0 * da0 + da1 * da1; - }); + accel_error.addBodyIntegral(mesh_->entireBodyName(), [a_exact](auto /*t*/, auto /*X*/, auto A) { + auto a = smith::get(A); + auto da0 = a[0]; + auto da1 = a[1] - a_exact; + return da0 * da0 + da1 * da1; + }); double a_err = accel_error.evaluate(smith::TimeInfo(0.0, 1.0, 0), shape_disp_->get().get(), smith::getConstFieldPointers({all_fields[ACCEL]})); EXPECT_NEAR(0.0, a_err, 1e-14); diff --git a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp index 04c203ece3..c2cb034247 100644 --- a/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp +++ b/src/smith/differentiable_numerics/tests/test_multiphysics_time_integrator.cpp @@ -76,6 +76,51 @@ class CountingNoOpNonlinearBlockSolver : public NoOpNonlinearBlockSolver { } }; +class ConstantNonlinearBlockSolver : public NonlinearBlockSolverBase { + public: + explicit ConstantNonlinearBlockSolver(double value) : value_(value) {} + + std::vector solve( + const std::vector& u_guesses, std::function(const std::vector&)>, + std::function>(const std::vector&)>) const override + { + ++solve_calls_; + std::vector solved; + solved.reserve(u_guesses.size()); + for (const auto& guess : u_guesses) { + auto state = std::make_shared(*guess); + *state = value_; + solved.push_back(state); + } + return solved; + } + + std::vector solveAdjoint(const std::vector&, std::vector>&) const override + { + return {}; + } + + ConvergenceStatus convergenceStatus(double, const std::vector& residuals, + NonlinearConvergenceContext&) const override + { + ConvergenceStatus status; + status.global_converged = true; + status.converged = true; + status.block_norms.resize(residuals.size(), 0.0); + return status; + } + + void primeConvergenceContext(const std::vector&, NonlinearConvergenceContext&) const override {} + + int solveCalls() const { return solve_calls_; } + + void setInnerToleranceMultiplier(double) override {} + + private: + double value_; + mutable int solve_calls_ = 0; +}; + class NeedsInitialSolveRule : public QuasiStaticRule { public: bool requiresInitialAccelerationSolve() const override { return true; } @@ -246,6 +291,68 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroSkippedForQuasiStaticSecondOrderRule) StateManager::reset(); } +TEST(MultiphysicsTimeIntegrator, CycleZeroSolveResultUpdatesAccelerationField) +{ + axom::sidre::DataStore datastore; + StateManager::initialize(datastore, "cycle_zero_solve_result_updates_acceleration"); + + auto mesh = std::make_shared(mfem::Mesh::MakeCartesian2D(4, 4, mfem::Element::QUADRILATERAL, true, 1.0, 1.0), + "cycle_zero_update_mesh"); + + auto field_store = std::make_shared(mesh, 20); + FieldType> shape_disp_type("shape_displacement"); + field_store->addShapeDisp(shape_disp_type); + + auto time_rule = std::make_shared(); + auto static_rule = std::make_shared(); + FieldType> displacement_type("displacement_solve_state"); + field_store->addIndependent(displacement_type, time_rule); + FieldType> displacement_old_type("displacement"); + FieldType> velocity_type("velocity"); + FieldType> acceleration_type("acceleration"); + field_store->addIndependent(displacement_old_type, static_rule); + field_store->addIndependent(velocity_type, static_rule); + field_store->addIndependent(acceleration_type, static_rule); + + *field_store->getField(displacement_type.name).get() = 3.0; + *field_store->getField(acceleration_type.name).get() = -2.0; + + auto main_wf = buildScalarDiffusionWeakForm("displacement_main", mesh, field_store, displacement_type); + auto cycle_zero_wf = buildSecondOrderMainWeakForm("cycle_zero_acceleration", mesh, field_store, displacement_type, + displacement_old_type, velocity_type, acceleration_type); + + auto main_block_solver = std::make_shared(); + auto main_solver = std::make_shared(main_block_solver); + auto cycle_zero_block_solver = std::make_shared(9.0); + auto cycle_zero_solver = std::make_shared(cycle_zero_block_solver); + + auto main_system = std::make_shared(); + main_system->field_store = field_store; + main_system->weak_forms = {main_wf}; + main_system->solver = main_solver; + + auto cycle_zero_system = std::make_shared(); + cycle_zero_system->field_store = field_store; + cycle_zero_system->weak_forms = {cycle_zero_wf}; + cycle_zero_system->solver = cycle_zero_solver; + cycle_zero_system->solve_result_field_names = {acceleration_type.name}; + cycle_zero_system->solve_input_field_names = { + {displacement_old_type.name, displacement_old_type.name, velocity_type.name, acceleration_type.name}}; + + MultiphysicsTimeIntegrator advancer(main_system, {cycle_zero_system}); + + auto [new_states, reactions] = + advancer.advanceState(TimeInfo(0.0, 1.0, 0), field_store->getShapeDisp(), field_store->getAllFields(), {}); + static_cast(reactions); + + EXPECT_EQ(cycle_zero_block_solver->solveCalls(), 1); + EXPECT_EQ(main_block_solver->solveCalls(), 1); + EXPECT_NEAR((*new_states[field_store->getFieldIndex(acceleration_type.name)].get())(0), 9.0, 1.0e-12); + EXPECT_NEAR((*new_states[field_store->getFieldIndex(displacement_type.name)].get())(0), 3.0, 1.0e-12); + + StateManager::reset(); +} + TEST(MultiphysicsTimeIntegrator, CycleZeroAccelerationBcUsesDisplacementSecondTimeDerivative) { axom::sidre::DataStore datastore; @@ -277,8 +384,12 @@ TEST(MultiphysicsTimeIntegrator, CycleZeroAccelerationBcUsesDisplacementSecondTi displacement_bc->setScalarBCs<2>(mesh->domain("right"), [](double t, tensor) { return t * t; }); auto bc_managers = field_store->getBoundaryConditionManagers({cycle_zero_wf->name()}); + auto acceleration_bc_managers = field_store->getBoundaryConditionManagersForFields({acceleration_type.name}); + ASSERT_EQ(acceleration_bc_managers.size(), 1); + ASSERT_NE(acceleration_bc_managers[0], nullptr); ASSERT_EQ(bc_managers.size(), 1); ASSERT_NE(bc_managers[0], nullptr); + EXPECT_EQ(acceleration_bc_managers[0]->allEssentialTrueDofs().Size(), bc_managers[0]->allEssentialTrueDofs().Size()); const int right_dofs = bc_managers[0]->allEssentialTrueDofs().Size(); ASSERT_GT(right_dofs, 0); diff --git a/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp b/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp index 6f8c890c4f..e7d218dd1f 100644 --- a/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp +++ b/src/smith/differentiable_numerics/tests/test_porous_heat_sink.cpp @@ -191,14 +191,13 @@ TEST_P(BlockPreconditionerTest, BlockSolve) auto [gamma_val, dgamma_dX] = gamma_; return smith::tuple{a(gamma_val) * q_n(T_1_val, T_2_val), sigma(gamma_val) * dT_1_dX}; }); - T2_form.addBodyIntegral(mesh->entireBodyName(), - [kappa, a, q_n](auto /* t_info */, auto /* x */, auto T_1, auto T_2, auto gamma_) { - auto [T_1_val, dT_1_dX] = T_1; - auto [T_2_val, dT_2_dX] = T_2; - auto [gamma_val, dgamma_dX] = gamma_; - return smith::tuple{-1.0 * a(gamma_val) * q_n(T_1_val, T_2_val), - kappa(gamma_val) * dT_2_dX}; - }); + T2_form.addBodyIntegral( + mesh->entireBodyName(), [kappa, a, q_n](auto /* t_info */, auto /* x */, auto T_1, auto T_2, auto gamma_) { + auto [T_1_val, dT_1_dX] = T_1; + auto [T_2_val, dT_2_dX] = T_2; + auto [gamma_val, dgamma_dX] = gamma_; + return smith::tuple{-1.0 * a(gamma_val) * q_n(T_1_val, T_2_val), kappa(gamma_val) * dT_2_dX}; + }); auto T1_bc_manager = std::make_shared(mesh->mfemParMesh()); auto T2_bc_manager = std::make_shared(mesh->mfemParMesh()); @@ -207,8 +206,9 @@ TEST_P(BlockPreconditionerTest, BlockSolve) T2_bc_manager->addEssential(std::set{1}, zero_bcs, space(T2), 0); mesh->addDomainOfBoundaryElements("heat_spreader", by_attr<2>(2)); - T1_form.addBoundaryIntegral("heat_spreader", - [heatsink_options = heatsink_options](auto, auto) { return heatsink_options.q_app; }); + T1_form.addBoundaryIntegral("heat_spreader", [heatsink_options = heatsink_options](auto, auto, auto... /*args*/) { + return heatsink_options.q_app; + }); // Block Preconditioner Options smith::LinearSolverOptions linear_options; diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index a9153d59a2..900b5460ba 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -141,6 +141,30 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver EXPECT_FALSE(cz->solver->exactStaggeredSteps()); } +TEST(ImplicitNewmarkSecondOrderRule, CycleZeroModeUsesFourthArgumentAsCurrentAcceleration) +{ + ImplicitNewmarkSecondOrderTimeIntegrationRule rule; + TimeInfo regular_time(0.0, 2.0, 0); + TimeInfo cycle_zero_time(0.0, 2.0, 0, TimeInfo::EvaluationMode::CycleZero); + + const double u_new = 10.0; + const double u_old = 4.0; + const double v_old = 3.0; + const double a_arg = 7.0; + + EXPECT_DOUBLE_EQ(rule.value(regular_time, u_new, u_old, v_old, a_arg), u_new); + EXPECT_DOUBLE_EQ(rule.dot(regular_time, u_new, u_old, v_old, a_arg), 3.0); + EXPECT_DOUBLE_EQ(rule.ddot(regular_time, u_new, u_old, v_old, a_arg), -7.0); + + EXPECT_DOUBLE_EQ(rule.value(cycle_zero_time, u_new, u_old, v_old, a_arg), u_old); + EXPECT_DOUBLE_EQ(rule.dot(cycle_zero_time, u_new, u_old, v_old, a_arg), v_old); + EXPECT_DOUBLE_EQ(rule.ddot(cycle_zero_time, u_new, u_old, v_old, a_arg), a_arg); + + EXPECT_DOUBLE_EQ(rule.value(regular_time, u_old, u_old, v_old, a_arg), u_old); + EXPECT_DOUBLE_EQ(rule.dot(regular_time, u_old, u_old, v_old, a_arg), -v_old); + EXPECT_DOUBLE_EQ(rule.ddot(regular_time, u_old, u_old, v_old, a_arg), -6.0 - a_arg); +} + TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index a461c6c7da..c776cddc43 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -222,7 +222,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) // Apply a gravity-like body force in the -z direction double body_force_mag = -0.01; - solid_system->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto) { + solid_system->addBodyForce(mesh->entireBodyName(), [=](double, auto, auto, auto, auto, auto, auto, auto... /*args*/) { tensor f{}; f[2] = body_force_mag; return f; @@ -230,7 +230,7 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) // Apply a traction on the top face in the +z direction double traction_mag = 0.005; - solid_system->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto) { + solid_system->addTraction("top", [=](double, auto, auto /*n*/, auto, auto, auto, auto, auto, auto... /*args*/) { tensor t{}; t[2] = traction_mag; return t; diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 463d9d4ace..403be1653f 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -60,7 +60,7 @@ struct ThermalStaticFixture : public testing::Test { auto grad_temperature) { return smith::tuple{0.0, -k * grad_temperature}; }, "entire_body"); - thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/) { + thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/, auto... /*args*/) { auto x = X[0]; auto y = X[1]; return 2.0 * k * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); @@ -155,7 +155,7 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) "entire_body"); // DependsOn now indexes only trailing coupling/parameter args, so empty means "state-only". - thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/) { + thermal_system->addHeatSource("entire_body", [=](auto /*t*/, auto X, auto /*T*/, auto... /*args*/) { auto x = X[0]; auto y = X[1]; return 2.0 * M_PI * M_PI * sin(M_PI * x) * sin(M_PI * y); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 4630ac1579..5d1b30cd53 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -178,7 +178,8 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // Compressive traction on right face. // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old, alpha_ss, alpha_old) solid_system->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, - auto /*temp_ss*/, auto /*temp_old*/, auto /*alpha_ss*/, auto /*alpha_old*/) { + auto /*temp_ss*/, auto /*temp_old*/, auto /*alpha_ss*/, auto /*alpha_old*/, + auto... /*args*/) { auto t = 0.0 * X; t[0] = -0.005; return t; diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index bebeacdb92..710a4ed885 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -107,27 +107,6 @@ struct ThermalSystem : public SystemBase { }); } - /** - * @brief Add a body heat source to the thermal system (with DependsOn). - * @param depends_on Selects which primal and parameter fields the contribution depends on. - * @param domain_name The name of the domain where the heat source is applied. - * @param source_function (t_info, X, T, params...) -> heat_source. - */ - template - void addHeatSource(DependsOn depends_on, const std::string& domain_name, - HeatSourceType source_function) - { - (void)depends_on; - auto captured_temp_rule = temperature_time_rule; - - thermal_weak_form->template addBodySource<0, 1, (2 + active_parameters)...>( - DependsOn<0, 1, (2 + active_parameters)...>{}, domain_name, - [=](auto t_info, auto X, auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - return source_function(t_info, X, T, params...); - }); - } - /** * @brief Add a body heat source that depends on all state and parameter fields. * @param domain_name The name of the domain where the heat source is applied. @@ -137,33 +116,10 @@ struct ThermalSystem : public SystemBase { void addHeatSource(const std::string& domain_name, HeatSourceType source_function) { auto captured_rule = temperature_time_rule; - thermal_weak_form->addBodySource( - domain_name, - [=](auto t_info, auto X, auto T, auto T_old, auto... params) { - auto T_current = captured_rule->value(t_info, T, T_old); - return source_function(t_info.time(), X, T_current, params...); - }); - } - - /** - * @brief Add a boundary heat flux to the thermal system (with DependsOn). - * @param depends_on Selects which primal and parameter fields the contribution depends on. - * @param boundary_name The name of the boundary where the heat flux is applied. - * @param flux_function (t_info, X, n, T, params...) -> heat_flux. - */ - template - void addHeatFlux(DependsOn depends_on, const std::string& boundary_name, - HeatFluxType flux_function) - { - (void)depends_on; - auto captured_temp_rule = temperature_time_rule; - - thermal_weak_form->template addBoundaryFlux<0, 1, (2 + active_parameters)...>( - DependsOn<0, 1, (2 + active_parameters)...>{}, boundary_name, - [=](auto t_info, auto X, auto n, auto temperature, auto temperature_old, auto... params) { - auto T = captured_temp_rule->value(t_info, temperature, temperature_old); - return -flux_function(t_info, X, n, T, params...); - }); + thermal_weak_form->addBodySource(domain_name, [=](auto t_info, auto X, auto T, auto T_old, auto... params) { + auto T_current = captured_rule->value(t_info, T, T_old); + return source_function(t_info.time(), X, T_current, params...); + }); } /** @@ -175,12 +131,11 @@ struct ThermalSystem : public SystemBase { void addHeatFlux(const std::string& boundary_name, HeatFluxType flux_function) { auto captured_rule = temperature_time_rule; - thermal_weak_form->addBoundaryFlux( - boundary_name, - [=](auto t_info, auto X, auto n, auto T, auto T_old, auto... params) { - auto T_current = captured_rule->value(t_info, T, T_old); - return flux_function(t_info.time(), X, n, T_current, params...); - }); + thermal_weak_form->addBoundaryFlux(boundary_name, + [=](auto t_info, auto X, auto n, auto T, auto T_old, auto... params) { + auto T_current = captured_rule->value(t_info, T, T_old); + return flux_function(t_info.time(), X, n, T_current, params...); + }); } /// Set zero-temperature Dirichlet BC. @@ -194,7 +149,6 @@ struct ThermalSystem : public SystemBase { } private: - }; struct ThermalOptions {}; diff --git a/src/smith/differentiable_numerics/time_integration_rule.hpp b/src/smith/differentiable_numerics/time_integration_rule.hpp index 8262d31068..7dd7ec5996 100644 --- a/src/smith/differentiable_numerics/time_integration_rule.hpp +++ b/src/smith/differentiable_numerics/time_integration_rule.hpp @@ -241,7 +241,12 @@ struct ImplicitNewmarkSecondOrderTimeIntegrationRule : public TimeIntegrationRul [[maybe_unused]] const T2& field_old, [[maybe_unused]] const T3& velo_old, [[maybe_unused]] const T4& accel_old) const { - return field_new; + auto regular_value = field_new + (field_old - field_old); + auto cycle_zero_correction = field_old - field_new; + if (t.isCycleZeroEvaluation()) { + return regular_value + cycle_zero_correction; + } + return regular_value + (cycle_zero_correction - cycle_zero_correction); } /// @brief evaluate time derivative discretization of the ode state as used by the integration rule @@ -250,7 +255,12 @@ struct ImplicitNewmarkSecondOrderTimeIntegrationRule : public TimeIntegrationRul [[maybe_unused]] const T2& field_old, [[maybe_unused]] const T3& velo_old, [[maybe_unused]] const T4& accel_old) const { - return (2.0 / t.dt()) * (field_new - field_old) - velo_old; + auto regular_dot = (2.0 / t.dt()) * (field_new - field_old) - velo_old; + auto cycle_zero_correction = velo_old - regular_dot; + if (t.isCycleZeroEvaluation()) { + return regular_dot + cycle_zero_correction; + } + return regular_dot + (cycle_zero_correction - cycle_zero_correction); } /// @brief evaluate time derivative discretization of the ode state as used by the integration rule @@ -260,7 +270,12 @@ struct ImplicitNewmarkSecondOrderTimeIntegrationRule : public TimeIntegrationRul [[maybe_unused]] const T4& accel_old) const { auto dt = t.dt(); - return (4.0 / (dt * dt)) * (field_new - field_old) - (4.0 / dt) * velo_old - accel_old; + auto regular_ddot = (4.0 / (dt * dt)) * (field_new - field_old) - (4.0 / dt) * velo_old - accel_old; + auto cycle_zero_correction = accel_old - regular_ddot; + if (t.isCycleZeroEvaluation()) { + return regular_ddot + cycle_zero_correction; + } + return regular_ddot + (cycle_zero_correction - cycle_zero_correction); } /// @brief interpolate all derived quantities in one call diff --git a/src/smith/physics/common.hpp b/src/smith/physics/common.hpp index 729ac58782..caa602fc5a 100644 --- a/src/smith/physics/common.hpp +++ b/src/smith/physics/common.hpp @@ -16,9 +16,11 @@ namespace smith { /// @brief struct storing time and timestep information struct TimeInfo { + enum class EvaluationMode { Regular, CycleZero }; + /// @brief constructor - TimeInfo(double t, double t_step, size_t c = 0) - : time_(std::make_pair(t, 0.0)), dt_(std::make_pair(t_step, 0.0)), cycle_(c) + TimeInfo(double t, double t_step, size_t c = 0, EvaluationMode mode = EvaluationMode::Regular) + : time_(std::make_pair(t, 0.0)), dt_(std::make_pair(t_step, 0.0)), cycle_(c), mode_(mode) { } @@ -31,10 +33,17 @@ struct TimeInfo { /// @brief accessor for cycle size_t cycle() const { return cycle_; } + /// @brief true when evaluating the startup acceleration solve. + bool isCycleZeroEvaluation() const { return mode_ == EvaluationMode::CycleZero; } + + /// @brief accessor for residual evaluation mode. + EvaluationMode mode() const { return mode_; } + private: std::pair time_; ///< time and its dual std::pair dt_; ///< timestep and its dual size_t cycle_; ///< cycle, step, iteration count + EvaluationMode mode_; ///< residual evaluation mode }; /** diff --git a/src/smith/physics/functional_objective.hpp b/src/smith/physics/functional_objective.hpp index 8937c7eab2..6c00addd8d 100644 --- a/src/smith/physics/functional_objective.hpp +++ b/src/smith/physics/functional_objective.hpp @@ -68,7 +68,8 @@ class FunctionalObjective, std::integer_ * @param qfunction a callable that returns a tuple of body-force and stress */ template - void addBodyIntegralImpl(std::string body_name, const FuncOfTimeSpaceAndParams& qfunction, std::integer_sequence) + void addBodyIntegralImpl(std::string body_name, const FuncOfTimeSpaceAndParams& qfunction, + std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; @@ -94,7 +95,8 @@ class FunctionalObjective, std::integer_ * @param qfunction a callable that returns a tuple of body-force and stress */ template - void addBoundaryIntegralImpl(std::string boundary_name, const FuncOfTimeSpaceAndParams& qfunction, std::integer_sequence) + void addBoundaryIntegralImpl(std::string boundary_name, const FuncOfTimeSpaceAndParams& qfunction, + std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; diff --git a/src/smith/physics/functional_weak_form.hpp b/src/smith/physics/functional_weak_form.hpp index 1a446cbfef..3eb0742551 100644 --- a/src/smith/physics/functional_weak_form.hpp +++ b/src/smith/physics/functional_weak_form.hpp @@ -87,7 +87,6 @@ class FunctionalWeakForm, /** * @brief Add a body integral contribution to the residual * - * // DependsOn can be indices into fields which the body integral may depend on * @tparam BodyIntegralType The type of the body integral * @param body_name The name of the registered domain over which the body integrals are evaluated. * @param integrand A function describing the body force applied. Our convention for the sign of the residual @@ -99,8 +98,7 @@ class FunctionalWeakForm, * 1. `double t` the time * 2. `tuple{tensor, isoparametric derivative} X` the spatial coordinates for the quadrature point and the * coordinate's isoparametric derivative. - * 3. `tuple{value, derivative}`, a variadic list of tuples (each with a values and spatial derivative), - * one tuple for each of the trial spaces specified in the `DependsOn<...>` argument. + * 3. `tuple{value, derivative}`, a variadic list of tuples, one for each input field. * @note The actual types of these arguments passed will be `double`, `tensor` or tuples thereof * when doing direct evaluation. When differentiating with respect to one of the inputs, its stored * values will change to `dual` numbers rather than `double`. (e.g. `tensor` becomes `tensor, @@ -112,17 +110,18 @@ class FunctionalWeakForm, { const double* dt = &dt_; const size_t* cycle = &cycle_; + const TimeInfo::EvaluationMode* mode = &mode_; weak_form_->AddDomainIntegral( Dimension{}, DependsOn{}, - [dt, cycle, integrand](double time, auto X, auto... inputs) { - return integrand(TimeInfo(time, *dt, *cycle), X, inputs...); + [dt, cycle, mode, integrand](double time, auto X, auto... inputs) { + return integrand(TimeInfo(time, *dt, *cycle, *mode), X, inputs...); }, mesh_->domain(body_name)); v_dot_weak_form_residual_->AddDomainIntegral( Dimension{}, DependsOn<0, 1 + all_params...>{}, - [dt, cycle, integrand](double time, auto X, auto V, auto... inputs) { - auto orig_tuple = integrand(TimeInfo(time, *dt, *cycle), X, inputs...); + [dt, cycle, mode, integrand](double time, auto X, auto V, auto... inputs) { + auto orig_tuple = integrand(TimeInfo(time, *dt, *cycle, *mode), X, inputs...); return smith::inner(get(V), get(orig_tuple)) + smith::inner(get(V), get(orig_tuple)); }, @@ -138,16 +137,13 @@ class FunctionalWeakForm, /** * @brief Add a body source (body load) to the weak form * - * @tparam active_parameters Type for indices into fields which the body integral may depend on * @tparam BodyLoadType The type of the body load function * @param body_name The name of the registered domain over which the body loads are applied. - * @param depends_on Indices into fields which the body integral may depend on * @param load_function A function describing the body force applied. * @pre load_function must be a object that can be called with the following arguments: * 1. `double t` the time * 2. `tensor X` the spatial coordinates for the quadrature point. - * 3. `value`, a variadic list of field values, one tuple for each of the trial spaces specified in the - * `DependsOn<...>` argument. + * 3. `value`, a variadic list of field values, one for each input field. * The expected return is the value of the source at X. * @note The actual types of these arguments passed will be `double`, `tensor` or tuples thereof * when doing direct evaluation. When differentiating with respect to one of the inputs, its stored @@ -166,7 +162,6 @@ class FunctionalWeakForm, /** * @brief Add a boundary integral term to the weak form * - * * // DependsOn can be indices into fields which the body integral may depend on * @tparam BoundaryIntegrandType The type of the boundary integral function. * @param boundary_name The name of the registered domain over which the boundary integral is applied. * @param integrand A function describing the boundary integral term to include in the weak form. @@ -178,8 +173,7 @@ class FunctionalWeakForm, * @pre integrand must be a object that can be called with the following arguments: * 1. `double t` the time * 2. `tuple{tensor, surface isoparametric derivative} X` the spatial coordinates for the quadrature point - * 3. `tuple{value, surface isoparametric derivative}`, a variadic list of tuples (each with a values and - * derivative), one tuple for each of the trial spaces specified in the `DependsOn<...>` argument. + * 3. `tuple{value, surface isoparametric derivative}`, a variadic list of tuples, one for each input field. * @note The actual types of these arguments passed will be `double`, `tensor` or tuples thereof * when doing direct evaluation. When differentiating with respect to one of the inputs, its stored * values will change to `dual` numbers rather than `double`. (e.g. `tensor` becomes `tensor, @@ -187,21 +181,23 @@ class FunctionalWeakForm, * */ template - void addBoundaryIntegralImpl(std::string boundary_name, BoundaryIntegrandType integrand, std::integer_sequence) + void addBoundaryIntegralImpl(std::string boundary_name, BoundaryIntegrandType integrand, + std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; + const TimeInfo::EvaluationMode* mode = &mode_; weak_form_->AddBoundaryIntegral( Dimension{}, DependsOn{}, - [dt, cycle, integrand](double time, auto X, auto... params) { - return integrand(TimeInfo(time, *dt, *cycle), X, params...); + [dt, cycle, mode, integrand](double time, auto X, auto... params) { + return integrand(TimeInfo(time, *dt, *cycle, *mode), X, params...); }, mesh_->domain(boundary_name)); v_dot_weak_form_residual_->AddBoundaryIntegral( Dimension{}, DependsOn<0, 1 + all_params...>{}, - [dt, cycle, integrand](double time, auto X, auto V, auto... params) { - auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle), X, params...); + [dt, cycle, mode, integrand](double time, auto X, auto V, auto... params) { + auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle, *mode), X, params...); return smith::inner(get(V), orig_surface_flux); }, mesh_->domain(boundary_name)); @@ -216,15 +212,13 @@ class FunctionalWeakForm, /** * @brief Add a interior boundary integral term to the weak form * - * * // DependsOn can be indices into fields which the body integral may depend on * @tparam InteriorIntegrandType The type of the interior boundary integral function. * @param interior_name The name of the registered domain over which the interior boundary integral is applied. * @param integrand A function describing the interior boundary integral term to include in the weak form. * @pre integrand must be a object that can be called with the following arguments: * 1. `double t` the time * 2. `tuple{tensor, surface isoparametric derivative} X` the spatial coordinates for the quadrature point - * 3. `tuple{value, surface isoparametric derivative}`, a variadic list of tuples (each with a values and - * derivative), one tuple for each of the trial spaces specified in the `DependsOn<...>` argument. + * 3. `tuple{value, surface isoparametric derivative}`, a variadic list of tuples, one for each input field. * @note The actual types of these arguments passed will be `double`, `tensor` or tuples thereof * when doing direct evaluation. When differentiating with respect to one of the inputs, its stored * values will change to `dual` numbers rather than `double`. (e.g. `tensor` becomes `tensor, @@ -232,22 +226,24 @@ class FunctionalWeakForm, * */ template - void addInteriorBoundaryIntegralImpl(std::string interior_name, InteriorIntegrandType integrand, std::integer_sequence) + void addInteriorBoundaryIntegralImpl(std::string interior_name, InteriorIntegrandType integrand, + std::integer_sequence) { const double* dt = &dt_; const size_t* cycle = &cycle_; + const TimeInfo::EvaluationMode* mode = &mode_; weak_form_->AddInteriorFaceIntegral( Dimension{}, DependsOn{}, - [dt, cycle, integrand](double time, auto X, auto... params) { - return integrand(TimeInfo(time, *dt, *cycle), X, params...); + [dt, cycle, mode, integrand](double time, auto X, auto... params) { + return integrand(TimeInfo(time, *dt, *cycle, *mode), X, params...); }, mesh_->domain(interior_name)); v_dot_weak_form_residual_->AddInteriorFaceIntegral( Dimension{}, DependsOn<0, 1 + all_params...>{}, - [dt, cycle, integrand](double time, auto X, auto V, auto... params) { + [dt, cycle, mode, integrand](double time, auto X, auto V, auto... params) { auto [V1, V2] = V; - auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle), X, params...); + auto orig_surface_flux = integrand(TimeInfo(time, *dt, *cycle, *mode), X, params...); auto [flux_pos, flux_neg] = orig_surface_flux; return smith::inner(V1, flux_pos) + smith::inner(V2, flux_neg); }, @@ -257,23 +253,21 @@ class FunctionalWeakForm, template void addInteriorBoundaryIntegral(std::string interior_name, const InteriorIntegrandType& integrand) { - addInteriorBoundaryIntegralImpl(interior_name, integrand, std::make_integer_sequence{}); + addInteriorBoundaryIntegralImpl(interior_name, integrand, + std::make_integer_sequence{}); } /** * @brief Add a boundary flux term to the weak form * - * @tparam active_parameters Type for indices into fields which the body integral may depend on * @tparam BoundaryFluxType The type of the traction load - * @param depends_on Indices into fields which the body integral may depend on * @param boundary_name The name of the registered domain over which the boundary integral is applied. * @param flux_function A function describing the outward normal flux applied. * @pre flux_function must be a object that can be called with the following arguments: * 1. `double t` the time * 1. `tensor X` the spatial coordinates for the quadrature point * 3. `tensor n` the outward-facing unit normal for the quadrature point - * 4. `value`, a variadic list of tuples of field values at quadrature points, - * one for each of the trial spaces specified in the `DependsOn<...>` argument. + * 4. `value`, a variadic list of field values at quadrature points, one for each input field. * The expected return is the value of the boundary flux oriented in the sense of the outward normal. * @note The actual types of these arguments passed will be `double`, `tensor` or tuples thereof * when doing direct evaluation. When differentiating with respect to one of the inputs, its stored @@ -297,6 +291,7 @@ class FunctionalWeakForm, validateFields(fields, "residual"); dt_ = time_info.dt(); cycle_ = time_info.cycle(); + mode_ = time_info.mode(); auto ret = (*weak_form_)(time_info.time(), *shape_disp, *fields[input_indices]...); return ret; } @@ -310,6 +305,7 @@ class FunctionalWeakForm, validateFields(fields, "jacobian"); dt_ = time_info.dt(); cycle_ = time_info.cycle(); + mode_ = time_info.mode(); std::unique_ptr J; @@ -352,6 +348,7 @@ class FunctionalWeakForm, dt_ = time_info.dt(); cycle_ = time_info.cycle(); + mode_ = time_info.mode(); auto jacs = jacobianFunctions(std::make_integer_sequence{}, time_info.time(), shape_disp, fields); @@ -378,6 +375,7 @@ class FunctionalWeakForm, dt_ = time_info.dt(); cycle_ = time_info.cycle(); + mode_ = time_info.mode(); auto vecJacs = vectorJacobianFunctions(std::make_integer_sequence{}, time_info.time(), shape_disp, v_field, fields); @@ -415,7 +413,7 @@ class FunctionalWeakForm, void addBodyIntegralWithAllParams(std::string body_name, BodyIntegralType integrand, std::integer_sequence) { - this->template addBodyIntegral(DependsOn{}, body_name, integrand); + addBodyIntegralImpl(body_name, integrand, std::integer_sequence{}); } template @@ -423,7 +421,8 @@ class FunctionalWeakForm, void addBodySourceWithAllParams(std::string body_name, BodyLoadType load_function, std::integer_sequence) { - this->template addBodySource(DependsOn{}, body_name, load_function); + (void)std::integer_sequence{}; + addBodySource(body_name, load_function); } template @@ -431,7 +430,7 @@ class FunctionalWeakForm, void addBoundaryIntegralWithAllParams(std::string boundary_name, BoundaryIntegrandType integrand, std::integer_sequence) { - this->template addBoundaryIntegral(DependsOn{}, boundary_name, integrand); + addBoundaryIntegralImpl(boundary_name, integrand, std::integer_sequence{}); } template @@ -439,7 +438,8 @@ class FunctionalWeakForm, void addBoundaryFluxWithAllParams(std::string boundary_name, BoundaryFluxType flux_function, std::integer_sequence) { - this->template addBoundaryFlux(DependsOn{}, boundary_name, flux_function); + (void)std::integer_sequence{}; + addBoundaryFlux(boundary_name, flux_function); } template @@ -447,7 +447,7 @@ class FunctionalWeakForm, void addInteriorBoundaryIntegralWithAllParams(std::string interior_name, InteriorIntegrandType integrand, std::integer_sequence) { - this->template addInteriorBoundaryIntegral(DependsOn{}, interior_name, integrand); + addInteriorBoundaryIntegralImpl(interior_name, integrand, std::integer_sequence{}); } /// @brief Helper to validate input spaces recursively (for constructor) @@ -554,6 +554,9 @@ class FunctionalWeakForm, /// @brief cycle or step or iteration. This counter is useful for certain time integrators. mutable size_t cycle_ = 0; + /// @brief residual evaluation mode. + mutable TimeInfo::EvaluationMode mode_ = TimeInfo::EvaluationMode::Regular; + /// @brief primary mesh std::shared_ptr mesh_; diff --git a/src/smith/physics/tests/solid.cpp b/src/smith/physics/tests/solid.cpp index e1d6411228..98b76bdffa 100644 --- a/src/smith/physics/tests/solid.cpp +++ b/src/smith/physics/tests/solid.cpp @@ -207,8 +207,7 @@ void functional_parameterized_solid_test(double expected_disp_norm) // add some nonexistent body forces / tractions to check that // these parameterized versions compile and run without error solid_solver.addBodyForce([](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, mesh->entireBody()); - solid_solver.addBodyForce(ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, - mesh->entireBody()); + solid_solver.addBodyForce(ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, mesh->entireBody()); solid_solver.setTraction(DependsOn<1>{}, [](const auto& x, auto...) { return 0 * x; }, mesh->entireBoundary()); // Finalize the data structures diff --git a/src/smith/physics/tests/solid_robin_condition.cpp b/src/smith/physics/tests/solid_robin_condition.cpp index 09f03f2748..da7c7e2fc0 100644 --- a/src/smith/physics/tests/solid_robin_condition.cpp +++ b/src/smith/physics/tests/solid_robin_condition.cpp @@ -76,7 +76,9 @@ void functional_solid_test_robin_condition() mesh->addDomainOfBoundaryElements("tip", by_attr(2)); solid_solver.setDisplacementBCs(translated_in_x, mesh->domain("tip"), Component::X); - solid_solver.addCustomBoundaryIntegral([](double /* t */, auto /*position*/, auto displacement, auto /*acceleration*/) { + solid_solver.addCustomBoundaryIntegral( + DependsOn<>{}, + [](double /* t */, auto /*position*/, auto displacement, auto /*acceleration*/) { auto [u, du_dxi] = displacement; auto f = u * 3.0; return f; // define a displacement-proportional traction at the support diff --git a/src/smith/physics/tests/test_functional_weak_form.cpp b/src/smith/physics/tests/test_functional_weak_form.cpp index 78c8a2ce54..c487ec3cab 100644 --- a/src/smith/physics/tests/test_functional_weak_form.cpp +++ b/src/smith/physics/tests/test_functional_weak_form.cpp @@ -99,11 +99,11 @@ struct WeakFormFixture : public testing::Test { mesh->addDomainOfBoundaryElements(surface_name, smith::by_attr(1)); f_weak_form->addBoundaryFlux(surface_name, - [](auto /*t_info*/, auto /*x*/, auto n) { return 1.0 * n; }); + [](auto /*t_info*/, auto /*x*/, auto n, auto... /*args*/) { return 1.0 * n; }); f_weak_form->addBodySource(mesh->entireBodyName(), - [](auto /*t_info*/, auto /*x*/, auto u) { return u; }); + [](auto /*t_info*/, auto /*x*/, auto u, auto... /*args*/) { return u; }); f_weak_form->addBodySource(mesh->entireBodyName(), - [](auto /*t_info*/, auto x) { return 0.5 * x; }); + [](auto /*t_info*/, auto x, auto... /*args*/) { return 0.5 * x; }); // initialize fields for testing diff --git a/src/smith/physics/tests/test_kinematic_objective.cpp b/src/smith/physics/tests/test_kinematic_objective.cpp index b509414e92..3163ea951c 100644 --- a/src/smith/physics/tests/test_kinematic_objective.cpp +++ b/src/smith/physics/tests/test_kinematic_objective.cpp @@ -58,8 +58,8 @@ struct ConstrainedWeakFormFixture : public testing::Test { SolidMaterial mat; mat.K = 1.0; mat.G = 0.5; - solid_mechanics_weak_form->addBodyIntegral(mesh->entireBodyName(), - [mat](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto density) { + solid_mechanics_weak_form->addBodyIntegral( + mesh->entireBodyName(), [mat](auto /*t_info*/, auto /*X*/, auto u, auto /*v*/, auto a, auto density) { typename SolidMaterial::State state; auto pk_stress = mat.pkStress(state, smith::get(u), density); return smith::tuple{smith::get(a) * mat.density(density), pk_stress}; @@ -99,8 +99,8 @@ struct ConstrainedWeakFormFixture : public testing::Test { for (int i = 0; i < dim; ++i) { auto cg_objective = std::make_shared("translation" + std::to_string(i), mesh, param_space_ptrs); cg_objective->addBodyIntegral(mesh->entireBodyName(), [i](auto /*t_info*/, auto X, auto U, auto RHO) { - return (get(X)[i] + get(U)[i]) * get(RHO); - }); + return (get(X)[i] + get(U)[i]) * get(RHO); + }); initial_cg[i] = cg_objective->evaluate(time_info, shape_disp.get(), objective_states) / mass; constraint_evaluators.push_back(cg_objective); } diff --git a/src/smith/physics/tests/thermal_nonlinear_solve.cpp b/src/smith/physics/tests/thermal_nonlinear_solve.cpp index 0e11b7de66..e1de3b1aa3 100644 --- a/src/smith/physics/tests/thermal_nonlinear_solve.cpp +++ b/src/smith/physics/tests/thermal_nonlinear_solve.cpp @@ -65,7 +65,7 @@ void functional_thermal_test_nonlinear() thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, mesh->entireBody()); // clang-format off - thermal_solver.addCustomBoundaryIntegral([&](auto, auto, auto temperature, auto) { + thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, [&](auto, auto, auto temperature, auto) { static constexpr double radiateConstant = 5.0-7; static constexpr double T0 = 21.0; using std::pow; diff --git a/src/smith/physics/tests/thermal_robin_condition.cpp b/src/smith/physics/tests/thermal_robin_condition.cpp index f7c2af2fcd..952325e59b 100644 --- a/src/smith/physics/tests/thermal_robin_condition.cpp +++ b/src/smith/physics/tests/thermal_robin_condition.cpp @@ -64,7 +64,7 @@ void functional_thermal_test_robin_condition() thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, mesh->entireBody()); // clang-format off - thermal_solver.addCustomBoundaryIntegral([](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { + thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, [](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { auto [T, dT_dxi] = temperature; auto q = 5.0*(T-25.0); return q; // define a convective (temperature-proportional) heat flux diff --git a/src/smith/physics/thermomechanics.hpp b/src/smith/physics/thermomechanics.hpp index f85e20b38b..266d9a5570 100644 --- a/src/smith/physics/thermomechanics.hpp +++ b/src/smith/physics/thermomechanics.hpp @@ -379,10 +379,8 @@ class Thermomechanics : public BasePhysics { // note: these parameter indices are offset by 1 since, internally, this module uses the first parameter // to communicate the temperature and displacement field information to the other physics module // - thermal_.setMaterial(ThermalMaterialInterface{material}, - domain); - solid_.setMaterial(MechanicalMaterialInterface{material}, - domain, qdata); + thermal_.setMaterial(ThermalMaterialInterface{material}, domain); + solid_.setMaterial(MechanicalMaterialInterface{material}, domain, qdata); } /// @overload From 863b5ddf2d79f67374165cdd224820b7814f2270 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 11 May 2026 08:54:54 -0600 Subject: [PATCH 51/67] Update some examples. --- examples/inertia_relief/inertia_relief_example.cpp | 12 ++++++------ .../solid_mechanics/composable_solid_mechanics.cpp | 12 ++++++------ .../composable_thermo_mechanics_advanced.cpp | 12 ++++++------ 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/examples/inertia_relief/inertia_relief_example.cpp b/examples/inertia_relief/inertia_relief_example.cpp index 35f5a8e150..2099428ed7 100644 --- a/examples/inertia_relief/inertia_relief_example.cpp +++ b/examples/inertia_relief/inertia_relief_example.cpp @@ -212,18 +212,18 @@ int main(int argc, char* argv[]) ObjectiveT mass_objective("mass constraining", mesh, param_space_ptrs); - mass_objective.addBodyIntegral(mesh->entireBodyName(), - [](auto /*t_info*/, auto /*X*/, auto RHO) { return get(RHO); }); + mass_objective.addBodyIntegral(mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto /*U*/, auto RHO) { + return get(RHO); + }); double mass = mass_objective.evaluate(time_info, shape_disp.get(), objective_states); smith::tensor initial_cg; // center of gravity for (int i = 0; i < dim; ++i) { auto cg_objective = std::make_shared("translation " + std::to_string(i), mesh, param_space_ptrs); - cg_objective->addBodyIntegral(mesh->entireBodyName(), - [i](auto /*t_info*/, auto X, auto U, auto RHO) { - return (get(X)[i] + get(U)[i]) * get(RHO); - }); + cg_objective->addBodyIntegral(mesh->entireBodyName(), [i](auto /*t_info*/, auto X, auto U, auto RHO) { + return (get(X)[i] + get(U)[i]) * get(RHO); + }); initial_cg[i] = cg_objective->evaluate(time_info, shape_disp.get(), objective_states) / mass; constraints.push_back(cg_objective); diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 0befdec5c1..34853a6497 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -105,12 +105,12 @@ int main(int argc, char* argv[]) // _bc_start solid_system->setDisplacementBC(mesh->domain("left"), std::vector{0, 2}); - solid_system->addBodyForce(mesh->entireBodyName(), [](double, auto X, auto, auto, auto) { + solid_system->addBodyForce(mesh->entireBodyName(), [](double, auto X, auto, auto, auto, auto... /*args*/) { auto body_force = 0.0 * X; body_force[1] = -0.02; return body_force; }); - solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto) { + solid_system->addTraction("right", [](double, auto X, auto, auto, auto, auto, auto... /*args*/) { auto traction = 0.0 * X; traction[0] = -0.01; return traction; @@ -155,10 +155,10 @@ int main(int argc, char* argv[]) smith::FunctionalObjective> qoi("solid_dynamic_energy_proxy", mesh, smith::spaces(qoi_fields)); qoi.addBodyIntegral(mesh->entireBodyName(), [](const smith::TimeInfo&, auto, auto U, auto V) { - auto u = smith::get(U); - auto v = smith::get(V); - return 0.5 * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + 0.05 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); - }); + auto u = smith::get(U); + auto v = smith::get(V); + return 0.5 * (u[0] * u[0] + u[1] * u[1] + u[2] * u[2]) + 0.05 * (v[0] * v[0] + v[1] * v[1] + v[2] * v[2]); + }); constexpr double dt = 0.25; constexpr int num_steps = 3; diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 540d14cedd..90bcae00da 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -81,12 +81,12 @@ int main(int argc, char* argv[]) thermal_system->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); thermal_system->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); - solid_system->addTraction("right", - [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/) { - auto traction = 0.0 * X; - traction[0] = -0.005; - return traction; - }); + solid_system->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, + auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -0.005; + return traction; + }); thermal_system->addHeatSource(mesh->entireBodyName(), [](auto, auto... /*unused*/) { return 0.1; }); // _bc_end From 81fa04272b1705b8dda7c9b494998c4198605ca4 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 11 May 2026 16:30:14 -0600 Subject: [PATCH 52/67] trying to discretize all odes that are coupled into every coupled physics. --- .../coupling_params.hpp | 207 +++++++++++++----- .../differentiable_numerics/field_store.hpp | 2 + .../solid_mechanics_system.hpp | 152 ++++++++----- ...id_mechanics_with_internal_vars_system.hpp | 37 ++-- .../state_variable_system.hpp | 37 ++-- .../tests/test_combined_thermo_mechanics.cpp | 40 +++- ...st_thermo_mechanics_with_internal_vars.cpp | 14 +- .../thermal_system.hpp | 84 +++++-- .../thermo_mechanics_system.hpp | 26 +-- 9 files changed, 408 insertions(+), 191 deletions(-) diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 180e21095f..2ffa12eac6 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -19,7 +19,9 @@ #pragma once +#include #include +#include #include "smith/differentiable_numerics/field_store.hpp" #include "smith/differentiable_numerics/system_base.hpp" @@ -90,6 +92,29 @@ struct PhysicsFields { } }; +template +struct PhysicsCouplingSegment { + using time_rule_type = TimeRule; + static constexpr std::size_t num_fields = sizeof...(Spaces); + std::tuple...> fields; +}; + +template +struct ParameterCouplingSegment { + static constexpr std::size_t num_fields = sizeof...(Spaces); + std::tuple...> fields; +}; + +template +struct CouplingDescriptor; + +template +struct CouplingDescriptor, Segments...> { + static constexpr std::size_t num_coupling_fields = sizeof...(FlatSpaces); + std::tuple...> fields; + std::tuple segments; +}; + /** * @brief Register parameter fields as type-level tokens. * @@ -115,6 +140,9 @@ struct is_coupling_params_impl> : std::true_type {}; template struct is_coupling_params_impl> : std::true_type {}; +template +struct is_coupling_params_impl> : std::true_type {}; + template inline constexpr bool is_coupling_params_v = is_coupling_params_impl>::value; ///< True for `CouplingParams` and `PhysicsFields`. @@ -158,28 +186,28 @@ auto getOrCreateFieldStore(T source, std::string prefix = "", size_t storage_siz // Helpers for variadic build functions // ------------------------------------------------------------------------- -/** - * @brief selects foreign `PhysicsFields` (skip self by rule match); keeps field names. - */ template -auto collectPhysicsFromPack(const Pack& pack) +auto collectPhysicsSegmentFromPack(const Pack& pack) { if constexpr (is_physics_fields_v) { if constexpr (std::is_same_v::time_rule_type, TargetRule>) { return std::tuple{}; // skip self } else { - return pack.fields; // include coupling fields + using Rule = typename std::decay_t::time_rule_type; + return std::apply( + [](auto... fields) { + return std::make_tuple( + PhysicsCouplingSegment{std::make_tuple(fields...)}); + }, + pack.fields); } } else { return std::tuple{}; // skip non-physics packs } } -/** - * @brief selects pure `CouplingParams` (not `PhysicsFields`); rewrites names to `{prefix}param_{name}`. - */ template -auto collectParamsFromPack(const std::shared_ptr& fs, const Pack& pack) +auto collectParamSegmentFromPack(const std::shared_ptr& fs, const Pack& pack) { if constexpr (is_coupling_params_v> && !is_physics_fields_v) { return std::apply( @@ -188,7 +216,8 @@ auto collectParamsFromPack(const std::shared_ptr& fs, const Pack& pa pt.name = fs->prefix("param_" + pt.name); return pt; }; - return std::make_tuple(qualify(pts)...); + return std::make_tuple( + ParameterCouplingSegment{std::make_tuple(qualify(pts)...)}); }, pack.fields); } else { @@ -196,16 +225,23 @@ auto collectParamsFromPack(const std::shared_ptr& fs, const Pack& pa } } -/** - * @brief concatenates physics then params into a single `CouplingParams`. - */ +template +auto makeCouplingDescriptor(std::tuple segments) +{ + auto fields = std::apply([](const auto&... segment) { return std::tuple_cat(segment.fields...); }, segments); + return std::apply( + [&](auto... field) { + return CouplingDescriptor, Segments...>{fields, segments}; + }, + fields); +} + template auto collectCouplingFields(const std::shared_ptr& fs, const Packs&... packs) { - auto physics = std::tuple_cat(collectPhysicsFromPack(packs)...); - auto params = std::tuple_cat(collectParamsFromPack(fs, packs)...); - auto combined = std::tuple_cat(physics, params); - return std::apply([](auto&... all) { return CouplingParams{all...}; }, combined); + auto physics_segments = std::tuple_cat(collectPhysicsSegmentFromPack(packs)...); + auto param_segments = std::tuple_cat(collectParamSegmentFromPack(fs, packs)...); + return makeCouplingDescriptor(std::tuple_cat(physics_segments, param_segments)); } /// Register parameter fields from a CouplingParams pack (not PhysicsFields) into a FieldStore. @@ -225,50 +261,117 @@ void registerParamsIfNeeded(std::shared_ptr fs, const Pack& pack) } } -/// @brief Build the weak-form parameter list for a time rule and coupling pack. -/// -/// Unpacks Coupling (either CouplingParams or PhysicsFields) and produces -/// TimeRuleParams, i.e. num_states copies of Space followed by the coupling field types. -template -struct TimeRuleParamsHelper; +template +decltype(auto) applyTimeRuleToPrefixImpl(const Rule& rule, const TimeInfoT& t_info, const ArgsTuple& raw_args, + Callback&& callback, std::index_sequence, + std::index_sequence) +{ + auto interpolated = rule.interpolate(t_info, std::get(raw_args)...); + return std::apply( + [&](auto&&... values) -> decltype(auto) { + return std::forward(callback)(std::forward(values)..., + std::get(raw_args)...); + }, + interpolated); +} + +/// @brief Interpolate the leading Rule::num_states arguments, then pass interpolated values and the raw tail to +/// callback. +template +decltype(auto) applyTimeRuleToPrefix(const Rule& rule, const TimeInfoT& t_info, Callback&& callback, + const RawArgs&... raw_args) +{ + static_assert(sizeof...(RawArgs) >= Rule::num_states, "Not enough raw arguments for time-rule interpolation"); + auto raw_tuple = std::forward_as_tuple(raw_args...); + constexpr std::size_t tail_count = sizeof...(RawArgs) - Rule::num_states; + return applyTimeRuleToPrefixImpl(rule, t_info, raw_tuple, std::forward(callback), + std::make_index_sequence{}, + std::make_index_sequence{}); +} + +template +auto evaluateCouplingSegment(const PhysicsCouplingSegment& /*segment*/, const TimeInfoT& t_info, + const RawTuple& raw_args, std::index_sequence) +{ + Rule rule; + return rule.interpolate(t_info, std::get(raw_args)...); +} + +template +auto evaluateCouplingSegment(const ParameterCouplingSegment& /*segment*/, const TimeInfoT& /*t_info*/, + const RawTuple& raw_args, std::index_sequence) +{ + return std::forward_as_tuple(std::get(raw_args)...); +} + +template +auto evaluateCouplingSegments(const SegmentsTuple& segments, const TimeInfoT& t_info, const RawTuple& raw_args) +{ + if constexpr (I == std::tuple_size_v>) { + return std::tuple{}; + } else { + const auto& segment = std::get(segments); + using Segment = std::decay_t; + auto head = + evaluateCouplingSegment(segment, t_info, raw_args, std::make_index_sequence{}); + auto tail = evaluateCouplingSegments(segments, t_info, raw_args); + return std::tuple_cat(head, tail); + } +} + +template +decltype(auto) applyCouplingTimeRules(const Coupling& coupling, const TimeInfoT& t_info, Callback&& callback, + const RawArgs&... raw_args) +{ + auto raw_tuple = std::forward_as_tuple(raw_args...); + auto interpolated_tail = evaluateCouplingSegments<0, 0>(coupling.segments, t_info, raw_tuple); + return std::apply(std::forward(callback), interpolated_tail); +} + +/// @brief Centralized type access for field-pack spaces. +template +struct CouplingSpaces; + +template +struct CouplingSpaces> { + template + using time_rule_params = smith::TimeRuleParams; -/// @brief Maps `CouplingParams` packs to weak-form time-rule parameters. -template -struct TimeRuleParamsHelper> { - using type = smith::TimeRuleParams; ///< Resolved weak-form time-rule parameter list. + template + using append_to_parameters = Parameters; }; -/// @brief Maps `PhysicsFields` packs to weak-form time-rule parameters. -template -struct TimeRuleParamsHelper> { - using type = smith::TimeRuleParams; ///< Resolved weak-form time-rule parameter list. +template +struct CouplingSpaces> { + template + using time_rule_params = smith::TimeRuleParams; + + template + using append_to_parameters = Parameters; }; -/// @brief Convenience alias selecting time-rule parameters for coupling pack type. +template +struct CouplingSpaces, Segments...>> { + template + using time_rule_params = smith::TimeRuleParams; + + template + using append_to_parameters = Parameters; +}; + +/// @brief Weak-form parameters for a self time rule followed by the coupling field spaces. template -using TimeRuleParams = typename TimeRuleParamsHelper::type; +using TimeRuleParams = typename CouplingSpaces>::template time_rule_params; -/** - * @brief Append coupling spaces (CS...) and Tail... onto a base Parameters type. - * - * Produces Parameters. - * Used for weak form types whose leading fields are hardcoded (cycle-zero, stress output). - */ -template +/// @brief Append coupling spaces onto a base Parameters type. +template struct AppendCouplingToParams; -template -/// @brief Specialization appending `CouplingParams` field spaces after fixed weak-form parameters. -struct AppendCouplingToParams, Parameters, Tail...> { - using type = - Parameters; ///< Base parameter list extended with coupling and trailing parameters. -}; - -template -/// @brief Specialization appending `PhysicsFields` field spaces after fixed weak-form parameters. -struct AppendCouplingToParams, Parameters, Tail...> { - using type = - Parameters; ///< Base parameter list extended with coupling and trailing parameters. +template +struct AppendCouplingToParams> { + using type = typename CouplingSpaces>::template append_to_parameters; }; } // namespace detail diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index ac100c9a27..52d4d1f5d6 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -45,6 +45,8 @@ struct ReactionInfo { */ template struct FieldType { + using space_type = Space; + /** * @brief Construct a new FieldType object. * @param n Name of the field. diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 7ab5ec021b..b67a1a219a 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -63,6 +63,7 @@ struct SolidMechanicsSystem : public SystemBase { std::shared_ptr solid_weak_form; ///< Solid mechanics weak form. std::shared_ptr disp_bc; ///< Displacement boundary conditions. std::shared_ptr disp_time_rule; ///< Time integration rule. + std::shared_ptr coupling; ///< Coupling metadata for callback interpolation. std::shared_ptr stress_weak_form; ///< Stress projection weak form (nullptr if disabled). std::shared_ptr stress_output_system; ///< Post-solve system for stress projection. @@ -78,15 +79,24 @@ struct SolidMechanicsSystem : public SystemBase { void setMaterial(const MaterialType& material, const std::string& domain_name) { auto captured_rule = disp_time_rule; - solid_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto u, auto u_old, auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); - - typename MaterialType::State state; - auto pk_stress = material(t_info, state, get(u_current), get(v_current), params...); - - return smith::tuple{get(a_current) * material.density, pk_stress}; - }); + auto captured_coupling = coupling; + solid_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto u_current, auto v_current, auto a_current, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + typename MaterialType::State state; + auto pk_stress = material(t_info, state, get(u_current), get(v_current), + interpolated_params...); + + return smith::tuple{get(a_current) * material.density, pk_stress}; + }, + params...); + }, + raw_args...); + }); // Stress output projection: L2 projection of PK1 stress onto an L2 piecewise-constant field. // Residual: ∫ test · (stress_unknown - pk_stress(u)) dx = 0. @@ -95,25 +105,32 @@ struct SolidMechanicsSystem : public SystemBase { // becomes the RHS. if (stress_weak_form) { bool do_cauchy = this->output_cauchy_stress; - stress_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto stress, auto u, auto u_old, - auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); - - typename MaterialType::State state; - auto pk_stress = material(t_info, state, get(u_current), get(v_current), params...); - - // Flatten the chosen stress into a dim*dim vector and subtract from the unknown. - auto flat_stress = [&]() { - if (do_cauchy) { - static constexpr auto I_ = Identity(); - auto F = get(u_current) + I_; - auto J = det(F); - auto sigma = (1.0 / J) * dot(pk_stress, transpose(F)); - return make_tensor([&](int i) { return sigma[i / dim][i % dim]; }); - } - return make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); - }(); - return smith::tuple{get(stress) - flat_stress, tensor{}}; + stress_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto stress, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto u_current, auto v_current, auto /*a_current*/, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + typename MaterialType::State state; + auto pk_stress = material(t_info, state, get(u_current), get(v_current), + interpolated_params...); + + auto flat_stress = [&]() { + if (do_cauchy) { + static constexpr auto I_ = Identity(); + auto F = get(u_current) + I_; + auto J = det(F); + auto sigma = (1.0 / J) * dot(pk_stress, transpose(F)); + return make_tensor([&](int i) { return sigma[i / dim][i % dim]; }); + } + return make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); + }(); + return smith::tuple{get(stress) - flat_stress, tensor{}}; + }, + params...); + }, + raw_args...); }); } } @@ -128,11 +145,20 @@ struct SolidMechanicsSystem : public SystemBase { void addBodyForce(const std::string& domain_name, BodyForceType force_function) { auto captured_rule = disp_time_rule; - solid_weak_form->addBodySource( - domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); - return force_function(t_info.time(), X, u_current, v_current, a_current, params...); - }); + auto captured_coupling = coupling; + solid_weak_form->addBodySource(domain_name, [=](auto t_info, auto X, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto u_current, auto v_current, auto a_current, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + return force_function(t_info.time(), X, u_current, v_current, a_current, interpolated_params...); + }, + params...); + }, + raw_args...); + }); } /** @@ -145,11 +171,21 @@ struct SolidMechanicsSystem : public SystemBase { void addTraction(const std::string& domain_name, TractionType traction_function) { auto captured_rule = disp_time_rule; - solid_weak_form->addBoundaryFlux( - domain_name, [=](auto t_info, auto X, auto n, auto u, auto u_old, auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_rule->interpolate(t_info, u, u_old, v_old, a_old); - return traction_function(t_info.time(), X, n, u_current, v_current, a_current, params...); - }); + auto captured_coupling = coupling; + solid_weak_form->addBoundaryFlux(domain_name, [=](auto t_info, auto X, auto n, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto u_current, auto v_current, auto a_current, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + return traction_function(t_info.time(), X, n, u_current, v_current, a_current, + interpolated_params...); + }, + params...); + }, + raw_args...); + }); } /** @@ -162,18 +198,26 @@ struct SolidMechanicsSystem : public SystemBase { void addPressure(const std::string& domain_name, PressureType pressure_function) { auto captured_rule = disp_time_rule; - solid_weak_form->addBoundaryIntegral( - domain_name, [=](auto t_info, auto X, auto u, auto u_old, auto v_old, auto a_old, auto... params) { - auto u_current = captured_rule->value(t_info, u, u_old, v_old, a_old); - - auto x_current = X + u_current; - auto n_deformed = cross(get(x_current)); - auto n_shape_norm = norm(cross(get(X))); - - auto pressure = pressure_function(t_info.time(), get(X), get(params)...); - - return pressure * n_deformed * (1.0 / n_shape_norm); - }); + auto captured_coupling = coupling; + solid_weak_form->addBoundaryIntegral(domain_name, [=](auto t_info, auto X, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto u_current, auto /*v_current*/, auto /*a_current*/, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + auto x_current = X + u_current; + auto n_deformed = cross(get(x_current)); + auto n_shape_norm = norm(cross(get(X))); + + auto pressure = pressure_function(t_info.time(), get(X), get(interpolated_params)...); + + return pressure * n_deformed * (1.0 / n_shape_norm); + }, + params...); + }, + raw_args...); + }); } /// Set zero-displacement Dirichlet BC on all components. @@ -308,6 +352,7 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); sys->disp_bc = disp_bc; sys->disp_time_rule = disp_time_rule_ptr; + sys->coupling = std::make_shared(coupling); sys->solid_weak_form = solid_weak_form; sys->output_cauchy_stress = options.output_cauchy_stress; @@ -318,11 +363,12 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons auto cycle_zero_system = makeSystem(field_store, cycle_zero_solver, {sys->solid_weak_form}); cycle_zero_system->solve_result_field_names = {accel_old_type.name}; - auto cycle_zero_inputs = std::vector{disp_old_type.name, disp_old_type.name, velo_old_type.name, - accel_old_type.name}; + auto cycle_zero_inputs = + std::vector{disp_old_type.name, disp_old_type.name, velo_old_type.name, accel_old_type.name}; auto append_if_state = [&](const auto& field) { const auto& states = field_store->getStateFields(); - if (std::any_of(states.begin(), states.end(), [&](const auto& state) { return state.get()->name() == field.name; })) { + if (std::any_of(states.begin(), states.end(), + [&](const auto& state) { return state.get()->name() == field.name; })) { cycle_zero_inputs.push_back(field.name); } }; diff --git a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp index acb020815b..68d2a82d81 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_with_internal_vars_system.hpp @@ -40,24 +40,22 @@ auto evaluateSolidInternalVariableMaterial(const MaterialType& material, const T return material(t_info, state, grad_u, grad_v, alpha, std::forward(params)...); } -template +template /// @brief Adapts coupled internal-variable solids to solid-system material interface. struct CoupledSolidInternalVariableMaterialAdapter { /// Material state type forwarded to solid system. using State = typename MaterialType::State; - MaterialType material; ///< Wrapped constitutive model. - InternalVariableRulePtr internal_variable_time_rule; ///< Time rule used to recover current internal variable. - double density; ///< Material density exposed for solid residual. + MaterialType material; ///< Wrapped constitutive model. + double density; ///< Material density exposed for solid residual. - template /// @brief Evaluate wrapped material with current internal-variable value. auto operator()(const TimeInfo& t_info, StateType& state, GradUType grad_u, GradVType grad_v, AlphaType alpha, - AlphaOldType alpha_old, ParamTypes&&... params) const + AlphaDotType /*alpha_dot*/, ParamTypes&&... params) const { - auto alpha_current = internal_variable_time_rule->value(t_info, alpha, alpha_old); - return evaluateSolidInternalVariableMaterial(material, t_info, state, grad_u, grad_v, get(alpha_current), + return evaluateSolidInternalVariableMaterial(material, t_info, state, grad_u, grad_v, get(alpha), std::forward(params)...); } }; @@ -81,13 +79,10 @@ template void setCoupledSolidMechanicsInternalVariableMaterial( std::shared_ptr> solid, - std::shared_ptr> internal_variables, + std::shared_ptr> /*internal_variables*/, const MaterialType& material, const std::string& domain_name) { - auto captured_internal_variable_rule = internal_variables->internal_variable_time_rule; - auto solid_material = - detail::CoupledSolidInternalVariableMaterialAdapter{ - material, captured_internal_variable_rule, material.density}; + auto solid_material = detail::CoupledSolidInternalVariableMaterialAdapter{material, material.density}; solid->setMaterial(solid_material, domain_name); } @@ -104,16 +99,14 @@ template void setCoupledInternalVariableMaterial( std::shared_ptr> internal_variables, - std::shared_ptr> solid, const MaterialType& material, - const std::string& domain_name) + std::shared_ptr> /*solid*/, + const MaterialType& material, const std::string& domain_name) { - auto captured_disp_rule = solid->disp_time_rule; - internal_variables->addEvolution(domain_name, [=](auto t_info, auto alpha, auto alpha_dot, auto u, auto u_old, - auto v_old, auto a_old, auto... params) { - auto [u_current, v_current, a_current] = captured_disp_rule->interpolate(t_info, u, u_old, v_old, a_old); - return detail::evaluateCoupledInternalVariableMaterial(material, t_info, alpha, alpha_dot, - get(u_current), params...); - }); + internal_variables->addEvolution( + domain_name, [=](auto t_info, auto alpha, auto alpha_dot, auto u, auto /*v*/, auto /*a*/, auto... params) { + return detail::evaluateCoupledInternalVariableMaterial(material, t_info, alpha, alpha_dot, get(u), + params...); + }); } template , H1, H1, H1> - * (the solid displacement coupling fields), the user accesses the displacement gradient via - * get(u_solve_state_arg) as the first coupling field argument. + * With solid displacement coupling, the callback receives `(u, v, a)` rather than raw + * `(u_solve_state, u_old, v_old, a_old)`. */ #pragma once @@ -70,6 +69,7 @@ struct InternalVariableSystem : public SystemBase { std::shared_ptr internal_variable_weak_form; ///< Internal variable weak form. std::shared_ptr internal_variable_bc; ///< Internal variable BCs. std::shared_ptr internal_variable_time_rule; ///< Time integration rule. + std::shared_ptr coupling; ///< Coupling metadata. /** * @brief Register an ODE evolution law for the internal variable. @@ -78,9 +78,6 @@ struct InternalVariableSystem : public SystemBase { * evolution_law(t_info, alpha_val, alpha_dot, coupling_fields..., params...) * and must return a scalar residual (zero when the ODE is satisfied). * - * When Coupling carries solid displacement fields (u_ss, u, v, a), access the - * displacement gradient via get(u_ss_arg) on the first coupling field. - * * @param domain_name Domain to apply the evolution on. * @param evolution_law Callable returning the ODE residual. */ @@ -88,14 +85,23 @@ struct InternalVariableSystem : public SystemBase { void addEvolution(const std::string& domain_name, EvolutionType evolution_law) { auto captured_time_rule = internal_variable_time_rule; - internal_variable_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto alpha, auto alpha_old, auto... coupling_and_params) { - auto [alpha_current, alpha_dot] = captured_time_rule->interpolate(t_info, alpha, alpha_old); - auto residual_val = - evolution_law(t_info, get(alpha_current), get(alpha_dot), coupling_and_params...); - tensor flux{}; - return smith::tuple{residual_val, flux}; - }); + auto captured_coupling = coupling; + internal_variable_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_time_rule, t_info, + [&](auto alpha_current, auto alpha_dot, auto... coupling_and_params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + auto residual_val = + evolution_law(t_info, get(alpha_current), get(alpha_dot), interpolated_params...); + tensor flux{}; + return smith::tuple{residual_val, flux}; + }, + coupling_and_params...); + }, + raw_args...); + }); } }; @@ -175,6 +181,7 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co std::vector>{internal_variable_weak_form}); sys->internal_variable_bc = internal_variable_bc; sys->internal_variable_time_rule = internal_variable_time_rule; + sys->coupling = std::make_shared(coupling); sys->internal_variable_weak_form = internal_variable_weak_form; return sys; diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 4607a28aab..8d2573828d 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -39,6 +39,41 @@ static constexpr int temperature_order = 1; using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using TempRule = BackwardEulerFirstOrderTimeIntegrationRule; +TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) +{ + axom::sidre::DataStore datastore; + smith::StateManager::initialize(datastore, "dcoupling_interpolation"); + auto mesh = + std::make_shared(mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON), "mesh", 0, 0); + auto field_store = std::make_shared(mesh, 100, ""); + auto solid_fields = registerSolidMechanicsFields(field_store); + auto thermal_fields = registerThermalFields(field_store); + auto scale_params = registerParameterFields(FieldType>("scale")); + + auto solid_coupling = detail::collectCouplingFields(field_store, thermal_fields, scale_params); + auto saw_thermal_values = detail::applyCouplingTimeRules( + solid_coupling, TimeInfo(0.0, 2.0, 0), + [](auto temperature, auto temperature_dot, auto scale) { + EXPECT_DOUBLE_EQ(temperature, 7.0); + EXPECT_DOUBLE_EQ(temperature_dot, 3.0); + EXPECT_DOUBLE_EQ(scale, 11.0); + return true; + }, + 7.0, 1.0, 11.0); + EXPECT_TRUE(saw_thermal_values); + + auto thermal_coupling = detail::collectCouplingFields(field_store, solid_fields); + auto saw_solid_values = detail::applyCouplingTimeRules( + thermal_coupling, TimeInfo(0.0, 2.0, 0), + [](auto displacement, auto velocity, auto /*acceleration*/) { + EXPECT_DOUBLE_EQ(displacement, 10.0); + EXPECT_DOUBLE_EQ(velocity, 3.0); + return true; + }, + 10.0, 4.0, 0.0, 0.0); + EXPECT_TRUE(saw_solid_values); +} + struct ThermoMechanicsMeshFixture : public testing::Test { void SetUp() { @@ -414,9 +449,8 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesCycleZeroSystems) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields, - thermal_fields); + auto solid_system = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, thermal_fields); auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, solid_fields); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 5d1b30cd53..03d4773b81 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -177,13 +177,13 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // Compressive traction on right face. // Lambda args from addTraction: (t, X, n, u, v, a, temp_ss, temp_old, alpha_ss, alpha_old) - solid_system->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, - auto /*temp_ss*/, auto /*temp_old*/, auto /*alpha_ss*/, auto /*alpha_old*/, - auto... /*args*/) { - auto t = 0.0 * X; - t[0] = -0.005; - return t; - }); + solid_system->addTraction( + "right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto /*temp_ss*/, auto /*temp_old*/, + auto /*alpha_ss*/, auto /*alpha_old*/, auto... /*args*/) { + auto t = 0.0 * X; + t[0] = -0.005; + return t; + }); // Phase 5: combine and solve. auto coupled_system = combineSystems(solid_system, thermal_system, internal_variable_system); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 710a4ed885..e3c4200a7c 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -52,6 +52,7 @@ struct ThermalSystem : public SystemBase { std::shared_ptr thermal_weak_form; ///< Thermal weak form. std::shared_ptr temperature_bc; ///< Temperature boundary conditions. std::shared_ptr temperature_time_rule; ///< Time integration for temperature. + std::shared_ptr coupling; ///< Coupling metadata for callback interpolation. /** * @brief Set the thermal material model for a domain. @@ -70,12 +71,22 @@ struct ThermalSystem : public SystemBase { void setMaterial(const MaterialType& material, const std::string& domain_name) { auto captured_temp_rule = temperature_time_rule; - - thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto temperature, auto temperature_old, - auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - auto [heat_capacity, heat_flux] = material(t_info, get(T_current), get(T_current), params...); - return smith::tuple{heat_capacity * get(T_dot), -heat_flux}; + auto captured_coupling = coupling; + + thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_temp_rule, t_info, + [&](auto T_current, auto T_dot, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + auto [heat_capacity, heat_flux] = + material(t_info, get(T_current), get(T_current), interpolated_params...); + return smith::tuple{heat_capacity * get(T_dot), -heat_flux}; + }, + params...); + }, + raw_args...); }); } @@ -97,14 +108,23 @@ struct ThermalSystem : public SystemBase { void setMaterialAndHeatSource(const MaterialType& material, const std::string& domain_name) { auto captured_temp_rule = temperature_time_rule; - - thermal_weak_form->addBodyIntegral( - domain_name, [=](auto t_info, auto /*X*/, auto temperature, auto temperature_old, auto... params) { - auto [T_current, T_dot] = captured_temp_rule->interpolate(t_info, temperature, temperature_old); - auto [heat_capacity, heat_flux, heat_source] = - material(t_info, get(T_current), get(T_current), params...); - return smith::tuple{heat_capacity * get(T_dot) - heat_source, -heat_flux}; - }); + auto captured_coupling = coupling; + + thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_temp_rule, t_info, + [&](auto T_current, auto T_dot, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + auto [heat_capacity, heat_flux, heat_source] = + material(t_info, get(T_current), get(T_current), interpolated_params...); + return smith::tuple{heat_capacity * get(T_dot) - heat_source, -heat_flux}; + }, + params...); + }, + raw_args...); + }); } /** @@ -116,9 +136,19 @@ struct ThermalSystem : public SystemBase { void addHeatSource(const std::string& domain_name, HeatSourceType source_function) { auto captured_rule = temperature_time_rule; - thermal_weak_form->addBodySource(domain_name, [=](auto t_info, auto X, auto T, auto T_old, auto... params) { - auto T_current = captured_rule->value(t_info, T, T_old); - return source_function(t_info.time(), X, T_current, params...); + auto captured_coupling = coupling; + thermal_weak_form->addBodySource(domain_name, [=](auto t_info, auto X, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto T_current, auto /*T_dot*/, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + return source_function(t_info.time(), X, T_current, interpolated_params...); + }, + params...); + }, + raw_args...); }); } @@ -131,11 +161,20 @@ struct ThermalSystem : public SystemBase { void addHeatFlux(const std::string& boundary_name, HeatFluxType flux_function) { auto captured_rule = temperature_time_rule; - thermal_weak_form->addBoundaryFlux(boundary_name, - [=](auto t_info, auto X, auto n, auto T, auto T_old, auto... params) { - auto T_current = captured_rule->value(t_info, T, T_old); - return flux_function(t_info.time(), X, n, T_current, params...); - }); + auto captured_coupling = coupling; + thermal_weak_form->addBoundaryFlux(boundary_name, [=](auto t_info, auto X, auto n, auto... raw_args) { + return detail::applyTimeRuleToPrefix( + *captured_rule, t_info, + [&](auto T_current, auto /*T_dot*/, auto... params) { + return detail::applyCouplingTimeRules( + *captured_coupling, t_info, + [&](auto... interpolated_params) { + return flux_function(t_info.time(), X, n, T_current, interpolated_params...); + }, + params...); + }, + raw_args...); + }); } /// Set zero-temperature Dirichlet BC. @@ -209,6 +248,7 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl std::make_shared(field_store, solver, std::vector>{thermal_weak_form}); sys->temperature_bc = temperature_bc; sys->temperature_time_rule = temperature_time_rule_ptr; + sys->coupling = std::make_shared(coupling); sys->thermal_weak_form = thermal_weak_form; return sys; diff --git a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp index 1dcf985587..a398a0c751 100644 --- a/src/smith/differentiable_numerics/thermo_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/thermo_mechanics_system.hpp @@ -30,26 +30,24 @@ auto evaluateCoupledThermoMechanicsMaterial(const MaterialType& material, const return material(t_info, state, grad_u, grad_v, theta, grad_theta, std::forward(params)...); } -template +template /// @brief Adapts coupled thermo-mechanical material to solid-system material interface. struct CoupledSolidThermoMechanicsMaterialAdapter { /// Material state type forwarded to solid system. using State = typename MaterialType::State; - MaterialType material; ///< Wrapped thermo-mechanical material. - TemperatureRulePtr temperature_time_rule; ///< Time rule used to recover current temperature value. - double density; ///< Material density exposed for solid residual. + MaterialType material; ///< Wrapped thermo-mechanical material. + double density; ///< Material density exposed for solid residual. template + typename TemperatureDotType, typename... ParamTypes> /// @brief Evaluate wrapped material and return solid PK1 contribution. auto operator()(const TimeInfo& t_info, StateType& state, GradUType grad_u, GradVType grad_v, - TemperatureType temperature, TemperatureOldType temperature_old, ParamTypes&&... params) const + TemperatureType temperature, TemperatureDotType /*temperature_dot*/, ParamTypes&&... params) const { - auto T = temperature_time_rule->value(t_info, temperature, temperature_old); auto [pk, C_v, s0, q0] = - evaluateCoupledThermoMechanicsMaterial(material, t_info, state, grad_u, grad_v, get(T), - get(T), std::forward(params)...); + evaluateCoupledThermoMechanicsMaterial(material, t_info, state, grad_u, grad_v, get(temperature), + get(temperature), std::forward(params)...); return pk; } }; @@ -89,18 +87,12 @@ void setCoupledThermoMechanicsMaterial( std::shared_ptr> thermal, const MaterialType& material, const std::string& domain_name) { - auto captured_disp_rule = solid->disp_time_rule; - auto captured_temp_rule = thermal->temperature_time_rule; - - auto solid_material = detail::CoupledSolidThermoMechanicsMaterialAdapter{ - material, captured_temp_rule, material.density}; + auto solid_material = detail::CoupledSolidThermoMechanicsMaterialAdapter{material, material.density}; solid->setMaterial(solid_material, domain_name); thermal->setMaterialAndHeatSource( - [=](const TimeInfo& t_info, auto temperature, auto grad_temperature, auto disp, auto disp_old, auto v_old, - auto a_old, auto... params) { - auto [u, v, a] = captured_disp_rule->interpolate(t_info, disp, disp_old, v_old, a_old); + [=](const TimeInfo& t_info, auto temperature, auto grad_temperature, auto u, auto v, auto /*a*/, auto... params) { typename MaterialType::State state{}; auto [pk, C_v, s0, q0] = detail::evaluateCoupledThermoMechanicsMaterial( material, t_info, state, get(u), get(v), temperature, grad_temperature, params...); From 6e842954900f2d0f048a17e07f85fa567e47d931 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 11 May 2026 17:26:16 -0600 Subject: [PATCH 53/67] Minor changes. --- examples/inertia_relief/inertia_relief_example.cpp | 5 ++--- .../composable_thermo_mechanics_advanced.cpp | 12 ++++++------ src/smith/physics/common.hpp | 6 +++++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/examples/inertia_relief/inertia_relief_example.cpp b/examples/inertia_relief/inertia_relief_example.cpp index 2099428ed7..dbfc4527f0 100644 --- a/examples/inertia_relief/inertia_relief_example.cpp +++ b/examples/inertia_relief/inertia_relief_example.cpp @@ -212,9 +212,8 @@ int main(int argc, char* argv[]) ObjectiveT mass_objective("mass constraining", mesh, param_space_ptrs); - mass_objective.addBodyIntegral(mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto /*U*/, auto RHO) { - return get(RHO); - }); + mass_objective.addBodyIntegral( + mesh->entireBodyName(), [](auto /*t_info*/, auto /*X*/, auto /*U*/, auto RHO) { return get(RHO); }); double mass = mass_objective.evaluate(time_info, shape_disp.get(), objective_states); smith::tensor initial_cg; // center of gravity diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 90bcae00da..8bc82bb342 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -81,12 +81,12 @@ int main(int argc, char* argv[]) thermal_system->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); thermal_system->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); - solid_system->addTraction("right", [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, - auto... /*args*/) { - auto traction = 0.0 * X; - traction[0] = -0.005; - return traction; - }); + solid_system->addTraction("right", + [](double, auto X, auto /*n*/, auto /*u*/, auto /*v*/, auto /*a*/, auto... /*args*/) { + auto traction = 0.0 * X; + traction[0] = -0.005; + return traction; + }); thermal_system->addHeatSource(mesh->entireBodyName(), [](auto, auto... /*unused*/) { return 0.1; }); // _bc_end diff --git a/src/smith/physics/common.hpp b/src/smith/physics/common.hpp index caa602fc5a..49b6ba3848 100644 --- a/src/smith/physics/common.hpp +++ b/src/smith/physics/common.hpp @@ -16,7 +16,11 @@ namespace smith { /// @brief struct storing time and timestep information struct TimeInfo { - enum class EvaluationMode { Regular, CycleZero }; + enum class EvaluationMode + { + Regular, + CycleZero + }; /// @brief constructor TimeInfo(double t, double t_step, size_t c = 0, EvaluationMode mode = EvaluationMode::Regular) From 640626cf2601dd12ab647b078fb6ef89390264d5 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 11 May 2026 23:36:53 -0600 Subject: [PATCH 54/67] Put back depends on. --- examples/contact/beam_bending.cpp | 2 +- examples/contact/ironing.cpp | 2 +- src/smith/physics/heat_transfer.hpp | 2 +- src/smith/physics/solid_mechanics.hpp | 8 ++++---- src/smith/physics/tests/dynamic_solid_adjoint.cpp | 2 +- src/smith/physics/tests/dynamic_thermal_adjoint.cpp | 4 ++-- src/smith/physics/tests/lce_Bertoldi_lattice.cpp | 2 +- src/smith/physics/tests/lce_Brighenti_tensile.cpp | 2 +- src/smith/physics/tests/parameterized_thermal.cpp | 2 +- .../tests/parameterized_thermomechanics_example.cpp | 2 +- src/smith/physics/tests/solid.cpp | 8 +++++--- src/smith/physics/tests/solid_finite_diff.cpp | 2 +- src/smith/physics/tests/solid_periodic.cpp | 2 +- src/smith/physics/tests/solid_reaction_adjoint.cpp | 2 +- src/smith/physics/tests/thermal_finite_diff.cpp | 2 +- src/smith/physics/tests/thermal_nonlinear_solve.cpp | 2 +- src/smith/physics/tests/thermal_robin_condition.cpp | 3 ++- src/smith/physics/thermomechanics.hpp | 8 +++++--- src/smith/physics/thermomechanics_monolithic.hpp | 4 ++-- 19 files changed, 33 insertions(+), 28 deletions(-) diff --git a/examples/contact/beam_bending.cpp b/examples/contact/beam_bending.cpp index 558a439e97..79f268ec89 100644 --- a/examples/contact/beam_bending.cpp +++ b/examples/contact/beam_bending.cpp @@ -77,7 +77,7 @@ int main(int argc, char* argv[]) solid_solver.setParameter(1, G_field); smith::solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(mat, mesh->entireBody()); + solid_solver.setMaterial(smith::DependsOn<0, 1>{}, mat, mesh->entireBody()); // Pass the BC information to the solver object solid_solver.setFixedBCs(mesh->domain("support")); diff --git a/examples/contact/ironing.cpp b/examples/contact/ironing.cpp index 7e3261b3fc..793d4d2751 100644 --- a/examples/contact/ironing.cpp +++ b/examples/contact/ironing.cpp @@ -73,7 +73,7 @@ int main(int argc, char* argv[]) solid_solver.setParameter(1, G_field); smith::solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(mat, mesh->entireBody()); + solid_solver.setMaterial(smith::DependsOn<0, 1>{}, mat, mesh->entireBody()); // Pass the BC information to the solver object mesh->addDomainOfBoundaryElements("bottom_of_subtrate", smith::by_attr(5)); diff --git a/src/smith/physics/heat_transfer.hpp b/src/smith/physics/heat_transfer.hpp index 56984f98ae..29c1870138 100644 --- a/src/smith/physics/heat_transfer.hpp +++ b/src/smith/physics/heat_transfer.hpp @@ -460,7 +460,7 @@ class HeatTransfer, std::integer_sequ template void setMaterial(const MaterialType& material, Domain& domain) { - setMaterial(material, domain); + setMaterial(DependsOn<>{}, material, domain); } /** diff --git a/src/smith/physics/solid_mechanics.hpp b/src/smith/physics/solid_mechanics.hpp index e0ed49cee7..f6867ee72b 100644 --- a/src/smith/physics/solid_mechanics.hpp +++ b/src/smith/physics/solid_mechanics.hpp @@ -526,7 +526,7 @@ class SolidMechanics, std::integer_se * boundary is used. * ~~~ {.cpp} * - * solid_mechanics.addCustomBoundaryIntegral([](double t, auto position, auto displacement, auto + * solid_mechanics.addCustomBoundaryIntegral(DependsOn<>{}, [](double t, auto position, auto displacement, auto * acceleration, auto shape){ auto [X, dX_dxi] = position; * * auto [u, du_dxi] = displacement; @@ -628,7 +628,7 @@ class SolidMechanics, std::integer_se * * double lambda = 500.0; * double mu = 500.0; - * solid_mechanics.addCustomDomainIntegral([=](auto x, auto displacement, auto acceleration, auto + * solid_mechanics.addCustomDomainIntegral(DependsOn<>{}, [=](auto x, auto displacement, auto acceleration, auto * shape_displacement){ auto du_dx = smith::get<1>(displacement); * * auto I = Identity(); @@ -740,7 +740,7 @@ class SolidMechanics, std::integer_se void setMaterial(const MaterialType& material, Domain& domain, std::shared_ptr> qdata = EmptyQData) { - setMaterial(material, domain, qdata); + setMaterial(DependsOn<>{}, material, domain, qdata); } /** @@ -923,7 +923,7 @@ class SolidMechanics, std::integer_se template void addBodyForce(BodyForceType body_force, Domain& domain) { - addBodyForce(body_force, domain); + addBodyForce(DependsOn<>{}, body_force, domain); } /** diff --git a/src/smith/physics/tests/dynamic_solid_adjoint.cpp b/src/smith/physics/tests/dynamic_solid_adjoint.cpp index 26936b5b49..50e19abbfd 100644 --- a/src/smith/physics/tests/dynamic_solid_adjoint.cpp +++ b/src/smith/physics/tests/dynamic_solid_adjoint.cpp @@ -505,7 +505,7 @@ struct BucklingSensitivityFixture : public ::testing::Test { nonlinear_opts, linear_opts, dyn_opts, physics_prefix + std::to_string(iter++), mesh, std::vector{kname, gname}, 0, 0.0, checkpoint_to_disk, false); - solid->setMaterial(mat, mesh->entireBody()); + solid->setMaterial(smith::DependsOn<0, 1>{}, mat, mesh->entireBody()); return solid; } }; diff --git a/src/smith/physics/tests/dynamic_thermal_adjoint.cpp b/src/smith/physics/tests/dynamic_thermal_adjoint.cpp index ae88e75e3e..4eb24916b6 100644 --- a/src/smith/physics/tests/dynamic_thermal_adjoint.cpp +++ b/src/smith/physics/tests/dynamic_thermal_adjoint.cpp @@ -93,7 +93,7 @@ std::unique_ptr createParameterizedHeatTransfer( FiniteElementState user_defined_conductivity(mesh->mfemParMesh(), H1

{}, "user_defined_conductivity"); user_defined_conductivity = 1.1; thermal->setParameter(0, user_defined_conductivity); - thermal->setMaterial(mat, mesh->entireBody()); + thermal->setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, mesh->entireBody()); thermal->setTemperature([](const mfem::Vector&, double) { return 0.0; }); @@ -117,7 +117,7 @@ std::unique_ptr createParameterizedNonlinearHeatTrans user_defined_conductivity = 1.1; thermal->setParameter(0, user_defined_conductivity); - thermal->setMaterial(mat, mesh->entireBody()); + thermal->setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); thermal->setSource([](auto /* X */, auto /* time */, auto /* u */, auto /* du_dx */) { return 1.0; }, mesh->entireBody()); diff --git a/src/smith/physics/tests/lce_Bertoldi_lattice.cpp b/src/smith/physics/tests/lce_Bertoldi_lattice.cpp index 9570d597d3..d4ee2268e8 100644 --- a/src/smith/physics/tests/lce_Bertoldi_lattice.cpp +++ b/src/smith/physics/tests/lce_Bertoldi_lattice.cpp @@ -120,7 +120,7 @@ TEST(LiquidCrystalElastomer, Bertoldi) // Set material LiquidCrystalElastomerBertoldi lceMat(density, young_modulus, possion_ratio, max_order_param, beta_param); - solid_solver.setMaterial(lceMat, mesh->entireBody()); + solid_solver.setMaterial(DependsOn{}, lceMat, mesh->entireBody()); // Boundary conditions: // Prescribe zero displacement at the supported end of the beam diff --git a/src/smith/physics/tests/lce_Brighenti_tensile.cpp b/src/smith/physics/tests/lce_Brighenti_tensile.cpp index 402b2dcb75..a97d3c6d40 100644 --- a/src/smith/physics/tests/lce_Brighenti_tensile.cpp +++ b/src/smith/physics/tests/lce_Brighenti_tensile.cpp @@ -133,7 +133,7 @@ TEST(LiquidCrystalElastomer, Brighenti) LiquidCrystElastomerBrighenti::State initial_state{}; auto qdata = solid_solver.createQuadratureDataBuffer(initial_state); - solid_solver.setMaterial(mat, mesh->entireBody(), qdata); + solid_solver.setMaterial(DependsOn{}, mat, mesh->entireBody(), qdata); // prescribe symmetry conditions solid_solver.setFixedBCs(mesh->domain("xmin_face"), Component::X); diff --git a/src/smith/physics/tests/parameterized_thermal.cpp b/src/smith/physics/tests/parameterized_thermal.cpp index 1e1765bfa5..2db1ca9c37 100644 --- a/src/smith/physics/tests/parameterized_thermal.cpp +++ b/src/smith/physics/tests/parameterized_thermal.cpp @@ -74,7 +74,7 @@ TEST(Thermal, ParameterizedMaterial) // Construct a potentially user-defined parameterized material and send it to the thermal module heat_transfer::ParameterizedLinearIsotropicConductor mat; - thermal_solver.setMaterial(mat, mesh->entireBody()); + thermal_solver.setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); // Define the function for the initial temperature and boundary condition auto bdr_temp = [](const mfem::Vector& x, double) -> double { diff --git a/src/smith/physics/tests/parameterized_thermomechanics_example.cpp b/src/smith/physics/tests/parameterized_thermomechanics_example.cpp index 2f7ac519c0..b3bd15984f 100644 --- a/src/smith/physics/tests/parameterized_thermomechanics_example.cpp +++ b/src/smith/physics/tests/parameterized_thermomechanics_example.cpp @@ -117,7 +117,7 @@ TEST(Thermomechanics, ParameterizedMaterial) double theta_ref = 0.0; ///< datum temperature for thermal expansion ParameterizedThermoelasticMaterial material{density, E, nu, theta_ref}; - simulation.setMaterial(material, mesh->entireBody()); + simulation.setMaterial(DependsOn<0, 1>{}, material, mesh->entireBody()); double deltaT = 1.0; FiniteElementState temperature(mesh->mfemParMesh(), H1

) { + std::apply( + [&](auto... pts) { + auto prefix_and_add = [&](auto pt) { + pt.name = "param_" + pt.name; + fs->addParameter(pt); + }; + (prefix_and_add(pts), ...); }, pack.fields); } - } else { - return std::tuple{}; // skip non-physics packs - } + }; + (register_one(trailing), ...); } -template -auto collectParamSegmentFromPack(const std::shared_ptr& fs, const Pack& pack) +inline auto extractParamPackQualified(const std::shared_ptr& /*fs*/) { return std::tuple<>{}; } + +template +auto extractParamPackQualified(const std::shared_ptr& fs, const First& first, const Rest&... rest) { - if constexpr (is_coupling_params_v> && !is_physics_fields_v) { - return std::apply( - [&](auto... pts) { - auto qualify = [&](auto pt) { - pt.name = fs->prefix("param_" + pt.name); - return pt; - }; - return std::make_tuple( - ParameterCouplingSegment{std::make_tuple(qualify(pts)...)}); - }, - pack.fields); + if constexpr (is_parameter_pack_v) { + return std::make_tuple(qualifyParams(fs, first)); } else { - return std::tuple{}; + return extractParamPackQualified(fs, rest...); } } -template -auto makeCouplingDescriptor(std::tuple segments) +template +auto makeCouplingDescriptor(std::tuple segments) { - auto fields = std::apply([](const auto&... segment) { return std::tuple_cat(segment.fields...); }, segments); + auto flat = std::apply([](const auto&... s) { return std::tuple_cat(s.fields...); }, segments); return std::apply( [&](auto... field) { - return CouplingDescriptor, Segments...>{fields, segments}; + return CouplingDescriptor, Segs...>{flat, segments}; }, - fields); + flat); } -template -auto collectCouplingFields(const std::shared_ptr& fs, const Packs&... packs) +template +auto collectCouplingFields(const std::shared_ptr& fs, const Trailing&... trailing) { - auto physics_segments = std::tuple_cat(collectPhysicsSegmentFromPack(packs)...); - auto param_segments = std::tuple_cat(collectParamSegmentFromPack(fs, packs)...); - return makeCouplingDescriptor(std::tuple_cat(physics_segments, param_segments)); + auto physics_segments = extractCouplingPacks(trailing...); + auto param_segment = extractParamPackQualified(fs, trailing...); + return makeCouplingDescriptor(std::tuple_cat(physics_segments, param_segment)); } -/// Register parameter fields from a CouplingParams pack (not PhysicsFields) into a FieldStore. -template -void registerParamsIfNeeded(std::shared_ptr fs, const Pack& pack) -{ - if constexpr (is_coupling_params_v> && !is_physics_fields_v) { - std::apply( - [&](auto... pts) { - auto prefix_and_add = [&](auto pt) { - pt.name = "param_" + pt.name; - fs->addParameter(pt); - }; - (prefix_and_add(pts), ...); - }, - pack.fields); - } -} +// ------------------------------------------------------------------------- +// Time-rule interpolation +// ------------------------------------------------------------------------- template @@ -276,8 +301,6 @@ decltype(auto) applyTimeRuleToPrefixImpl(const Rule& rule, const TimeInfoT& t_in interpolated); } -/// @brief Interpolate the leading Rule::num_states arguments, then pass interpolated values and the raw tail to -/// callback. template decltype(auto) applyTimeRuleToPrefix(const Rule& rule, const TimeInfoT& t_info, Callback&& callback, const RawArgs&... raw_args) @@ -290,17 +313,17 @@ decltype(auto) applyTimeRuleToPrefix(const Rule& rule, const TimeInfoT& t_info, std::make_index_sequence{}); } -template -auto evaluateCouplingSegment(const PhysicsCouplingSegment& /*segment*/, const TimeInfoT& t_info, +auto evaluateCouplingSegment(const PhysicsFields& /*seg*/, const TimeInfoT& t_info, const RawTuple& raw_args, std::index_sequence) { Rule rule; return rule.interpolate(t_info, std::get(raw_args)...); } -template -auto evaluateCouplingSegment(const ParameterCouplingSegment& /*segment*/, const TimeInfoT& /*t_info*/, +template +auto evaluateCouplingSegment(const CouplingParams& /*seg*/, const TimeInfoT& /*t_info*/, const RawTuple& raw_args, std::index_sequence) { return std::forward_as_tuple(std::get(raw_args)...); @@ -312,11 +335,10 @@ auto evaluateCouplingSegments(const SegmentsTuple& segments, const TimeInfoT& t_ if constexpr (I == std::tuple_size_v>) { return std::tuple{}; } else { - const auto& segment = std::get(segments); - using Segment = std::decay_t; - auto head = - evaluateCouplingSegment(segment, t_info, raw_args, std::make_index_sequence{}); - auto tail = evaluateCouplingSegments(segments, t_info, raw_args); + const auto& seg = std::get(segments); + using Seg = std::decay_t; + auto head = evaluateCouplingSegment(seg, t_info, raw_args, std::make_index_sequence{}); + auto tail = evaluateCouplingSegments(segments, t_info, raw_args); return std::tuple_cat(head, tail); } } @@ -330,30 +352,15 @@ decltype(auto) applyCouplingTimeRules(const Coupling& coupling, const TimeInfoT& return std::apply(std::forward(callback), interpolated_tail); } -/// @brief Centralized type access for field-pack spaces. -template -struct CouplingSpaces; - -template -struct CouplingSpaces> { - template - using time_rule_params = smith::TimeRuleParams; - - template - using append_to_parameters = Parameters; -}; - -template -struct CouplingSpaces> { - template - using time_rule_params = smith::TimeRuleParams; +// ------------------------------------------------------------------------- +// Type-level coupling-space extraction (used by weak-form parameter type computation) +// ------------------------------------------------------------------------- - template - using append_to_parameters = Parameters; -}; +template +struct CouplingSpaces; -template -struct CouplingSpaces, Segments...>> { +template +struct CouplingSpaces, Segs...>> { template using time_rule_params = smith::TimeRuleParams; @@ -361,11 +368,9 @@ struct CouplingSpaces, Segments...>> { using append_to_parameters = Parameters; }; -/// @brief Weak-form parameters for a self time rule followed by the coupling field spaces. template using TimeRuleParams = typename CouplingSpaces>::template time_rule_params; -/// @brief Append coupling spaces onto a base Parameters type. template struct AppendCouplingToParams; diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index b67a1a219a..16cf68c101 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -43,7 +43,7 @@ namespace smith { * @tparam parameter_space Parameter spaces for material properties. */ template > + typename Coupling = CouplingDescriptor>> struct SolidMechanicsSystem : public SystemBase { using SystemBase::SystemBase; @@ -416,18 +416,18 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons * Usage: * @code * auto solid_system = buildSolidMechanicsSystem( - * solver, opts, solid_fields, youngs_modulus, thermal_fields); + * solver, opts, solid_fields, couplingFields(thermal_fields), param_fields); * @endcode */ -template - requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +template + requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) + const SelfFields& self_fields, const Trailing&... trailing) { using DisplacementTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; - (detail::registerParamsIfNeeded(field_store, other_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + detail::registerParamsIfNeeded(field_store, trailing...); + auto coupling = detail::collectCouplingFields(field_store, trailing...); bool has_stress_output = detail::hasRegisteredStressOutput(field_store); return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, options, has_stress_output); @@ -441,27 +441,27 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid * Usage: * @code * auto solid_system = buildSolidMechanicsSystem( - * nonlin_opts, lin_opts, opts, field_store, param_fields, thermal_fields); + * nonlin_opts, lin_opts, opts, field_store, couplingFields(thermal_fields), param_fields); * auto standalone_solid = buildSolidMechanicsSystem( * nonlin_opts, lin_opts, opts, mesh, param_fields); * @endcode */ -template +template requires((detail::is_field_store_ptr_v || detail::is_mesh_ptr_v) && - (detail::is_coupling_params_v && ...)) + detail::trailing_coupling_args_valid_v) auto buildSolidMechanicsSystem(const NonlinearSolverOptions& nonlinear_options, const LinearSolverOptions& linear_options, const SolidMechanicsOptions& options, - FieldStoreOrMesh field_store_or_mesh, const OtherPacks&... other_packs) + FieldStoreOrMesh field_store_or_mesh, const Trailing&... trailing) { if constexpr (detail::is_mesh_ptr_v) { - static_assert((!detail::is_physics_fields_v && ...), - "Pass a FieldStore when building a system coupled to registered physics fields."); + static_assert((!detail::is_coupling_fields_v && ...), + "Pass a FieldStore when building a system coupled to couplingFields(...)."); } auto field_store = detail::getOrCreateFieldStore(field_store_or_mesh, options.prefix); auto self_fields = registerSolidMechanicsFields(field_store, options); auto solver = std::make_shared( buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); - return buildSolidMechanicsSystem(solver, options, self_fields, other_packs...); + return buildSolidMechanicsSystem(solver, options, self_fields, trailing...); } } // namespace smith diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index 69fe8f0d69..e35d5ee4cf 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -13,7 +13,7 @@ * field_store, params...); * * auto internal_variable_system = buildInternalVariableSystem( - * solver, opts, internal_variable_fields, params..., solid_fields); + * solver, internal_variable_fields, couplingFields(solid_fields), param_fields); * * The returned PhysicsFields from registerInternalVariableFields carries field tokens * (state_solve_state, state) that can be injected into another physics system @@ -55,7 +55,7 @@ namespace smith { * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). */ template > + typename Coupling = CouplingDescriptor>> struct InternalVariableSystem : public SystemBase { using SystemBase::SystemBase; @@ -194,15 +194,15 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co * * The time rule is deduced from SelfFields::time_rule_type. */ -template - requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +template + requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields, - const OtherPacks&... other_packs) + const Trailing&... trailing) { using InternalVarTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; - (detail::registerParamsIfNeeded(field_store, other_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + detail::registerParamsIfNeeded(field_store, trailing...); + auto coupling = detail::collectCouplingFields(field_store, trailing...); return detail::buildInternalVariableSystemImpl(field_store, coupling, solver); } diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 8d2573828d..259eaf5877 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -50,7 +50,7 @@ TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) auto thermal_fields = registerThermalFields(field_store); auto scale_params = registerParameterFields(FieldType>("scale")); - auto solid_coupling = detail::collectCouplingFields(field_store, thermal_fields, scale_params); + auto solid_coupling = detail::collectCouplingFields(field_store, couplingFields(thermal_fields), scale_params); auto saw_thermal_values = detail::applyCouplingTimeRules( solid_coupling, TimeInfo(0.0, 2.0, 0), [](auto temperature, auto temperature_dot, auto scale) { @@ -62,7 +62,7 @@ TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) 7.0, 1.0, 11.0); EXPECT_TRUE(saw_thermal_values); - auto thermal_coupling = detail::collectCouplingFields(field_store, solid_fields); + auto thermal_coupling = detail::collectCouplingFields(field_store, couplingFields(solid_fields)); auto saw_solid_values = detail::applyCouplingTimeRules( thermal_coupling, TimeInfo(0.0, 2.0, 0), [](auto displacement, auto velocity, auto /*acceleration*/) { @@ -74,6 +74,34 @@ TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) EXPECT_TRUE(saw_solid_values); } +TEST(CouplingTimeRuleInterpolation, PreservesForeignPacksWithSameTimeRuleType) +{ + axom::sidre::DataStore datastore; + smith::StateManager::initialize(datastore, "same_rule_coupling"); + auto mesh = + std::make_shared(mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON), "mesh", 0, 0); + auto field_store = std::make_shared(mesh, 100, ""); + + using ScalarSpace = H1; + PhysicsFields thermal_a{ + field_store, FieldType("temperature_a_solve_state"), FieldType("temperature_a")}; + PhysicsFields thermal_b{ + field_store, FieldType("temperature_b_solve_state"), FieldType("temperature_b")}; + + auto same_rule_coupling = detail::collectCouplingFields(field_store, couplingFields(thermal_a, thermal_b)); + auto saw_all_values = detail::applyCouplingTimeRules( + same_rule_coupling, TimeInfo(0.0, 2.0, 0), + [](auto temperature_a, auto temperature_a_dot, auto temperature_b, auto temperature_b_dot) { + EXPECT_DOUBLE_EQ(temperature_a, 7.0); + EXPECT_DOUBLE_EQ(temperature_a_dot, 3.0); + EXPECT_DOUBLE_EQ(temperature_b, 5.0); + EXPECT_DOUBLE_EQ(temperature_b_dot, 1.75); + return true; + }, + 7.0, 1.0, 5.0, 1.5); + EXPECT_TRUE(saw_all_values); +} + struct ThermoMechanicsMeshFixture : public testing::Test { void SetUp() { @@ -151,10 +179,12 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto thermal_fields = registerThermalFields(field_store_); auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), + param_fields); auto thermal_system = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields), + param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); auto physics = makeDifferentiablePhysics(coupled_system, "coupled_physics"); @@ -186,10 +216,12 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto thermal_fields = registerThermalFields(field_store_); auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, param_fields, thermal_fields); + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), + param_fields); auto thermal_system = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, param_fields, solid_fields); + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields), + param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, @@ -251,10 +283,10 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) .max_iterations = 12, .max_line_search_iterations = 6}; - auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - param_fields, thermal_fields); - auto thermal_system = - buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, param_fields, solid_fields); + auto solid_system = buildSolidMechanicsSystem( + nullptr, SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), param_fields); + auto thermal_system = buildThermalSystem( + nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_solver = std::make_shared(10); coupled_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); @@ -334,9 +366,10 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto thermal_fields = registerThermalFields(field_store_); auto solid_system = buildSolidMechanicsSystem( - makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, thermal_fields); - auto thermal_system = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), - ThermalOptions{}, thermal_fields, solid_fields); + makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem( + makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -362,10 +395,10 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - thermal_fields); + auto solid_system = buildSolidMechanicsSystem( + nullptr, SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields)); auto thermal_system = - buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, solid_fields); + buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto coupled_system = combineSystems(solver_ptr, solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -431,10 +464,10 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - solid_opts, solid_fields, thermal_fields); - auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), - ThermalOptions{}, thermal_fields, solid_fields); + auto solid_system = buildSolidMechanicsSystem( + makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields, couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem( + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto combined_system = combineSystems(solid_system, thermal_system); @@ -450,9 +483,10 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesCycleZeroSystems) auto thermal_fields = registerThermalFields(field_store_); auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, thermal_fields); - auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), - ThermalOptions{}, thermal_fields, solid_fields); + makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem( + makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto combined_system = combineSystems(solid_system, thermal_system); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index c776cddc43..bc78d6b050 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -146,9 +146,10 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - internal_variable_fields); + couplingFields(internal_variable_fields)); auto internal_variable_system = - buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, solid_fields); + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, + couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); @@ -184,9 +185,10 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - internal_variable_fields); + couplingFields(internal_variable_fields)); auto internal_variable_system = - buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, solid_fields); + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, + couplingFields(solid_fields)); auto system = combineSystems(staggered_solver, solid_system, internal_variable_system); @@ -209,9 +211,10 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto field_store = std::make_shared(mesh, 100, "body_force_test_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - internal_variable_fields); + couplingFields(internal_variable_fields)); auto internal_variable_system = - buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, solid_fields); + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, + couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 03d4773b81..fd41bbe776 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -156,15 +156,16 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // Phase 2: build each system. // Solid receives thermal and alpha coupling. - auto solid_system = buildSolidMechanicsSystem(std::make_shared(solid_block_solver), - SolidMechanicsOptions{}, solid_fields, thermal_fields, - internal_variable_fields); + auto solid_system = buildSolidMechanicsSystem( + std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields, internal_variable_fields)); auto thermal_system = buildThermalSystem(std::make_shared(thermal_block_solver), - ThermalOptions{}, thermal_fields, solid_fields); + ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto internal_variable_system = buildInternalVariableSystem( - std::make_shared(internal_variable_block_solver), internal_variable_fields, solid_fields); + std::make_shared(internal_variable_block_solver), internal_variable_fields, + couplingFields(solid_fields)); // Phase 3: register material integrands. auto material = SimpleThermoelasticMaterial{}; diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index e3c4200a7c..70bfb74437 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -39,7 +39,7 @@ namespace smith { * before user parameter_space fields. */ template > + typename Coupling = CouplingDescriptor>> struct ThermalSystem : public SystemBase { using SystemBase::SystemBase; @@ -264,18 +264,18 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl * Usage: * @code * auto thermal_system = buildThermalSystem( - * solver, opts, thermal_fields, solid_fields); + * solver, opts, thermal_fields, couplingFields(solid_fields)); * @endcode */ -template - requires(detail::has_time_rule_v && (detail::is_coupling_params_v && ...)) +template + requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, - const SelfFields& self_fields, const OtherPacks&... other_packs) + const SelfFields& self_fields, const Trailing&... trailing) { using TemperatureTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; - (detail::registerParamsIfNeeded(field_store, other_packs), ...); - auto coupling = detail::collectCouplingFields(field_store, self_fields, other_packs...); + detail::registerParamsIfNeeded(field_store, trailing...); + auto coupling = detail::collectCouplingFields(field_store, trailing...); return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } @@ -287,27 +287,27 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio * Usage: * @code * auto thermal_system = buildThermalSystem( - * nonlin_opts, lin_opts, opts, field_store, param_fields, solid_fields); + * nonlin_opts, lin_opts, opts, field_store, couplingFields(solid_fields), param_fields); * auto standalone_thermal = buildThermalSystem( * nonlin_opts, lin_opts, opts, mesh, param_fields); * @endcode */ -template +template requires((detail::is_field_store_ptr_v || detail::is_mesh_ptr_v) && - (detail::is_coupling_params_v && ...)) + detail::trailing_coupling_args_valid_v) auto buildThermalSystem(const NonlinearSolverOptions& nonlinear_options, const LinearSolverOptions& linear_options, const ThermalOptions& options, FieldStoreOrMesh field_store_or_mesh, - const OtherPacks&... other_packs) + const Trailing&... trailing) { if constexpr (detail::is_mesh_ptr_v) { - static_assert((!detail::is_physics_fields_v && ...), - "Pass a FieldStore when building a system coupled to registered physics fields."); + static_assert((!detail::is_coupling_fields_v && ...), + "Pass a FieldStore when building a system coupled to couplingFields(...)."); } auto field_store = detail::getOrCreateFieldStore(field_store_or_mesh); auto self_fields = registerThermalFields(field_store, options); auto solver = std::make_shared( buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); - return buildThermalSystem(solver, options, self_fields, other_packs...); + return buildThermalSystem(solver, options, self_fields, trailing...); } } // namespace smith From 24185aa28a070dd182cd4febc92b88530e5f662f Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 12 May 2026 10:08:49 -0600 Subject: [PATCH 56/67] Simplify coupled interface even more. --- .../tests/test_combined_thermo_mechanics.cpp | 52 +++++++++---------- .../test_solid_static_with_internal_vars.cpp | 15 +++--- ...st_thermo_mechanics_with_internal_vars.cpp | 11 ++-- 3 files changed, 38 insertions(+), 40 deletions(-) diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 259eaf5877..2949f5e658 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -178,13 +178,13 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), - param_fields); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields), param_fields); - auto thermal_system = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields), - param_fields); + auto thermal_system = + buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); auto physics = makeDifferentiablePhysics(coupled_system, "coupled_physics"); @@ -215,13 +215,13 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), - param_fields); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields), param_fields); - auto thermal_system = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields), - param_fields); + auto thermal_system = + buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, @@ -283,10 +283,10 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) .max_iterations = 12, .max_line_search_iterations = 6}; - auto solid_system = buildSolidMechanicsSystem( - nullptr, SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), param_fields); - auto thermal_system = buildThermalSystem( - nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields), param_fields); + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields), param_fields); + auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, + couplingFields(solid_fields), param_fields); auto coupled_solver = std::make_shared(10); coupled_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); @@ -365,9 +365,9 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem( - makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields)); + auto solid_system = buildSolidMechanicsSystem(makeSolver(mech_nonlin_opts, mech_lin_opts), + SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields)); auto thermal_system = buildThermalSystem( makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); @@ -395,10 +395,10 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem( - nullptr, SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields)); - auto thermal_system = - buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, + couplingFields(solid_fields)); auto coupled_system = combineSystems(solver_ptr, solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -482,9 +482,9 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesCycleZeroSystems) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields)); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), + SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields)); auto thermal_system = buildThermalSystem( makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index bc78d6b050..d19a14b938 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -147,9 +147,8 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, couplingFields(internal_variable_fields)); - auto internal_variable_system = - buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, - couplingFields(solid_fields)); + auto internal_variable_system = buildInternalVariableSystem( + internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); @@ -186,9 +185,8 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, couplingFields(internal_variable_fields)); - auto internal_variable_system = - buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, - couplingFields(solid_fields)); + auto internal_variable_system = buildInternalVariableSystem( + internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(staggered_solver, solid_system, internal_variable_system); @@ -212,9 +210,8 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, couplingFields(internal_variable_fields)); - auto internal_variable_system = - buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, - couplingFields(solid_fields)); + auto internal_variable_system = buildInternalVariableSystem( + internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index fd41bbe776..00f359db1b 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -160,12 +160,13 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields, internal_variable_fields)); - auto thermal_system = buildThermalSystem(std::make_shared(thermal_block_solver), - ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); + auto thermal_system = + buildThermalSystem(std::make_shared(thermal_block_solver), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields)); - auto internal_variable_system = buildInternalVariableSystem( - std::make_shared(internal_variable_block_solver), internal_variable_fields, - couplingFields(solid_fields)); + auto internal_variable_system = + buildInternalVariableSystem(std::make_shared(internal_variable_block_solver), + internal_variable_fields, couplingFields(solid_fields)); // Phase 3: register material integrands. auto material = SimpleThermoelasticMaterial{}; From 784335b0512e4cfec00b2bb60583911d67e5f265 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 12 May 2026 11:44:03 -0600 Subject: [PATCH 57/67] Update system solver api again for coupled systems to make the templating a bit easier. --- .../composable_solid_mechanics.cpp | 10 +- .../composable_thermo_mechanics.cpp | 8 +- .../composable_thermo_mechanics_advanced.cpp | 8 +- .../coupling_params.hpp | 250 ++++++++++-------- .../differentiable_numerics/field_store.hpp | 10 + .../solid_mechanics_system.hpp | 162 ++++-------- .../state_variable_system.hpp | 38 ++- .../tests/test_combined_thermo_mechanics.cpp | 72 +++-- .../tests/test_solid_dynamics.cpp | 44 ++- .../test_solid_static_with_internal_vars.cpp | 14 +- .../tests/test_thermal_static.cpp | 12 +- ...st_thermo_mechanics_with_internal_vars.cpp | 8 +- .../thermal_system.hpp | 106 ++------ 13 files changed, 352 insertions(+), 390 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 34853a6497..e54584c90f 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -94,9 +94,13 @@ int main(int argc, char* argv[]) // _solver_end // _build_start - auto solid_system = - smith::buildSolidMechanicsSystem( - nonlinear_options, linear_options, output_options, mesh, param_fields); + auto field_store = smith::fieldStore(mesh); + auto solid_fields = + smith::registerSolidMechanicsFields( + field_store, output_options); + auto solver = std::make_shared( + smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); + auto solid_system = smith::buildSolidMechanicsSystem(solver, output_options, solid_fields, param_fields); constexpr double E = 100.0; constexpr double nu = 0.25; diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index ceab8dd294..ff74391257 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -72,10 +72,10 @@ int main(int argc, char* argv[]) auto thermal_solver = std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); - auto solid_system = smith::buildSolidMechanicsSystem( - solid_solver, smith::SolidMechanicsOptions{}, solid_fields, smith::couplingFields(thermal_fields)); - auto thermal_system = smith::buildThermalSystem( - thermal_solver, smith::ThermalOptions{}, thermal_fields, smith::couplingFields(solid_fields)); + auto solid_system = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, + solid_fields, smith::couplingFields(thermal_fields)); + auto thermal_system = smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, + smith::couplingFields(solid_fields)); auto coupled_system = smith::combineSystems(solid_system, thermal_system); diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index f90868ff0e..01e60ac084 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -65,10 +65,10 @@ int main(int argc, char* argv[]) smith::registerThermalFields(field_store); auto param_fields = smith::registerParameterFields(smith::FieldType>("thermal_expansion_scaling")); - auto solid_system = smith::buildSolidMechanicsSystem( - nullptr, solid_options, solid_fields, smith::couplingFields(thermal_fields), param_fields); - auto thermal_system = smith::buildThermalSystem( - nullptr, smith::ThermalOptions{}, thermal_fields, smith::couplingFields(solid_fields), param_fields); + auto solid_system = smith::buildSolidMechanicsSystem(nullptr, solid_options, solid_fields, + smith::couplingFields(thermal_fields), param_fields); + auto thermal_system = smith::buildThermalSystem(nullptr, smith::ThermalOptions{}, thermal_fields, + smith::couplingFields(solid_fields), param_fields); smith::thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{ 1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 626e1714d2..6041c53474 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -26,20 +26,6 @@ namespace smith { -/** - * @brief Declares the finite element spaces and field names of parameter fields. - */ -template -struct CouplingParams { - static constexpr std::size_t num_fields = sizeof...(Spaces); - static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); - std::tuple...> fields; - CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} -}; - -template -CouplingParams(FieldType...) -> CouplingParams; - /// Sentinel: no time integration rule (used for parameter-only packs). struct NoTimeRule { static constexpr int num_states = 0; @@ -50,14 +36,14 @@ struct NoTimeRule { * * Doubles as a per-physics coupling segment: when supplied via `couplingFields(...)`, the * builder interpolates `TimeRule::num_states` raw arguments before passing the values to - * the user callback. + * the user callback. Parameter packs use `TimeRule = NoTimeRule`. */ -template +template struct PhysicsFields { using time_rule_type = TimeRule; - static constexpr std::size_t num_rule_states = TimeRule::num_states; + static constexpr int dim = Dim; + static constexpr int order = Order; static constexpr std::size_t num_fields = sizeof...(Spaces); - static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); std::shared_ptr field_store; std::tuple...> fields; @@ -67,6 +53,23 @@ struct PhysicsFields { } }; +/** + * @brief Parameter-only field bundle (no time rule, no field store reference). + * + * Distinct type so callers can plain-pass it after `couplingFields(...)`. Otherwise has the + * same shape as a `PhysicsFields` with `NoTimeRule`. + */ +template +struct CouplingParams { + using time_rule_type = NoTimeRule; + static constexpr std::size_t num_fields = sizeof...(Spaces); + std::tuple...> fields; + CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} +}; + +template +CouplingParams(FieldType...) -> CouplingParams; + /** * @brief Bundle of foreign `PhysicsFields` packs supplied to a builder as a single coupling arg. * @@ -84,23 +87,6 @@ auto couplingFields(const PFs&... pfs) return CouplingFields{std::make_tuple(pfs...)}; } -/** - * @brief Aggregate of caller-ordered segments plus a flat field tuple. - * - * `segments` holds the foreign `PhysicsFields<...>` in caller order, optionally followed by a - * single (name-qualified) `CouplingParams<...>`. `fields` is the concatenation of each segment's - * `fields`, used directly as trailing weak-form parameter spaces. - */ -template -struct CouplingDescriptor; - -template -struct CouplingDescriptor, Segments...> { - static constexpr std::size_t num_coupling_fields = sizeof...(FlatSpaces); - std::tuple...> fields; - std::tuple segments; -}; - /** * @brief Register parameter fields as type-level tokens. */ @@ -115,8 +101,8 @@ namespace detail { template struct is_physics_fields_impl : std::false_type {}; -template -struct is_physics_fields_impl> : std::true_type {}; +template +struct is_physics_fields_impl> : std::true_type {}; template inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; @@ -139,14 +125,15 @@ struct is_coupling_fields_impl> : std::true_type {}; template inline constexpr bool is_coupling_fields_v = is_coupling_fields_impl>::value; +/// True for a `std::tuple` returned by `collectCouplingFields`. template -struct is_coupling_descriptor_impl : std::false_type {}; +struct is_coupling_packs_impl : std::false_type {}; -template -struct is_coupling_descriptor_impl> : std::true_type {}; +template +struct is_coupling_packs_impl> : std::true_type {}; template -inline constexpr bool is_coupling_params_v = is_coupling_descriptor_impl>::value; +inline constexpr bool is_coupling_packs_v = is_coupling_packs_impl>::value; template inline constexpr bool has_time_rule_v = false; @@ -155,12 +142,6 @@ template inline constexpr bool has_time_rule_v>> = !std::is_same_v::time_rule_type, NoTimeRule>; -template -inline constexpr bool is_field_store_ptr_v = std::is_same_v, std::shared_ptr>; - -template -inline constexpr bool is_mesh_ptr_v = std::is_same_v, std::shared_ptr>; - /// Trailing args must be one of: {}, {CouplingFields}, {CouplingParams}, {CouplingFields, CouplingParams}. template inline constexpr bool trailing_coupling_args_valid_v = [] { @@ -178,16 +159,6 @@ inline constexpr bool trailing_coupling_args_valid_v = [] { } }(); -template -auto getOrCreateFieldStore(T source, std::string prefix = "", size_t storage_size = 100) -{ - if constexpr (is_field_store_ptr_v) { - return source; - } else { - return std::make_shared(source, storage_size, prefix); - } -} - // ------------------------------------------------------------------------- // Trailing-arg extraction // ------------------------------------------------------------------------- @@ -263,23 +234,19 @@ auto extractParamPackQualified(const std::shared_ptr& fs, const Firs } } -template -auto makeCouplingDescriptor(std::tuple segments) +/// Concatenate each pack's `.fields` tuple — used to derive trailing weak-form parameter spaces. +template +auto flattenCouplingFields(const PacksTuple& packs) { - auto flat = std::apply([](const auto&... s) { return std::tuple_cat(s.fields...); }, segments); - return std::apply( - [&](auto... field) { - return CouplingDescriptor, Segs...>{flat, segments}; - }, - flat); + return std::apply([](const auto&... pack) { return std::tuple_cat(pack.fields...); }, packs); } template auto collectCouplingFields(const std::shared_ptr& fs, const Trailing&... trailing) { - auto physics_segments = extractCouplingPacks(trailing...); - auto param_segment = extractParamPackQualified(fs, trailing...); - return makeCouplingDescriptor(std::tuple_cat(physics_segments, param_segment)); + auto physics_packs = extractCouplingPacks(trailing...); + auto param_pack = extractParamPackQualified(fs, trailing...); + return std::tuple_cat(physics_packs, param_pack); } // ------------------------------------------------------------------------- @@ -313,70 +280,143 @@ decltype(auto) applyTimeRuleToPrefix(const Rule& rule, const TimeInfoT& t_info, std::make_index_sequence{}); } -template -auto evaluateCouplingSegment(const PhysicsFields& /*seg*/, const TimeInfoT& t_info, - const RawTuple& raw_args, std::index_sequence) +template +auto evaluateCouplingPack(const Pack& /*pack*/, const TimeInfoT& t_info, const RawTuple& raw_args, + std::index_sequence) { - Rule rule; - return rule.interpolate(t_info, std::get(raw_args)...); -} - -template -auto evaluateCouplingSegment(const CouplingParams& /*seg*/, const TimeInfoT& /*t_info*/, - const RawTuple& raw_args, std::index_sequence) -{ - return std::forward_as_tuple(std::get(raw_args)...); + using Rule = typename Pack::time_rule_type; + if constexpr (std::is_same_v) { + return std::forward_as_tuple(std::get(raw_args)...); + } else { + Rule rule; + return rule.interpolate(t_info, std::get(raw_args)...); + } } -template -auto evaluateCouplingSegments(const SegmentsTuple& segments, const TimeInfoT& t_info, const RawTuple& raw_args) +template +auto evaluateCouplingPacks(const PacksTuple& packs, const TimeInfoT& t_info, const RawTuple& raw_args) { - if constexpr (I == std::tuple_size_v>) { + if constexpr (I == std::tuple_size_v>) { return std::tuple{}; } else { - const auto& seg = std::get(segments); - using Seg = std::decay_t; - auto head = evaluateCouplingSegment(seg, t_info, raw_args, std::make_index_sequence{}); - auto tail = evaluateCouplingSegments(segments, t_info, raw_args); + const auto& pack = std::get(packs); + using Pack = std::decay_t; + auto head = evaluateCouplingPack(pack, t_info, raw_args, std::make_index_sequence{}); + auto tail = evaluateCouplingPacks(packs, t_info, raw_args); return std::tuple_cat(head, tail); } } -template -decltype(auto) applyCouplingTimeRules(const Coupling& coupling, const TimeInfoT& t_info, Callback&& callback, +template +decltype(auto) applyCouplingTimeRules(const PacksTuple& packs, const TimeInfoT& t_info, Callback&& callback, const RawArgs&... raw_args) { auto raw_tuple = std::forward_as_tuple(raw_args...); - auto interpolated_tail = evaluateCouplingSegments<0, 0>(coupling.segments, t_info, raw_tuple); + auto interpolated_tail = evaluateCouplingPacks<0, 0>(packs, t_info, raw_tuple); return std::apply(std::forward(callback), interpolated_tail); } +/** + * @brief Interpolate self time-rule states then coupling segments, then invoke callback. + * + * Combines `applyTimeRuleToPrefix` with `applyCouplingTimeRules` into a single helper so + * per-method weak-form bodies stop repeating the same 3-level nested-lambda boilerplate. + * + * Callback signature: `(self_states..., interpolated_coupling...)`. + */ +template +decltype(auto) applyTimeRuleAndCoupling(const Rule& rule, const Coupling& coupling, const TimeInfoT& t_info, + Callback&& callback, const RawArgs&... raw_args) +{ + constexpr std::size_t tail_count = sizeof...(RawArgs) - Rule::num_states; + return applyTimeRuleToPrefix( + rule, t_info, + [&](auto... self_states_and_tail) { + constexpr std::size_t n_self = sizeof...(self_states_and_tail) - tail_count; + auto all = std::forward_as_tuple(self_states_and_tail...); + return [&](std::index_sequence, std::index_sequence) { + return applyCouplingTimeRules( + coupling, t_info, + [&](auto... interpolated_coupling) { + return std::forward(callback)(std::get(all)..., interpolated_coupling...); + }, + std::get(all)...); + }(std::make_index_sequence{}, std::make_index_sequence{}); + }, + raw_args...); +} + // ------------------------------------------------------------------------- // Type-level coupling-space extraction (used by weak-form parameter type computation) // ------------------------------------------------------------------------- -template -struct CouplingSpaces; +/// Flatten a `tuple` type into `Parameters`. +template +struct FlattenCoupling; + +template +struct FlattenCoupling> { + private: + template + struct pack_spaces; + template + struct pack_spaces> { + using type = Parameters; + }; + template + struct pack_spaces> { + using type = Parameters; + }; + + template + struct concat; + template <> + struct concat<> { + using type = Parameters<>; + }; + template + struct concat> { + using type = Parameters; + }; + template + struct concat, Parameters, Rest...> { + using type = typename concat, Rest...>::type; + }; + + public: + using parameters = typename concat>::type...>::type; +}; + +template +using flatten_coupling_t = typename FlattenCoupling>::parameters; -template -struct CouplingSpaces, Segs...>> { - template - using time_rule_params = smith::TimeRuleParams; +/// `Parameters` for a weak form's parameter list. +template +struct TimeRuleParamsImpl; - template - using append_to_parameters = Parameters; +template +struct TimeRuleParamsImpl> { + using type = smith::TimeRuleParams; }; -template -using TimeRuleParams = typename CouplingSpaces>::template time_rule_params; +template +using TimeRuleParams = typename TimeRuleParamsImpl>::type; -template +template struct AppendCouplingToParams; -template -struct AppendCouplingToParams> { - using type = typename CouplingSpaces>::template append_to_parameters; +template +struct AppendCouplingToParams> { + private: + template + struct expand; + template + struct expand> { + using type = Parameters; + }; + + public: + using type = typename expand>::type; }; } // namespace detail diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 52d4d1f5d6..859b108727 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -517,4 +517,14 @@ auto createWeakForm(std::string name, FieldType test_type, FieldS field_store.createSpaces(name, test_type.name, field_types...)); } +/** + * @brief Construct a `shared_ptr` over a mesh. Inline call-site helper for + * standalone-physics setup; equivalent to `std::make_shared(mesh, ...)`. + */ +inline std::shared_ptr fieldStore(std::shared_ptr mesh, std::size_t storage_size = 200, + std::string prefix = "") +{ + return std::make_shared(std::move(mesh), storage_size, std::move(prefix)); +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index 16cf68c101..ff5bcf978b 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -43,7 +43,7 @@ namespace smith { * @tparam parameter_space Parameter spaces for material properties. */ template >> + typename Coupling = std::tuple<>> struct SolidMechanicsSystem : public SystemBase { using SystemBase::SystemBase; @@ -81,19 +81,13 @@ struct SolidMechanicsSystem : public SystemBase { auto captured_rule = disp_time_rule; auto captured_coupling = coupling; solid_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto u_current, auto v_current, auto a_current, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - typename MaterialType::State state; - auto pk_stress = material(t_info, state, get(u_current), get(v_current), - interpolated_params...); - - return smith::tuple{get(a_current) * material.density, pk_stress}; - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto u_current, auto v_current, auto a_current, auto... interpolated_params) { + typename MaterialType::State state; + auto pk_stress = + material(t_info, state, get(u_current), get(v_current), interpolated_params...); + return smith::tuple{get(a_current) * material.density, pk_stress}; }, raw_args...); }); @@ -106,29 +100,24 @@ struct SolidMechanicsSystem : public SystemBase { if (stress_weak_form) { bool do_cauchy = this->output_cauchy_stress; stress_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto stress, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto u_current, auto v_current, auto /*a_current*/, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - typename MaterialType::State state; - auto pk_stress = material(t_info, state, get(u_current), get(v_current), - interpolated_params...); - - auto flat_stress = [&]() { - if (do_cauchy) { - static constexpr auto I_ = Identity(); - auto F = get(u_current) + I_; - auto J = det(F); - auto sigma = (1.0 / J) * dot(pk_stress, transpose(F)); - return make_tensor([&](int i) { return sigma[i / dim][i % dim]; }); - } - return make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); - }(); - return smith::tuple{get(stress) - flat_stress, tensor{}}; - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto u_current, auto v_current, auto /*a_current*/, auto... interpolated_params) { + typename MaterialType::State state; + auto pk_stress = material(t_info, state, get(u_current), get(v_current), + interpolated_params...); + + auto flat_stress = [&]() { + if (do_cauchy) { + static constexpr auto I_ = Identity(); + auto F = get(u_current) + I_; + auto J = det(F); + auto sigma = (1.0 / J) * dot(pk_stress, transpose(F)); + return make_tensor([&](int i) { return sigma[i / dim][i % dim]; }); + } + return make_tensor([&](int i) { return pk_stress[i / dim][i % dim]; }); + }(); + return smith::tuple{get(stress) - flat_stress, tensor{}}; }, raw_args...); }); @@ -147,15 +136,10 @@ struct SolidMechanicsSystem : public SystemBase { auto captured_rule = disp_time_rule; auto captured_coupling = coupling; solid_weak_form->addBodySource(domain_name, [=](auto t_info, auto X, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto u_current, auto v_current, auto a_current, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - return force_function(t_info.time(), X, u_current, v_current, a_current, interpolated_params...); - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto u_current, auto v_current, auto a_current, auto... interpolated_params) { + return force_function(t_info.time(), X, u_current, v_current, a_current, interpolated_params...); }, raw_args...); }); @@ -173,16 +157,10 @@ struct SolidMechanicsSystem : public SystemBase { auto captured_rule = disp_time_rule; auto captured_coupling = coupling; solid_weak_form->addBoundaryFlux(domain_name, [=](auto t_info, auto X, auto n, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto u_current, auto v_current, auto a_current, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - return traction_function(t_info.time(), X, n, u_current, v_current, a_current, - interpolated_params...); - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto u_current, auto v_current, auto a_current, auto... interpolated_params) { + return traction_function(t_info.time(), X, n, u_current, v_current, a_current, interpolated_params...); }, raw_args...); }); @@ -200,21 +178,16 @@ struct SolidMechanicsSystem : public SystemBase { auto captured_rule = disp_time_rule; auto captured_coupling = coupling; solid_weak_form->addBoundaryIntegral(domain_name, [=](auto t_info, auto X, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto u_current, auto /*v_current*/, auto /*a_current*/, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - auto x_current = X + u_current; - auto n_deformed = cross(get(x_current)); - auto n_shape_norm = norm(cross(get(X))); - - auto pressure = pressure_function(t_info.time(), get(X), get(interpolated_params)...); - - return pressure * n_deformed * (1.0 / n_shape_norm); - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto u_current, auto /*v_current*/, auto /*a_current*/, auto... interpolated_params) { + auto x_current = X + u_current; + auto n_deformed = cross(get(x_current)); + auto n_shape_norm = norm(cross(get(X))); + + auto pressure = pressure_function(t_info.time(), get(X), get(interpolated_params)...); + + return pressure * n_deformed * (1.0 / n_shape_norm); }, raw_args...); }); @@ -245,7 +218,6 @@ struct SolidMechanicsSystem : public SystemBase { struct SolidMechanicsOptions { bool enable_stress_output = false; ///< Register stress output fields during phase 1. bool output_cauchy_stress = false; ///< When true, project Cauchy stress (sigma) instead of PK1 (P). - std::string prefix; ///< Prefix used only by standalone Mesh-based builders. }; /** @@ -273,7 +245,7 @@ auto registerSolidMechanicsFields(std::shared_ptr field_store, field_store->addDependent(disp_type, FieldStore::TimeDerivative::DDOT, "acceleration"); auto physics_fields = - PhysicsFields, H1, H1, H1>{ + PhysicsFields, H1, H1, H1>{ field_store, FieldType>(field_store->prefix("displacement_solve_state")), FieldType>(field_store->prefix("displacement")), FieldType>(field_store->prefix("velocity")), @@ -327,7 +299,7 @@ inline std::shared_ptr makeCycleZeroSolver(std::shared_ptr - requires detail::is_coupling_params_v + requires detail::is_coupling_packs_v auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, const Coupling& coupling, std::shared_ptr solver, const SolidMechanicsOptions& options, bool has_stress_output) @@ -344,10 +316,11 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons using SystemType = SolidMechanicsSystem; + auto coupling_fields_flat = detail::flattenCouplingFields(coupling); std::string force_name = field_store->prefix("reactions"); auto solid_weak_form = detail::buildWeakFormWithCoupling( field_store, force_name, disp_type.name, disp_type, disp_old_type, velo_old_type, accel_old_type, - coupling.fields); + coupling_fields_flat); auto sys = std::make_shared(field_store, solver, std::vector>{solid_weak_form}); sys->disp_bc = disp_bc; @@ -372,7 +345,7 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons cycle_zero_inputs.push_back(field.name); } }; - std::apply([&](const auto&... coupling_fields) { (append_if_state(coupling_fields), ...); }, coupling.fields); + std::apply([&](const auto&... coupling_fields) { (append_if_state(coupling_fields), ...); }, coupling_fields_flat); cycle_zero_system->solve_input_field_names = {cycle_zero_inputs}; sys->cycle_zero_systems.push_back(cycle_zero_system); } @@ -383,7 +356,7 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons std::string stress_name = field_store->prefix("stress_projection"); sys->stress_weak_form = detail::buildWeakFormWithCoupling( field_store, stress_name, stress_type.name, stress_type, disp_as_input, disp_old_type, velo_old_type, - accel_old_type, coupling.fields); + accel_old_type, coupling_fields_flat); NonlinearSolverOptions stress_nonlin{.nonlin_solver = NonlinearSolver::Newton, .relative_tol = 1e-14, @@ -419,11 +392,13 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons * solver, opts, solid_fields, couplingFields(thermal_fields), param_fields); * @endcode */ -template +template requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, const SelfFields& self_fields, const Trailing&... trailing) { + constexpr int dim = SelfFields::dim; + constexpr int order = SelfFields::order; using DisplacementTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; detail::registerParamsIfNeeded(field_store, trailing...); @@ -433,35 +408,4 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid has_stress_output); } -/** - * @brief Build a SolidMechanicsSystem from solver options and either a FieldStore or Mesh. - * - * Pass a FieldStore for coupled/composed systems. Pass a Mesh for a standalone solid system. - * - * Usage: - * @code - * auto solid_system = buildSolidMechanicsSystem( - * nonlin_opts, lin_opts, opts, field_store, couplingFields(thermal_fields), param_fields); - * auto standalone_solid = buildSolidMechanicsSystem( - * nonlin_opts, lin_opts, opts, mesh, param_fields); - * @endcode - */ -template - requires((detail::is_field_store_ptr_v || detail::is_mesh_ptr_v) && - detail::trailing_coupling_args_valid_v) -auto buildSolidMechanicsSystem(const NonlinearSolverOptions& nonlinear_options, - const LinearSolverOptions& linear_options, const SolidMechanicsOptions& options, - FieldStoreOrMesh field_store_or_mesh, const Trailing&... trailing) -{ - if constexpr (detail::is_mesh_ptr_v) { - static_assert((!detail::is_coupling_fields_v && ...), - "Pass a FieldStore when building a system coupled to couplingFields(...)."); - } - auto field_store = detail::getOrCreateFieldStore(field_store_or_mesh, options.prefix); - auto self_fields = registerSolidMechanicsFields(field_store, options); - auto solver = std::make_shared( - buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); - return buildSolidMechanicsSystem(solver, options, self_fields, trailing...); -} - } // namespace smith diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index e35d5ee4cf..d4798ac29a 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -55,7 +55,7 @@ namespace smith { * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). */ template >> + typename Coupling = std::tuple<>> struct InternalVariableSystem : public SystemBase { using SystemBase::SystemBase; @@ -87,18 +87,13 @@ struct InternalVariableSystem : public SystemBase { auto captured_time_rule = internal_variable_time_rule; auto captured_coupling = coupling; internal_variable_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_time_rule, t_info, - [&](auto alpha_current, auto alpha_dot, auto... coupling_and_params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - auto residual_val = - evolution_law(t_info, get(alpha_current), get(alpha_dot), interpolated_params...); - tensor flux{}; - return smith::tuple{residual_val, flux}; - }, - coupling_and_params...); + return detail::applyTimeRuleAndCoupling( + *captured_time_rule, *captured_coupling, t_info, + [&](auto alpha_current, auto alpha_dot, auto... interpolated_params) { + auto residual_val = + evolution_law(t_info, get(alpha_current), get(alpha_dot), interpolated_params...); + tensor flux{}; + return smith::tuple{residual_val, flux}; }, raw_args...); }); @@ -117,7 +112,7 @@ struct InternalVariableSystem : public SystemBase { * @return PhysicsFields carrying (state_solve_state, state) field tokens * suitable for injection into another physics system. */ -template +template auto registerInternalVariableFields(std::shared_ptr field_store, FieldType... parameter_types) { @@ -134,16 +129,16 @@ auto registerInternalVariableFields(std::shared_ptr field_store, (prefix_param(parameter_types), ...); } - return PhysicsFields{ + return PhysicsFields{ field_store, FieldType(field_store->prefix("state_solve_state")), FieldType(field_store->prefix("state"))}; } -template +template /// @brief Backward-compatible alias for `registerInternalVariableFields`. auto registerStateVariableFields(std::shared_ptr field_store, FieldType... parameter_types) { - return registerInternalVariableFields(field_store, parameter_types...); + return registerInternalVariableFields(field_store, parameter_types...); } // --------------------------------------------------------------------------- @@ -159,7 +154,7 @@ namespace detail { * @brief Internal builder for an internal-variable system after public registration and coupling collection. */ template - requires detail::is_coupling_params_v + requires detail::is_coupling_packs_v auto buildInternalVariableSystemImpl(std::shared_ptr field_store, const Coupling& coupling, std::shared_ptr solver) { @@ -175,7 +170,8 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co std::string internal_variable_residual_name = field_store->prefix("state_residual"); auto internal_variable_weak_form = detail::buildWeakFormWithCoupling( - field_store, internal_variable_residual_name, state_type.name, state_type, state_old_type, coupling.fields); + field_store, internal_variable_residual_name, state_type.name, state_type, state_old_type, + detail::flattenCouplingFields(coupling)); auto sys = std::make_shared(field_store, solver, std::vector>{internal_variable_weak_form}); @@ -194,11 +190,13 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co * * The time rule is deduced from SelfFields::time_rule_type. */ -template +template requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields, const Trailing&... trailing) { + constexpr int dim = SelfFields::dim; + using StateSpace = typename std::tuple_element_t<0, decltype(self_fields.fields)>::space_type; using InternalVarTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; detail::registerParamsIfNeeded(field_store, trailing...); diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 2949f5e658..88fb585600 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -83,9 +83,9 @@ TEST(CouplingTimeRuleInterpolation, PreservesForeignPacksWithSameTimeRuleType) auto field_store = std::make_shared(mesh, 100, ""); using ScalarSpace = H1; - PhysicsFields thermal_a{ + PhysicsFields thermal_a{ field_store, FieldType("temperature_a_solve_state"), FieldType("temperature_a")}; - PhysicsFields thermal_b{ + PhysicsFields thermal_b{ field_store, FieldType("temperature_b_solve_state"), FieldType("temperature_b")}; auto same_rule_coupling = detail::collectCouplingFields(field_store, couplingFields(thermal_a, thermal_b)); @@ -178,13 +178,11 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields), param_fields); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, + solid_fields, couplingFields(thermal_fields), param_fields); - auto thermal_system = - buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, - thermal_fields, couplingFields(solid_fields), param_fields); + auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); auto physics = makeDifferentiablePhysics(coupled_system, "coupled_physics"); @@ -215,13 +213,11 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields), param_fields); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, + solid_fields, couplingFields(thermal_fields), param_fields); - auto thermal_system = - buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, - thermal_fields, couplingFields(solid_fields), param_fields); + auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, @@ -283,10 +279,10 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) .max_iterations = 12, .max_line_search_iterations = 6}; - auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields), param_fields); - auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, - couplingFields(solid_fields), param_fields); + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, + couplingFields(thermal_fields), param_fields); + auto thermal_system = + buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_solver = std::make_shared(10); coupled_solver->addSubsystemSolver({0}, buildNonlinearBlockSolver(solid_nonlin_opts, solid_lin_opts, *mesh_)); @@ -365,11 +361,10 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(makeSolver(mech_nonlin_opts, mech_lin_opts), - SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields)); - auto thermal_system = buildThermalSystem( - makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); + auto solid_system = buildSolidMechanicsSystem(makeSolver(mech_nonlin_opts, mech_lin_opts), SolidMechanicsOptions{}, + solid_fields, couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem(makeSolver(therm_nonlin_opts, therm_lin_opts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields)); auto coupled_system = combineSystems(solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -395,10 +390,9 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields)); - auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, - couplingFields(solid_fields)); + auto solid_system = + buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto coupled_system = combineSystems(solver_ptr, solid_system, thermal_system); thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; @@ -418,8 +412,7 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); EXPECT_TRUE(field_store_->hasField(field_store_->prefix("stress"))); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - solid_opts, solid_fields); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields); ASSERT_EQ(solid_system->post_solve_systems.size(), 1u); constexpr double E = 100.0; @@ -452,8 +445,8 @@ TEST_F(ThermoMechanicsMeshFixture, StressOutputRegistrationDisabledByDefault) auto solid_fields = registerSolidMechanicsFields(field_store_); EXPECT_FALSE(field_store_->hasField(field_store_->prefix("stress"))); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields); + auto solid_system = + buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields); EXPECT_TRUE(solid_system->post_solve_systems.empty()); } @@ -464,10 +457,10 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesPostSolveSystems) auto solid_fields = registerSolidMechanicsFields(field_store_, solid_opts); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem( - makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields, couplingFields(thermal_fields)); - auto thermal_system = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), solid_opts, solid_fields, + couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields)); auto combined_system = combineSystems(solid_system, thermal_system); @@ -482,11 +475,10 @@ TEST_F(ThermoMechanicsMeshFixture, CombinedSystemCarriesCycleZeroSystems) auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); - auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), - SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields)); - auto thermal_system = buildThermalSystem( - makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); + auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, + solid_fields, couplingFields(thermal_fields)); + auto thermal_system = buildThermalSystem(makeSolver(newtonNonlinOpts, directLinOpts), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields)); auto combined_system = combineSystems(solid_system, thermal_system); diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 900b5460ba..06ab33405a 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -96,15 +96,22 @@ struct SolidMechanicsMeshFixture : public testing::Test { // produce a non-empty cycle_zero_systems; rules that don't (QuasiStatic) produce empty. TEST_F(SolidMechanicsMeshFixture, CycleZeroSystemPresenceMatchesRuleContract) { + auto makeSolver = [&] { + return std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + }; { - auto solid_system = buildSolidMechanicsSystem( - solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.prefix = "impl"}, mesh); + auto field_store = fieldStore(mesh, 200, "impl"); + auto solid_fields = + registerSolidMechanicsFields(field_store); + auto solid_system = buildSolidMechanicsSystem(makeSolver(), SolidMechanicsOptions{}, solid_fields); EXPECT_EQ(solid_system->cycle_zero_systems.size(), 1u) << "ImplicitNewmark should emit a cycle-zero initial acceleration solve"; } { - auto solid_system = buildSolidMechanicsSystem( - solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.prefix = "qs"}, mesh); + auto field_store = fieldStore(mesh, 200, "qs"); + auto solid_fields = + registerSolidMechanicsFields(field_store); + auto solid_system = buildSolidMechanicsSystem(makeSolver(), SolidMechanicsOptions{}, solid_fields); EXPECT_TRUE(solid_system->cycle_zero_systems.empty()) << "QuasiStatic has no initial acceleration solve"; } } @@ -118,7 +125,7 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverUsesOwnedSingleStepPolicy) auto field_store = std::make_shared(mesh, 100, "cycle_zero_policy"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto solid_system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); + auto solid_system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields); ASSERT_EQ(solid_system->cycle_zero_systems.size(), 1u); const auto& cz = solid_system->cycle_zero_systems[0]; @@ -132,7 +139,7 @@ TEST_F(SolidMechanicsMeshFixture, CycleZeroSolverFallbackBuildsWithoutMainSolver auto field_store = std::make_shared(mesh, 100, "cycle_zero_fallback"); using TimeRule = ImplicitNewmarkSecondOrderTimeIntegrationRule; auto solid_fields = registerSolidMechanicsFields(field_store); - auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); + auto solid_system = buildSolidMechanicsSystem(nullptr, SolidMechanicsOptions{}, solid_fields); ASSERT_EQ(solid_system->cycle_zero_systems.size(), 1u); const auto& cz = solid_system->cycle_zero_systems[0]; @@ -171,9 +178,13 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) auto param_fields = registerParameterFields(FieldType("bulk"), FieldType("shear")); - auto solid_system = buildSolidMechanicsSystem( - solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{.enable_stress_output = true}, mesh, - param_fields); + auto field_store = fieldStore(mesh); + SolidMechanicsOptions solid_opts{.enable_stress_output = true}; + auto solid_fields = + registerSolidMechanicsFields(field_store, solid_opts); + auto solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solid_system = buildSolidMechanicsSystem(solver, solid_opts, solid_fields, param_fields); static constexpr double gravity = -9.0; @@ -271,8 +282,12 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditions) { SolidMechanicsOptions solid_options{.enable_stress_output = true}; - auto solid_system = buildSolidMechanicsSystem( - solid_nonlinear_opts, solid_linear_options, solid_options, mesh); + auto field_store = fieldStore(mesh); + auto solid_fields = registerSolidMechanicsFields( + field_store, solid_options); + auto solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solid_system = buildSolidMechanicsSystem(solver, solid_options, solid_fields); static constexpr double gravity = -9.0; double E = 100.0; @@ -331,8 +346,11 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr("bulk"), FieldType("shear")); - auto solid_system = buildSolidMechanicsSystem( - solid_nonlinear_opts, solid_linear_options, SolidMechanicsOptions{}, field_store, param_fields); + auto solid_fields = + registerSolidMechanicsFields(field_store); + auto solver = + std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solid_system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields, param_fields); auto physics = makeDifferentiablePhysics(solid_system, physics_name); auto bcs = solid_system->disp_bc; diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index d19a14b938..7e37b31980 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -111,7 +111,7 @@ std::shared_ptr makeSystemSolver(const Mesh& mesh) auto registerFields(const std::shared_ptr& field_store) { return std::tuple{registerSolidMechanicsFields(field_store), - registerInternalVariableFields(field_store)}; + registerInternalVariableFields(field_store)}; } template @@ -145,9 +145,9 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); - auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, couplingFields(internal_variable_fields)); - auto internal_variable_system = buildInternalVariableSystem( + auto internal_variable_system = buildInternalVariableSystem( internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); @@ -183,9 +183,9 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); - auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, couplingFields(internal_variable_fields)); - auto internal_variable_system = buildInternalVariableSystem( + auto internal_variable_system = buildInternalVariableSystem( internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(staggered_solver, solid_system, internal_variable_system); @@ -208,9 +208,9 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "body_force_test_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); - auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, + auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, couplingFields(internal_variable_fields)); - auto internal_variable_system = buildInternalVariableSystem( + auto internal_variable_system = buildInternalVariableSystem( internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 403be1653f..247aff38a0 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -49,8 +49,10 @@ struct ThermalStaticFixture : public testing::Test { solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); - auto thermal_system = buildThermalSystem<2, temp_order, QuasiStaticFirstOrderTimeIntegrationRule>( - solver_options, linear_options, ThermalOptions{}, mesh); + auto field_store = fieldStore(mesh); + auto thermal_fields = registerThermalFields<2, temp_order, QuasiStaticFirstOrderTimeIntegrationRule>(field_store); + auto solver = std::make_shared(buildNonlinearBlockSolver(solver_options, linear_options, *mesh)); + auto thermal_system = buildThermalSystem(solver, ThermalOptions{}, thermal_fields); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -139,8 +141,10 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) FieldType> conductivity_param("conductivity"); auto param_fields = registerParameterFields(conductivity_param); - auto thermal_system = buildThermalSystem<2, 1, QuasiStaticFirstOrderTimeIntegrationRule>( - solver_options, linear_options, ThermalOptions{}, mesh, param_fields); + auto field_store = fieldStore(mesh); + auto thermal_fields = registerThermalFields<2, 1, QuasiStaticFirstOrderTimeIntegrationRule>(field_store); + auto solver = std::make_shared(buildNonlinearBlockSolver(solver_options, linear_options, *mesh)); + auto thermal_system = buildThermalSystem(solver, ThermalOptions{}, thermal_fields, param_fields); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 00f359db1b..5ca128b7ad 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -152,20 +152,20 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // the displacement field tokens available for thermal coupling. auto solid_fields = registerSolidMechanicsFields(field_store); auto thermal_fields = registerThermalFields(field_store); - auto internal_variable_fields = registerInternalVariableFields(field_store); + auto internal_variable_fields = registerInternalVariableFields(field_store); // Phase 2: build each system. // Solid receives thermal and alpha coupling. - auto solid_system = buildSolidMechanicsSystem( + auto solid_system = buildSolidMechanicsSystem( std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields, internal_variable_fields)); auto thermal_system = - buildThermalSystem(std::make_shared(thermal_block_solver), ThermalOptions{}, + buildThermalSystem(std::make_shared(thermal_block_solver), ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto internal_variable_system = - buildInternalVariableSystem(std::make_shared(internal_variable_block_solver), + buildInternalVariableSystem(std::make_shared(internal_variable_block_solver), internal_variable_fields, couplingFields(solid_fields)); // Phase 3: register material integrands. diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 70bfb74437..b53f5170a5 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -39,7 +39,7 @@ namespace smith { * before user parameter_space fields. */ template >> + typename Coupling = std::tuple<>> struct ThermalSystem : public SystemBase { using SystemBase::SystemBase; @@ -74,17 +74,12 @@ struct ThermalSystem : public SystemBase { auto captured_coupling = coupling; thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_temp_rule, t_info, - [&](auto T_current, auto T_dot, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - auto [heat_capacity, heat_flux] = - material(t_info, get(T_current), get(T_current), interpolated_params...); - return smith::tuple{heat_capacity * get(T_dot), -heat_flux}; - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_temp_rule, *captured_coupling, t_info, + [&](auto T_current, auto T_dot, auto... interpolated_params) { + auto [heat_capacity, heat_flux] = + material(t_info, get(T_current), get(T_current), interpolated_params...); + return smith::tuple{heat_capacity * get(T_dot), -heat_flux}; }, raw_args...); }); @@ -111,17 +106,12 @@ struct ThermalSystem : public SystemBase { auto captured_coupling = coupling; thermal_weak_form->addBodyIntegral(domain_name, [=](auto t_info, auto /*X*/, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_temp_rule, t_info, - [&](auto T_current, auto T_dot, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - auto [heat_capacity, heat_flux, heat_source] = - material(t_info, get(T_current), get(T_current), interpolated_params...); - return smith::tuple{heat_capacity * get(T_dot) - heat_source, -heat_flux}; - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_temp_rule, *captured_coupling, t_info, + [&](auto T_current, auto T_dot, auto... interpolated_params) { + auto [heat_capacity, heat_flux, heat_source] = + material(t_info, get(T_current), get(T_current), interpolated_params...); + return smith::tuple{heat_capacity * get(T_dot) - heat_source, -heat_flux}; }, raw_args...); }); @@ -138,15 +128,10 @@ struct ThermalSystem : public SystemBase { auto captured_rule = temperature_time_rule; auto captured_coupling = coupling; thermal_weak_form->addBodySource(domain_name, [=](auto t_info, auto X, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto T_current, auto /*T_dot*/, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - return source_function(t_info.time(), X, T_current, interpolated_params...); - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto T_current, auto /*T_dot*/, auto... interpolated_params) { + return source_function(t_info.time(), X, T_current, interpolated_params...); }, raw_args...); }); @@ -163,15 +148,10 @@ struct ThermalSystem : public SystemBase { auto captured_rule = temperature_time_rule; auto captured_coupling = coupling; thermal_weak_form->addBoundaryFlux(boundary_name, [=](auto t_info, auto X, auto n, auto... raw_args) { - return detail::applyTimeRuleToPrefix( - *captured_rule, t_info, - [&](auto T_current, auto /*T_dot*/, auto... params) { - return detail::applyCouplingTimeRules( - *captured_coupling, t_info, - [&](auto... interpolated_params) { - return flux_function(t_info.time(), X, n, T_current, interpolated_params...); - }, - params...); + return detail::applyTimeRuleAndCoupling( + *captured_rule, *captured_coupling, t_info, + [&](auto T_current, auto /*T_dot*/, auto... interpolated_params) { + return flux_function(t_info.time(), X, n, T_current, interpolated_params...); }, raw_args...); }); @@ -213,7 +193,7 @@ auto registerThermalFields(std::shared_ptr field_store, field_store->addIndependent(temperature_type, temperature_time_rule_ptr); field_store->addDependent(temperature_type, FieldStore::TimeDerivative::VAL, "temperature"); - return PhysicsFields, H1>{ + return PhysicsFields, H1>{ field_store, FieldType>(field_store->prefix("temperature_solve_state")), FieldType>(field_store->prefix("temperature"))}; } @@ -226,7 +206,7 @@ auto registerThermalFields(std::shared_ptr field_store, namespace detail { template - requires detail::is_coupling_params_v + requires detail::is_coupling_packs_v auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupling& coupling, std::shared_ptr solver, const ThermalOptions& /*options*/) { @@ -242,7 +222,8 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl std::string thermal_flux_name = field_store->prefix("thermal_flux"); auto thermal_weak_form = detail::buildWeakFormWithCoupling( - field_store, thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, coupling.fields); + field_store, thermal_flux_name, temperature_type.name, temperature_type, temperature_old_type, + detail::flattenCouplingFields(coupling)); auto sys = std::make_shared(field_store, solver, std::vector>{thermal_weak_form}); @@ -263,15 +244,17 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl * * Usage: * @code - * auto thermal_system = buildThermalSystem( + * auto thermal_system = buildThermalSystem( * solver, opts, thermal_fields, couplingFields(solid_fields)); * @endcode */ -template +template requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, const SelfFields& self_fields, const Trailing&... trailing) { + constexpr int dim = SelfFields::dim; + constexpr int temp_order = SelfFields::order; using TemperatureTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; detail::registerParamsIfNeeded(field_store, trailing...); @@ -279,35 +262,4 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } -/** - * @brief Build a ThermalSystem from solver options and either a FieldStore or Mesh. - * - * Pass a FieldStore for coupled/composed systems. Pass a Mesh for a standalone thermal system. - * - * Usage: - * @code - * auto thermal_system = buildThermalSystem( - * nonlin_opts, lin_opts, opts, field_store, couplingFields(solid_fields), param_fields); - * auto standalone_thermal = buildThermalSystem( - * nonlin_opts, lin_opts, opts, mesh, param_fields); - * @endcode - */ -template - requires((detail::is_field_store_ptr_v || detail::is_mesh_ptr_v) && - detail::trailing_coupling_args_valid_v) -auto buildThermalSystem(const NonlinearSolverOptions& nonlinear_options, const LinearSolverOptions& linear_options, - const ThermalOptions& options, FieldStoreOrMesh field_store_or_mesh, - const Trailing&... trailing) -{ - if constexpr (detail::is_mesh_ptr_v) { - static_assert((!detail::is_coupling_fields_v && ...), - "Pass a FieldStore when building a system coupled to couplingFields(...)."); - } - auto field_store = detail::getOrCreateFieldStore(field_store_or_mesh); - auto self_fields = registerThermalFields(field_store, options); - auto solver = std::make_shared( - buildNonlinearBlockSolver(nonlinear_options, linear_options, *field_store->getMesh())); - return buildThermalSystem(solver, options, self_fields, trailing...); -} - } // namespace smith From 0f70b758abf49a0e40601e1c58947b27a987ca61 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 12 May 2026 11:57:03 -0600 Subject: [PATCH 58/67] Clean up the sphinx a bit. --- .../composable_solid_mechanics.cpp | 29 +++++---- .../composable_thermo_mechanics.cpp | 6 +- .../composable_thermo_mechanics_advanced.cpp | 62 +++++++++---------- .../composable_solid_mechanics_tutorial.rst | 14 ++++- ...ble_thermo_mechanics_advanced_tutorial.rst | 13 ++-- .../test_solid_static_with_internal_vars.cpp | 18 +++--- ...st_thermo_mechanics_with_internal_vars.cpp | 13 ++-- 7 files changed, 85 insertions(+), 70 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index e54584c90f..6d994f686a 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -90,16 +90,17 @@ int main(int argc, char* argv[]) .print_level = 0}; smith::SolidMechanicsOptions output_options{.enable_stress_output = true, .output_cauchy_stress = true}; - auto param_fields = smith::registerParameterFields(smith::FieldType>("youngs_modulus")); - // _solver_end - // _build_start - auto field_store = smith::fieldStore(mesh); + auto field_store = std::make_shared(mesh, 100); auto solid_fields = smith::registerSolidMechanicsFields( field_store, output_options); - auto solver = std::make_shared( - smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); + auto param_fields = smith::registerParameterFields(smith::FieldType>("youngs_modulus")); + // _solver_end + + // _build_start + auto solver = + std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); auto solid_system = smith::buildSolidMechanicsSystem(solver, output_options, solid_fields, param_fields); constexpr double E = 100.0; @@ -119,12 +120,15 @@ int main(int argc, char* argv[]) traction[0] = -0.01; return traction; }); + // _bc_end + + // _ic_start + auto physics = smith::makeDifferentiablePhysics(solid_system, "composable_solid_mechanics"); if (solid_system->cycle_zero_systems.empty()) { throw std::runtime_error("Expected cycle-zero solve for implicit dynamics."); } - auto physics = smith::makeDifferentiablePhysics(solid_system, "composable_solid_mechanics"); physics->getFieldParam("param_youngs_modulus").get()->setFromFieldFunction([=](smith::tensor) { return E; }); @@ -146,11 +150,7 @@ int main(int argc, char* argv[]) physics->getInitialFieldState("acceleration").get()->setFromFieldFunction([](smith::tensor) { return smith::tensor{}; }); - - auto writer = - smith::createParaviewWriter(*mesh, physics->getFieldStatesAndParamStates(), "paraview_composable_solid_mechanics", - smith::ParaviewWriter::Options{.write_duals = false}); - // _bc_end + // _ic_end // _run_start using DispSpace = smith::H1; @@ -201,9 +201,12 @@ int main(int argc, char* argv[]) // _run_end // _output_start + auto writer = + smith::createParaviewWriter(*mesh, physics->getFieldStatesAndParamStates(), "paraview_composable_solid_mechanics", + smith::ParaviewWriter::Options{.write_duals = false}); writer.write(physics->cycle(), physics->time(), physics->getFieldStatesAndParamStates()); std::cout << "ParaView output: paraview_composable_solid_mechanics\n"; // _output_end return 0; -} +} \ No newline at end of file diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index ff74391257..777dd84737 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -72,10 +72,10 @@ int main(int argc, char* argv[]) auto thermal_solver = std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); - auto solid_system = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, - solid_fields, smith::couplingFields(thermal_fields)); + auto solid_system = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, solid_fields, + smith::couplingFields(thermal_fields)); auto thermal_system = smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, - smith::couplingFields(solid_fields)); + smith::couplingFields(solid_fields)); auto coupled_system = smith::combineSystems(solid_system, thermal_system); diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 01e60ac084..edd5597eb2 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -56,6 +56,18 @@ int main(int argc, char* argv[]) // _mesh_end // _solver_start + smith::LinearSolverOptions coupled_linear{.linear_solver = smith::LinearSolver::SuperLU, + .relative_tol = 1e-8, + .absolute_tol = 1e-10, + .max_iterations = 200, + .print_level = 0}; + smith::NonlinearSolverOptions coupled_nonlin{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, + .relative_tol = 1e-8, + .absolute_tol = 1e-8, + .max_iterations = 12, + .max_line_search_iterations = 6, + .print_level = 0}; + auto field_store = std::make_shared(mesh, 200); smith::SolidMechanicsOptions solid_options{.enable_stress_output = true, .output_cauchy_stress = true}; @@ -64,19 +76,34 @@ int main(int argc, char* argv[]) auto thermal_fields = smith::registerThermalFields(field_store); auto param_fields = smith::registerParameterFields(smith::FieldType>("thermal_expansion_scaling")); + // _solver_end + // _build_start auto solid_system = smith::buildSolidMechanicsSystem(nullptr, solid_options, solid_fields, - smith::couplingFields(thermal_fields), param_fields); + smith::couplingFields(thermal_fields), param_fields); auto thermal_system = smith::buildThermalSystem(nullptr, smith::ThermalOptions{}, thermal_fields, - smith::couplingFields(solid_fields), param_fields); + smith::couplingFields(solid_fields), param_fields); smith::thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{ 1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); - field_store->getParameterFields()[0].get()->setFromFieldFunction([](smith::tensor) { return 1.0; }); + auto coupled_solver = std::make_shared(10); + coupled_solver->addSubsystemSolver({0, 1}, smith::buildNonlinearBlockSolver(coupled_nonlin, coupled_linear, *mesh), + 1.0); + + auto coupled_system = smith::combineSystems(coupled_solver, solid_system, thermal_system); + std::string physics_name = "composable_thermo_mechanics_advanced"; + auto physics = smith::makeDifferentiablePhysics(coupled_system, physics_name); + auto output_states = field_store->getOutputFieldStates(); + auto output_writer = + smith::createParaviewWriter(*mesh, output_states, "paraview_composable_thermo_mechanics_advanced", + smith::ParaviewWriter::Options{.write_duals = false}); + // _build_end // _bc_start + field_store->getParameterFields()[0].get()->setFromFieldFunction([](smith::tensor) { return 1.0; }); + solid_system->setDisplacementBC(mesh->domain("left")); thermal_system->setTemperatureBC(mesh->domain("left"), [](auto, auto) { return 1.0; }); thermal_system->setTemperatureBC(mesh->domain("right"), [](auto, auto) { return 0.0; }); @@ -91,33 +118,6 @@ int main(int argc, char* argv[]) thermal_system->addHeatSource(mesh->entireBodyName(), [](auto, auto... /*unused*/) { return 0.1; }); // _bc_end - smith::LinearSolverOptions coupled_linear{.linear_solver = smith::LinearSolver::SuperLU, - .relative_tol = 1e-8, - .absolute_tol = 1e-10, - .max_iterations = 200, - .print_level = 0}; - smith::NonlinearSolverOptions coupled_nonlin{.nonlin_solver = smith::NonlinearSolver::NewtonLineSearch, - .relative_tol = 1e-8, - .absolute_tol = 1e-8, - .max_iterations = 12, - .max_line_search_iterations = 6, - .print_level = 0}; - // _solver_end - - // _build_start - auto coupled_solver = std::make_shared(10); - coupled_solver->addSubsystemSolver({0, 1}, smith::buildNonlinearBlockSolver(coupled_nonlin, coupled_linear, *mesh), - 1.0); - - auto coupled_system = smith::combineSystems(coupled_solver, solid_system, thermal_system); - std::string physics_name = "composable_thermo_mechanics_advanced"; - auto physics = smith::makeDifferentiablePhysics(coupled_system, physics_name); - auto output_states = field_store->getOutputFieldStates(); - auto output_writer = - smith::createParaviewWriter(*mesh, output_states, "paraview_composable_thermo_mechanics_advanced", - smith::ParaviewWriter::Options{.write_duals = false}); - // _build_end - // _qoi_start auto fetch_qoi_fields = [&]() { return std::vector{field_store->getField("displacement"), field_store->getField("temperature")}; @@ -170,4 +170,4 @@ int main(int argc, char* argv[]) // _sensitivity_end return 0; -} +} \ No newline at end of file diff --git a/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst b/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst index 22508f5fd3..33fb4d348f 100644 --- a/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst +++ b/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst @@ -54,8 +54,7 @@ System Build and Material Setup The solid system is built from the registered field pack. The material wrapper adapts the standard Neo-Hookean material to the ``TimeInfo``-based interface, -pulling bulk and shear response from the Young's modulus parameter field. The -example also seeds non-zero initial displacement and velocity fields. +pulling bulk and shear response from the Young's modulus parameter field. .. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp :start-after: _build_start @@ -73,6 +72,17 @@ The left boundary uses a component-wise Dirichlet condition, fixing only the :end-before: _bc_end :language: C++ +Physics Creation and Initial Conditions +--------------------------------------- + +The differentiable physics object is constructed from the system. Then the example +seeds non-zero initial displacement and velocity fields and sets the parameter field. + +.. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp + :start-after: _ic_start + :end-before: _ic_end + :language: C++ + Advance, Sensitivities, and Reactions ------------------------------------- diff --git a/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst b/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst index 3962e0edd5..d9318728cf 100644 --- a/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst +++ b/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst @@ -34,16 +34,19 @@ Mesh and Field Setup :end-before: _mesh_end :language: C++ +Solver Config and Field Registration +------------------------------------ + +This example uses a single block solver combining Newton line search for the non-linear +solve and SuperLU for the linear solve. + .. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp :start-after: _solver_start :end-before: _solver_end :language: C++ -Staged Solver and Coupled Build -------------------------------- - -This example uses a custom staggered ``SystemSolver``. The solid subsystem uses -trust-region iterations, while the thermal subsystem uses Newton line search. +System Build and Coupling +------------------------- .. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp :start-after: _build_start diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 7e37b31980..6d402d07b1 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -146,9 +146,9 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - couplingFields(internal_variable_fields)); - auto internal_variable_system = buildInternalVariableSystem( - internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); + couplingFields(internal_variable_fields)); + auto internal_variable_system = + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); @@ -184,9 +184,9 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) auto field_store = std::make_shared(mesh, 100, "solid_staggered_relaxation_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - couplingFields(internal_variable_fields)); - auto internal_variable_system = buildInternalVariableSystem( - internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); + couplingFields(internal_variable_fields)); + auto internal_variable_system = + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(staggered_solver, solid_system, internal_variable_system); @@ -209,9 +209,9 @@ TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) auto field_store = std::make_shared(mesh, 100, "body_force_test_"); auto [solid_fields, internal_variable_fields] = registerFields(field_store); auto solid_system = buildSolidMechanicsSystem(solid_solver, SolidMechanicsOptions{}, solid_fields, - couplingFields(internal_variable_fields)); - auto internal_variable_system = buildInternalVariableSystem( - internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); + couplingFields(internal_variable_fields)); + auto internal_variable_system = + buildInternalVariableSystem(internal_variable_solver, internal_variable_fields, couplingFields(solid_fields)); auto system = combineSystems(coupled_solver, solid_system, internal_variable_system); diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp index 5ca128b7ad..8ca47787f8 100644 --- a/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics_with_internal_vars.cpp @@ -156,17 +156,16 @@ TEST_F(ThreeSystemMeshFixture, StronglyCoupledThreeSystems) // Phase 2: build each system. // Solid receives thermal and alpha coupling. - auto solid_system = buildSolidMechanicsSystem( - std::make_shared(solid_block_solver), SolidMechanicsOptions{}, solid_fields, - couplingFields(thermal_fields, internal_variable_fields)); + auto solid_system = + buildSolidMechanicsSystem(std::make_shared(solid_block_solver), SolidMechanicsOptions{}, + solid_fields, couplingFields(thermal_fields, internal_variable_fields)); - auto thermal_system = - buildThermalSystem(std::make_shared(thermal_block_solver), ThermalOptions{}, - thermal_fields, couplingFields(solid_fields)); + auto thermal_system = buildThermalSystem(std::make_shared(thermal_block_solver), ThermalOptions{}, + thermal_fields, couplingFields(solid_fields)); auto internal_variable_system = buildInternalVariableSystem(std::make_shared(internal_variable_block_solver), - internal_variable_fields, couplingFields(solid_fields)); + internal_variable_fields, couplingFields(solid_fields)); // Phase 3: register material integrands. auto material = SimpleThermoelasticMaterial{}; From ebc492ede703ae36e639af3d2c655447a70de898 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 12 May 2026 21:42:24 -0600 Subject: [PATCH 59/67] Fix docs. --- plan.md | 81 ------------------- .../coupling_params.hpp | 64 +++++++++++---- .../differentiable_numerics/field_store.hpp | 3 +- .../thermal_system.hpp | 1 + src/smith/physics/common.hpp | 5 +- src/smith/physics/functional_objective.hpp | 2 + src/smith/physics/functional_weak_form.hpp | 3 + 7 files changed, 58 insertions(+), 101 deletions(-) delete mode 100644 plan.md diff --git a/plan.md b/plan.md deleted file mode 100644 index d9b9577526..0000000000 --- a/plan.md +++ /dev/null @@ -1,81 +0,0 @@ -# Refactoring Plan: Compile Time Reduction & Interface Unification - -## 1. Remove `DependsOn` from WeakForm Interfaces [COMPLETED] -- **Status:** **DONE**. Removed `DependsOn` overloads and template parameters from `FunctionalWeakForm`, `FunctionalObjective`, `SolidMechanicsSystem`, and `ThermalSystem`. This dramatically reduced the number of AST instantiations required for AD derivatives. -- **Action Taken:** By default, all integrals now depend on the full parameter pack (`DependsOn<0, 1, ..., N>{}`). The explicit template instantiation burden is completely removed from the user API. - -## 2. Eliminate `cycle_zero_weak_form` [COMPLETED] -- **Status:** **DONE**. Deleted `cycle_zero_solid_weak_form` entirely from the SolidMechanics system. -- **Action Taken:** The `MultiphysicsTimeIntegrator` and `SystemBase` now evaluate the cycle-zero acceleration residual directly through the main `solid_weak_form` by properly packing the state variables as `(u, u, v, a)` so the primary time-integration rule interpolates them correctly as initial conditions. - ---- - -## 3. Simplify Deep Type-Level Rewriting (Coupling Types) -- **Problem:** Types like `AppendCouplingToParams`, `TimeRuleParamsHelper`, and operations in `combined_system.hpp` rely on deep, recursive template metaprogramming which bogs down the compiler's frontend. -- **Goal:** Move away from recursive template structs (`struct Helper { ... }`) towards flat parameter pack expansions and `decltype(std::tuple_cat(...))` which are significantly faster for the compiler to evaluate. -- **Proposed Architecture:** We can replace much of `coupling_params.hpp` logic by capturing foreign physics fields directly into a flattened `std::tuple` of descriptors at the system construction phase, completely avoiding the need to re-parse the types during weak form evaluations. - -## 4. Unified Time Discretization for Coupled Physics -- **Problem:** Currently, self-fields are beautifully interpolated (e.g., `u_new, u_old, v_old, a_old` becomes `u, v, a` inside the user lambda), but coupled fields are passed raw (e.g., `temp_new, temp_old` are passed directly in `auto... params`). -- **Action:** Transform the time discretization for *all* coupled physics before calling the user closure. If a thermal system is coupled to a solid mechanics system, the solid system's lambdas should see `(t, t_dot)` rather than `(t_new, t_old)`. - -### Analysis & Example C++ Code for Steps 3 & 4 -To solve both Step 3 and Step 4 simultaneously without introducing massive compile-time overhead, we can create a lightweight, flat **Coupled State Evaluator**. Instead of recursively tearing apart type-lists, we will store a tuple of the active Time Rules for all coupled physics. - -When a weak form evaluates, it receives a flat `auto... raw_args` representing the raw FE states (new, old, old_v, etc.) for *all* physics concatenated together. We will use a compile-time index sequence to partition this flat pack, feed the partitions into their respective time rules, and then flat-unpack the interpolated results into the user's lambda. - -**Example implementation approach:** - -```cpp -#include -#include - -// Helper to extract a sub-pack from a tuple of arguments -template -constexpr auto extract_args_impl(const Tuple& t, std::index_sequence) { - return std::make_tuple(std::get(t)...); -} - -// Given a flat tuple of raw arguments, extract `Count` arguments starting at `Offset` -template -constexpr auto extract_args(const Tuple& t) { - return extract_args_impl(t, std::make_index_sequence{}); -} - -// The Unified Evaluator -template -struct MultiphysicsEvaluator { - std::tuple rules; - - template - auto evaluate(UserLambda&& user_fn, const TInfo& t_info, const XType& X, const RawArgs&... raw_args) { - auto flat_raw_args = std::forward_as_tuple(raw_args...); - - // This is pseudo-code. In practice, we would use a compile-time fold over the rules - // to automatically compute the offsets based on `TimeRule::num_states`. - - // E.g., Physics 0 (Solid) takes 4 states (u, u_old, v_old, a_old) - auto solid_raw = extract_args<0, 4>(flat_raw_args); - auto solid_interp = std::apply([&](auto... args){ - return std::get<0>(rules)->interpolate(t_info, args...); - }, solid_raw); - - // E.g., Physics 1 (Thermal) takes 2 states (T, T_old) - auto thermal_raw = extract_args<4, 2>(flat_raw_args); - auto thermal_interp = std::apply([&](auto... args){ - return std::get<1>(rules)->interpolate(t_info, args...); - }, thermal_raw); - - // Finally, expand both interpolated tuples into the user lambda - // User sees: lambda(t_info, X, u, v, a, T, T_dot) - return std::apply([&](auto... all_interp_args) { - return user_fn(t_info, X, all_interp_args...); - }, std::tuple_cat(solid_interp, thermal_interp)); - } -}; -``` - -**Why this reduces compile time:** -1. We rely on `std::tuple` and `std::apply`, which modern compilers highly optimize via built-in intrinsics. -2. The flat `raw_args` list is passed exactly as it is received from `FunctionalWeakForm`. We do not rewrite the `Parameters<...>` type recursively; we only slice the tuple at evaluation time. -3. AD instantiation stays confined to the `evaluate` wrapper, meaning the user's `UserLambda` is only instantiated once with the fully interpolated, unified types. \ No newline at end of file diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 6041c53474..f0a99fe538 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -28,7 +28,7 @@ namespace smith { /// Sentinel: no time integration rule (used for parameter-only packs). struct NoTimeRule { - static constexpr int num_states = 0; + static constexpr int num_states = 0; ///< Number of states. }; /** @@ -40,13 +40,14 @@ struct NoTimeRule { */ template struct PhysicsFields { - using time_rule_type = TimeRule; - static constexpr int dim = Dim; - static constexpr int order = Order; - static constexpr std::size_t num_fields = sizeof...(Spaces); - std::shared_ptr field_store; - std::tuple...> fields; - + using time_rule_type = TimeRule; ///< The time integration rule type. + static constexpr int dim = Dim; ///< Spatial dimension. + static constexpr int order = Order; ///< Spatial order. + static constexpr std::size_t num_fields = sizeof...(Spaces); ///< Number of fields. + std::shared_ptr field_store; ///< Pointer to the field store. + std::tuple...> fields; ///< The fields. + + /// Constructor PhysicsFields(std::shared_ptr fs, FieldType... f) : field_store(std::move(fs)), fields(std::move(f)...) { @@ -61,12 +62,14 @@ struct PhysicsFields { */ template struct CouplingParams { - using time_rule_type = NoTimeRule; - static constexpr std::size_t num_fields = sizeof...(Spaces); - std::tuple...> fields; + using time_rule_type = NoTimeRule; ///< Time rule type (none). + static constexpr std::size_t num_fields = sizeof...(Spaces); ///< Number of fields. + std::tuple...> fields; ///< The fields. + /// Constructor CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} }; +/// Deduction guide for CouplingParams template CouplingParams(FieldType...) -> CouplingParams; @@ -78,9 +81,10 @@ CouplingParams(FieldType...) -> CouplingParams; */ template struct CouplingFields { - std::tuple packs; + std::tuple packs; ///< The coupling packs. }; +/// Helper to construct a CouplingFields bundle template auto couplingFields(const PFs&... pfs) { @@ -104,6 +108,7 @@ struct is_physics_fields_impl : std::false_type {}; template struct is_physics_fields_impl> : std::true_type {}; +/// @brief True if T is a PhysicsFields type. template inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; @@ -113,6 +118,7 @@ struct is_parameter_pack_impl : std::false_type {}; template struct is_parameter_pack_impl> : std::true_type {}; +/// @brief True if T is a CouplingParams type. template inline constexpr bool is_parameter_pack_v = is_parameter_pack_impl>::value; @@ -122,6 +128,7 @@ struct is_coupling_fields_impl : std::false_type {}; template struct is_coupling_fields_impl> : std::true_type {}; +/// @brief True if T is a CouplingFields type. template inline constexpr bool is_coupling_fields_v = is_coupling_fields_impl>::value; @@ -132,12 +139,15 @@ struct is_coupling_packs_impl : std::false_type {}; template struct is_coupling_packs_impl> : std::true_type {}; +/// @brief True if T is a tuple of coupling packs. template inline constexpr bool is_coupling_packs_v = is_coupling_packs_impl>::value; +/// @brief Base case: T does not have a time rule. template inline constexpr bool has_time_rule_v = false; +/// @brief True if T is a PhysicsFields with a time rule other than NoTimeRule. template inline constexpr bool has_time_rule_v>> = !std::is_same_v::time_rule_type, NoTimeRule>; @@ -163,20 +173,24 @@ inline constexpr bool trailing_coupling_args_valid_v = [] { // Trailing-arg extraction // ------------------------------------------------------------------------- +/// @brief Check if Trailing arguments contain CouplingFields. template constexpr bool hasCouplingFields() { return ((is_coupling_fields_v) || ...); } +/// @brief Check if Trailing arguments contain CouplingParams. template constexpr bool hasParamPack() { return ((is_parameter_pack_v) || ...); } +/// @brief Base case for extracting coupling packs (empty). inline auto extractCouplingPacks() { return std::tuple<>{}; } +/// @brief Extract the tuple of coupling packs from Trailing arguments. template auto extractCouplingPacks(const First& first, const Rest&... rest) { @@ -187,6 +201,7 @@ auto extractCouplingPacks(const First& first, const Rest&... rest) } } +/// @brief Qualify parameters by prefixing their names. template auto qualifyParams(const std::shared_ptr& fs, const CouplingParams& pack) { @@ -222,8 +237,10 @@ void registerParamsIfNeeded(std::shared_ptr fs, const Trailing&... t (register_one(trailing), ...); } +/// @brief Base case for extracting qualified parameter pack (empty). inline auto extractParamPackQualified(const std::shared_ptr& /*fs*/) { return std::tuple<>{}; } +/// @brief Extract qualified parameter pack. template auto extractParamPackQualified(const std::shared_ptr& fs, const First& first, const Rest&... rest) { @@ -241,6 +258,7 @@ auto flattenCouplingFields(const PacksTuple& packs) return std::apply([](const auto&... pack) { return std::tuple_cat(pack.fields...); }, packs); } +/// @brief Collect all coupling fields and parameter packs. template auto collectCouplingFields(const std::shared_ptr& fs, const Trailing&... trailing) { @@ -253,6 +271,7 @@ auto collectCouplingFields(const std::shared_ptr& fs, const Trailing // Time-rule interpolation // ------------------------------------------------------------------------- +/// @brief Implementation of time rule prefix application. template decltype(auto) applyTimeRuleToPrefixImpl(const Rule& rule, const TimeInfoT& t_info, const ArgsTuple& raw_args, @@ -268,6 +287,7 @@ decltype(auto) applyTimeRuleToPrefixImpl(const Rule& rule, const TimeInfoT& t_in interpolated); } +/// @brief Apply time rule interpolation to the leading prefix of raw arguments. template decltype(auto) applyTimeRuleToPrefix(const Rule& rule, const TimeInfoT& t_info, Callback&& callback, const RawArgs&... raw_args) @@ -280,6 +300,7 @@ decltype(auto) applyTimeRuleToPrefix(const Rule& rule, const TimeInfoT& t_info, std::make_index_sequence{}); } +/// @brief Evaluate a single coupling pack's time rule. template auto evaluateCouplingPack(const Pack& /*pack*/, const TimeInfoT& t_info, const RawTuple& raw_args, std::index_sequence) @@ -293,6 +314,7 @@ auto evaluateCouplingPack(const Pack& /*pack*/, const TimeInfoT& t_info, const R } } +/// @brief Evaluate all coupling packs over their corresponding raw arguments. template auto evaluateCouplingPacks(const PacksTuple& packs, const TimeInfoT& t_info, const RawTuple& raw_args) { @@ -307,6 +329,7 @@ auto evaluateCouplingPacks(const PacksTuple& packs, const TimeInfoT& t_info, con } } +/// @brief Interpolate coupling packs and invoke the callback. template decltype(auto) applyCouplingTimeRules(const PacksTuple& packs, const TimeInfoT& t_info, Callback&& callback, const RawArgs&... raw_args) @@ -350,10 +373,11 @@ decltype(auto) applyTimeRuleAndCoupling(const Rule& rule, const Coupling& coupli // Type-level coupling-space extraction (used by weak-form parameter type computation) // ------------------------------------------------------------------------- -/// Flatten a `tuple` type into `Parameters`. +/// @brief Flatten a `tuple` type into `Parameters`. template struct FlattenCoupling; +/// @brief Specialization of FlattenCoupling for a tuple of coupling packs. template struct FlattenCoupling> { private: @@ -384,27 +408,33 @@ struct FlattenCoupling> { }; public: - using parameters = typename concat>::type...>::type; + using parameters = + typename concat>::type...>::type; ///< Flattened parameter spaces. }; +/// @brief Typedef for flattened coupling parameter spaces. template using flatten_coupling_t = typename FlattenCoupling>::parameters; -/// `Parameters` for a weak form's parameter list. +/// @brief Type trait to construct `Parameters` for a weak form's parameter list. template struct TimeRuleParamsImpl; +/// @brief Specialization of TimeRuleParamsImpl. template struct TimeRuleParamsImpl> { - using type = smith::TimeRuleParams; + using type = smith::TimeRuleParams; ///< The constructed TimeRuleParams type. }; +/// @brief Typedef for TimeRuleParams. template using TimeRuleParams = typename TimeRuleParamsImpl>::type; +/// @brief Type trait to append coupling parameter spaces to fixed parameters. template struct AppendCouplingToParams; +/// @brief Specialization of AppendCouplingToParams. template struct AppendCouplingToParams> { private: @@ -416,7 +446,7 @@ struct AppendCouplingToParams> { }; public: - using type = typename expand>::type; + using type = typename expand>::type; ///< The appended parameter list. }; } // namespace detail diff --git a/src/smith/differentiable_numerics/field_store.hpp b/src/smith/differentiable_numerics/field_store.hpp index 859b108727..d85b3a6338 100644 --- a/src/smith/differentiable_numerics/field_store.hpp +++ b/src/smith/differentiable_numerics/field_store.hpp @@ -45,7 +45,7 @@ struct ReactionInfo { */ template struct FieldType { - using space_type = Space; + using space_type = Space; ///< The finite element space type. /** * @brief Construct a new FieldType object. @@ -354,6 +354,7 @@ struct FieldStore { std::vector getBoundaryConditionManagers( const std::vector& weak_form_names) const; + /// @brief Get ordered boundary condition managers corresponding to an ordered list of fields. std::vector getBoundaryConditionManagersForFields( const std::vector& field_names) const; diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index b53f5170a5..0008497b2e 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -205,6 +205,7 @@ auto registerThermalFields(std::shared_ptr field_store, */ namespace detail { +/// @brief Internal thermal builder after coupling fields are assembled. template requires detail::is_coupling_packs_v auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupling& coupling, diff --git a/src/smith/physics/common.hpp b/src/smith/physics/common.hpp index 49b6ba3848..0f184206c4 100644 --- a/src/smith/physics/common.hpp +++ b/src/smith/physics/common.hpp @@ -16,10 +16,11 @@ namespace smith { /// @brief struct storing time and timestep information struct TimeInfo { + /// @brief Evaluation mode for the current step enum class EvaluationMode { - Regular, - CycleZero + Regular, ///< Normal evaluation step + CycleZero ///< Initialization or cycle zero step }; /// @brief constructor diff --git a/src/smith/physics/functional_objective.hpp b/src/smith/physics/functional_objective.hpp index 6c00addd8d..14ed9c0739 100644 --- a/src/smith/physics/functional_objective.hpp +++ b/src/smith/physics/functional_objective.hpp @@ -81,6 +81,7 @@ class FunctionalObjective, std::integer_ mesh_->domain(body_name)); } + /// @brief Add a body integral to the objective function. template void addBodyIntegral(std::string body_name, const FuncOfTimeSpaceAndParams& qfunction) { @@ -108,6 +109,7 @@ class FunctionalObjective, std::integer_ mesh_->domain(boundary_name)); } + /// @brief Add a boundary integral to the objective function. template void addBoundaryIntegral(std::string boundary_name, const FuncOfTimeSpaceAndParams& qfunction) { diff --git a/src/smith/physics/functional_weak_form.hpp b/src/smith/physics/functional_weak_form.hpp index 3eb0742551..4080b7e864 100644 --- a/src/smith/physics/functional_weak_form.hpp +++ b/src/smith/physics/functional_weak_form.hpp @@ -128,6 +128,7 @@ class FunctionalWeakForm, mesh_->domain(body_name)); } + /// @brief Add a body integral to the weak form. template void addBodyIntegral(std::string body_name, BodyIntegralType integrand) { @@ -203,6 +204,7 @@ class FunctionalWeakForm, mesh_->domain(boundary_name)); } + /// @brief Add a boundary integral to the weak form. template void addBoundaryIntegral(std::string boundary_name, const BoundaryIntegrandType& integrand) { @@ -250,6 +252,7 @@ class FunctionalWeakForm, mesh_->domain(interior_name)); } + /// @brief Add an interior boundary integral to the weak form. template void addInteriorBoundaryIntegral(std::string interior_name, const InteriorIntegrandType& integrand) { From 6ab30a3df2160121589ab4f3b110909f8424f505 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 13 May 2026 14:13:51 -0600 Subject: [PATCH 60/67] Fix docs, cleanup example sphinx doc. --- .../composable_solid_mechanics.cpp | 4 +- .../composable_thermo_mechanics_advanced.cpp | 5 +- .../composable_solid_mechanics_tutorial.rst | 7 +- ...ble_thermo_mechanics_advanced_tutorial.rst | 8 +- .../composable_thermo_mechanics_tutorial.rst | 4 +- .../coupling_params.hpp | 173 +++++------------- .../solid_mechanics_system.hpp | 66 ++++++- .../state_variable_system.hpp | 58 +++++- .../tests/test_combined_thermo_mechanics.cpp | 32 +++- .../tests/test_solid_dynamics.cpp | 8 +- .../tests/test_thermal_static.cpp | 2 +- .../thermal_system.hpp | 60 +++++- 12 files changed, 263 insertions(+), 164 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 6d994f686a..ec5b07cc0d 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -95,7 +95,7 @@ int main(int argc, char* argv[]) auto solid_fields = smith::registerSolidMechanicsFields( field_store, output_options); - auto param_fields = smith::registerParameterFields(smith::FieldType>("youngs_modulus")); + auto param_fields = smith::registerParameterFields(field_store, smith::FieldType>("youngs_modulus")); // _solver_end // _build_start @@ -209,4 +209,4 @@ int main(int argc, char* argv[]) // _output_end return 0; -} \ No newline at end of file +} diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index edd5597eb2..4a1ebc1e79 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -75,7 +75,8 @@ int main(int argc, char* argv[]) field_store, solid_options); auto thermal_fields = smith::registerThermalFields(field_store); - auto param_fields = smith::registerParameterFields(smith::FieldType>("thermal_expansion_scaling")); + auto param_fields = + smith::registerParameterFields(field_store, smith::FieldType>("thermal_expansion_scaling")); // _solver_end // _build_start @@ -170,4 +171,4 @@ int main(int argc, char* argv[]) // _sensitivity_end return 0; -} \ No newline at end of file +} diff --git a/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst b/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst index 33fb4d348f..62701b569a 100644 --- a/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst +++ b/src/docs/sphinx/user_guide/composable_solid_mechanics_tutorial.rst @@ -41,8 +41,11 @@ Solver and Field Registration ----------------------------- The field registration phase declares the dynamic displacement state pack, -Young's modulus parameter field, and optional stress output field on a shared -``FieldStore``. +registers the Young's modulus parameter field on the shared ``FieldStore``, +and optionally enables stress output. Parameter fields now use the same +two-phase setup as physics fields: first register with +``registerParameterFields(field_store, ...)``, then pass the returned +``ParamFields`` bundle into ``buildSolidMechanicsSystem(...)``. .. literalinclude:: ../../../../examples/solid_mechanics/composable_solid_mechanics.cpp :start-after: _solver_start diff --git a/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst b/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst index d9318728cf..2c68783cf0 100644 --- a/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst +++ b/src/docs/sphinx/user_guide/composable_thermo_mechanics_advanced_tutorial.rst @@ -38,7 +38,9 @@ Solver Config and Field Registration ------------------------------------ This example uses a single block solver combining Newton line search for the non-linear -solve and SuperLU for the linear solve. +solve and SuperLU for the linear solve. It also registers the thermal-expansion +scaling parameter directly on the shared ``FieldStore`` with +``registerParameterFields(field_store, ...)`` before either system is built. .. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp :start-after: _solver_start @@ -58,7 +60,9 @@ Boundary Conditions and Loads The traction call uses ``DependsOn<>{}``, so the user callback receives only the state arguments it actually needs and none of the trailing coupling or parameter -fields. +fields. The system builders now take the registered ``ParamFields`` bundle +explicitly, so the build order stays ``self_fields``, optional +``couplingFields(...)``, then optional params. .. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp :start-after: _bc_start diff --git a/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst b/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst index dbc741b926..d36220e894 100644 --- a/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst +++ b/src/docs/sphinx/user_guide/composable_thermo_mechanics_tutorial.rst @@ -38,7 +38,9 @@ Field Registration ------------------ Registration is phase 1. It declares the solid and thermal fields up front in a -shared ``FieldStore``. +shared ``FieldStore``. When user parameters are needed, register them in this +same phase with ``registerParameterFields(field_store, ...)`` and carry the +returned ``ParamFields`` into the build step. .. literalinclude:: ../../../../examples/thermo_mechanics/composable_thermo_mechanics.cpp :start-after: _solver_start diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index f0a99fe538..f9d9be8920 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -10,7 +10,7 @@ * * Builders accept at most two optional trailing arguments after `self_fields`: * 1. `couplingFields(foreign_physics_fields...)` — foreign physics contributions - * 2. `param_fields` (a `CouplingParams<...>`) — user parameter fields (must be last) + * 2. `param_fields` (a `ParamFields<...>`) — registered user parameter fields (must be last) * * Tail of each material/source closure: `(coupling_fields..., parameter_fields...)`. */ @@ -26,17 +26,12 @@ namespace smith { -/// Sentinel: no time integration rule (used for parameter-only packs). -struct NoTimeRule { - static constexpr int num_states = 0; ///< Number of states. -}; - /** * @brief Fields returned by a physics register function, carrying time rule type information. * * Doubles as a per-physics coupling segment: when supplied via `couplingFields(...)`, the * builder interpolates `TimeRule::num_states` raw arguments before passing the values to - * the user callback. Parameter packs use `TimeRule = NoTimeRule`. + * the user callback. */ template struct PhysicsFields { @@ -54,24 +49,26 @@ struct PhysicsFields { } }; +template +struct is_physics_fields_arg : std::false_type {}; + +template +struct is_physics_fields_arg> : std::true_type {}; + /** - * @brief Parameter-only field bundle (no time rule, no field store reference). - * - * Distinct type so callers can plain-pass it after `couplingFields(...)`. Otherwise has the - * same shape as a `PhysicsFields` with `NoTimeRule`. + * @brief Registered parameter-only field bundle. */ template -struct CouplingParams { - using time_rule_type = NoTimeRule; ///< Time rule type (none). +struct ParamFields { static constexpr std::size_t num_fields = sizeof...(Spaces); ///< Number of fields. std::tuple...> fields; ///< The fields. /// Constructor - CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} + ParamFields(FieldType... fs) : fields(std::move(fs)...) {} }; -/// Deduction guide for CouplingParams +/// Deduction guide for ParamFields template -CouplingParams(FieldType...) -> CouplingParams; +ParamFields(FieldType...) -> ParamFields; /** * @brief Bundle of foreign `PhysicsFields` packs supplied to a builder as a single coupling arg. @@ -88,6 +85,8 @@ struct CouplingFields { template auto couplingFields(const PFs&... pfs) { + static_assert((is_physics_fields_arg>::value && ...), + "couplingFields(...) only accepts PhysicsFields packs"); return CouplingFields{std::make_tuple(pfs...)}; } @@ -95,9 +94,14 @@ auto couplingFields(const PFs&... pfs) * @brief Register parameter fields as type-level tokens. */ template -auto registerParameterFields(FieldType... param_types) +auto registerParameterFields(const std::shared_ptr& field_store, FieldType... param_types) { - return CouplingParams{std::move(param_types)...}; + auto register_one = [&](auto param_type) { + param_type.name = "param_" + param_type.name; + field_store->addParameter(param_type); + return param_type; + }; + return ParamFields{register_one(std::move(param_types))...}; } namespace detail { @@ -116,9 +120,9 @@ template struct is_parameter_pack_impl : std::false_type {}; template -struct is_parameter_pack_impl> : std::true_type {}; +struct is_parameter_pack_impl> : std::true_type {}; -/// @brief True if T is a CouplingParams type. +/// @brief True if T is a ParamFields type. template inline constexpr bool is_parameter_pack_v = is_parameter_pack_impl>::value; @@ -147,124 +151,43 @@ inline constexpr bool is_coupling_packs_v = is_coupling_packs_impl inline constexpr bool has_time_rule_v = false; -/// @brief True if T is a PhysicsFields with a time rule other than NoTimeRule. +/// @brief True if T is a PhysicsFields type. template -inline constexpr bool has_time_rule_v>> = - !std::is_same_v::time_rule_type, NoTimeRule>; - -/// Trailing args must be one of: {}, {CouplingFields}, {CouplingParams}, {CouplingFields, CouplingParams}. -template -inline constexpr bool trailing_coupling_args_valid_v = [] { - if constexpr (sizeof...(Trailing) == 0) { - return true; - } else if constexpr (sizeof...(Trailing) == 1) { - using T0 = std::tuple_element_t<0, std::tuple>; - return is_coupling_fields_v || is_parameter_pack_v; - } else if constexpr (sizeof...(Trailing) == 2) { - using T0 = std::tuple_element_t<0, std::tuple>; - using T1 = std::tuple_element_t<1, std::tuple>; - return is_coupling_fields_v && is_parameter_pack_v; - } else { - return false; - } -}(); +inline constexpr bool has_time_rule_v>> = true; // ------------------------------------------------------------------------- // Trailing-arg extraction // ------------------------------------------------------------------------- -/// @brief Check if Trailing arguments contain CouplingFields. -template -constexpr bool hasCouplingFields() -{ - return ((is_coupling_fields_v) || ...); -} - -/// @brief Check if Trailing arguments contain CouplingParams. -template -constexpr bool hasParamPack() +/// Concatenate each pack's `.fields` tuple — used to derive trailing weak-form parameter spaces. +template +auto flattenCouplingFields(const PacksTuple& packs) { - return ((is_parameter_pack_v) || ...); + return std::apply([](const auto&... pack) { return std::tuple_cat(pack.fields...); }, packs); } -/// @brief Base case for extracting coupling packs (empty). -inline auto extractCouplingPacks() { return std::tuple<>{}; } +/// @brief Collect no coupling or parameter packs. +inline auto collectCouplingFields() { return std::tuple<>{}; } -/// @brief Extract the tuple of coupling packs from Trailing arguments. -template -auto extractCouplingPacks(const First& first, const Rest&... rest) +template +/// @brief Collect only coupled physics packs. +auto collectCouplingFields(const CouplingFields& coupled) { - if constexpr (is_coupling_fields_v) { - return first.packs; - } else { - return extractCouplingPacks(rest...); - } + return coupled.packs; } -/// @brief Qualify parameters by prefixing their names. template -auto qualifyParams(const std::shared_ptr& fs, const CouplingParams& pack) -{ - return std::apply( - [&](auto... pts) { - auto qualify = [&](auto pt) { - pt.name = fs->prefix("param_" + pt.name); - return pt; - }; - return CouplingParams{qualify(pts)...}; - }, - pack.fields); -} - -/// Register parameter fields from any trailing CouplingParams into the FieldStore. -template -void registerParamsIfNeeded(std::shared_ptr fs, const Trailing&... trailing) -{ - auto register_one = [&](const auto& pack) { - using P = std::decay_t; - if constexpr (is_parameter_pack_v

) { - std::apply( - [&](auto... pts) { - auto prefix_and_add = [&](auto pt) { - pt.name = "param_" + pt.name; - fs->addParameter(pt); - }; - (prefix_and_add(pts), ...); - }, - pack.fields); - } - }; - (register_one(trailing), ...); -} - -/// @brief Base case for extracting qualified parameter pack (empty). -inline auto extractParamPackQualified(const std::shared_ptr& /*fs*/) { return std::tuple<>{}; } - -/// @brief Extract qualified parameter pack. -template -auto extractParamPackQualified(const std::shared_ptr& fs, const First& first, const Rest&... rest) -{ - if constexpr (is_parameter_pack_v) { - return std::make_tuple(qualifyParams(fs, first)); - } else { - return extractParamPackQualified(fs, rest...); - } -} - -/// Concatenate each pack's `.fields` tuple — used to derive trailing weak-form parameter spaces. -template -auto flattenCouplingFields(const PacksTuple& packs) +/// @brief Collect only registered parameter fields. +auto collectCouplingFields(const ParamFields& params) { - return std::apply([](const auto&... pack) { return std::tuple_cat(pack.fields...); }, packs); + return std::make_tuple(params); } -/// @brief Collect all coupling fields and parameter packs. -template -auto collectCouplingFields(const std::shared_ptr& fs, const Trailing&... trailing) +template +/// @brief Collect coupled physics packs followed by registered parameter fields. +auto collectCouplingFields(const CouplingFields& coupled, const ParamFields& params) { - auto physics_packs = extractCouplingPacks(trailing...); - auto param_pack = extractParamPackQualified(fs, trailing...); - return std::tuple_cat(physics_packs, param_pack); + return std::tuple_cat(coupled.packs, std::make_tuple(params)); } // ------------------------------------------------------------------------- @@ -305,12 +228,12 @@ template ) { - using Rule = typename Pack::time_rule_type; - if constexpr (std::is_same_v) { - return std::forward_as_tuple(std::get(raw_args)...); - } else { + if constexpr (is_physics_fields_v) { + using Rule = typename Pack::time_rule_type; Rule rule; return rule.interpolate(t_info, std::get(raw_args)...); + } else { + return std::forward_as_tuple(std::get(raw_args)...); } } @@ -388,7 +311,7 @@ struct FlattenCoupling> { using type = Parameters; }; template - struct pack_spaces> { + struct pack_spaces> { using type = Parameters; }; diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index ff5bcf978b..a885768bd2 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -37,7 +37,7 @@ namespace smith { * @tparam dim Spatial dimension. * @tparam order Polynomial order for displacement field. * @tparam DisplacementTimeRule Time integration rule type (must have num_states == 4). - * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). + * @tparam Coupling Tuple of coupling and parameter packs (default: none). * Coupling fields occupy leading positions in the tail after the 4 time-rule state fields, * before user parameter_space fields. * @tparam parameter_space Parameter spaces for material properties. @@ -392,17 +392,71 @@ auto buildSolidMechanicsSystemImpl(std::shared_ptr field_store, cons * solver, opts, solid_fields, couplingFields(thermal_fields), param_fields); * @endcode */ -template - requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) +template + requires(detail::has_time_rule_v) auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, - const SelfFields& self_fields, const Trailing&... trailing) + const SelfFields& self_fields) { constexpr int dim = SelfFields::dim; constexpr int order = SelfFields::order; using DisplacementTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; - detail::registerParamsIfNeeded(field_store, trailing...); - auto coupling = detail::collectCouplingFields(field_store, trailing...); + auto coupling = detail::collectCouplingFields(); + bool has_stress_output = detail::hasRegisteredStressOutput(field_store); + return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, options, + has_stress_output); +} + +/** + * @brief Build a SolidMechanicsSystem from registered self fields plus coupled physics fields. + */ +template + requires(detail::has_time_rule_v) +auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, + const SelfFields& self_fields, const CouplingFields& coupled) +{ + constexpr int dim = SelfFields::dim; + constexpr int order = SelfFields::order; + using DisplacementTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(coupled); + bool has_stress_output = detail::hasRegisteredStressOutput(field_store); + return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, options, + has_stress_output); +} + +/** + * @brief Build a SolidMechanicsSystem from registered self fields plus registered parameter fields. + */ +template + requires(detail::has_time_rule_v) +auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, + const SelfFields& self_fields, const ParamFields& params) +{ + constexpr int dim = SelfFields::dim; + constexpr int order = SelfFields::order; + using DisplacementTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(params); + bool has_stress_output = detail::hasRegisteredStressOutput(field_store); + return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, options, + has_stress_output); +} + +/** + * @brief Build a SolidMechanicsSystem from registered self fields, coupled physics fields, and parameter fields. + */ +template + requires(detail::has_time_rule_v) +auto buildSolidMechanicsSystem(std::shared_ptr solver, const SolidMechanicsOptions& options, + const SelfFields& self_fields, const CouplingFields& coupled, + const ParamFields& params) +{ + constexpr int dim = SelfFields::dim; + constexpr int order = SelfFields::order; + using DisplacementTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(coupled, params); bool has_stress_output = detail::hasRegisteredStressOutput(field_store); return detail::buildSolidMechanicsSystemImpl(field_store, coupling, solver, options, has_stress_output); diff --git a/src/smith/differentiable_numerics/state_variable_system.hpp b/src/smith/differentiable_numerics/state_variable_system.hpp index d4798ac29a..25c237d2de 100644 --- a/src/smith/differentiable_numerics/state_variable_system.hpp +++ b/src/smith/differentiable_numerics/state_variable_system.hpp @@ -52,7 +52,7 @@ namespace smith { * @tparam dim Spatial dimension (needed for the weak form and zero-flux tensor). * @tparam StateSpace FE space for the internal variable (e.g., L2<0>). * @tparam InternalVarTimeRule Time integration rule (must have num_states == 2). - * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). + * @tparam Coupling Tuple of coupling and parameter packs (default: none). */ template > @@ -190,17 +190,63 @@ auto buildInternalVariableSystemImpl(std::shared_ptr field_store, co * * The time rule is deduced from SelfFields::time_rule_type. */ -template - requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) +template + requires(detail::has_time_rule_v) +auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields) +{ + constexpr int dim = SelfFields::dim; + using StateSpace = typename std::tuple_element_t<0, decltype(self_fields.fields)>::space_type; + using InternalVarTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(); + return detail::buildInternalVariableSystemImpl(field_store, coupling, solver); +} + +/** + * @brief Build an InternalVariableSystem from registered self fields plus coupled physics fields. + */ +template + requires(detail::has_time_rule_v) +auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields, + const CouplingFields& coupled) +{ + constexpr int dim = SelfFields::dim; + using StateSpace = typename std::tuple_element_t<0, decltype(self_fields.fields)>::space_type; + using InternalVarTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(coupled); + return detail::buildInternalVariableSystemImpl(field_store, coupling, solver); +} + +/** + * @brief Build an InternalVariableSystem from registered self fields plus registered parameter fields. + */ +template + requires(detail::has_time_rule_v) +auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields, + const ParamFields& params) +{ + constexpr int dim = SelfFields::dim; + using StateSpace = typename std::tuple_element_t<0, decltype(self_fields.fields)>::space_type; + using InternalVarTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(params); + return detail::buildInternalVariableSystemImpl(field_store, coupling, solver); +} + +/** + * @brief Build an InternalVariableSystem from registered self fields, coupled physics fields, and parameter fields. + */ +template + requires(detail::has_time_rule_v) auto buildInternalVariableSystem(std::shared_ptr solver, const SelfFields& self_fields, - const Trailing&... trailing) + const CouplingFields& coupled, const ParamFields& params) { constexpr int dim = SelfFields::dim; using StateSpace = typename std::tuple_element_t<0, decltype(self_fields.fields)>::space_type; using InternalVarTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; - detail::registerParamsIfNeeded(field_store, trailing...); - auto coupling = detail::collectCouplingFields(field_store, trailing...); + auto coupling = detail::collectCouplingFields(coupled, params); return detail::buildInternalVariableSystemImpl(field_store, coupling, solver); } diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 88fb585600..95494b0085 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -48,9 +48,9 @@ TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) auto field_store = std::make_shared(mesh, 100, ""); auto solid_fields = registerSolidMechanicsFields(field_store); auto thermal_fields = registerThermalFields(field_store); - auto scale_params = registerParameterFields(FieldType>("scale")); + auto scale_params = registerParameterFields(field_store, FieldType>("scale")); - auto solid_coupling = detail::collectCouplingFields(field_store, couplingFields(thermal_fields), scale_params); + auto solid_coupling = detail::collectCouplingFields(couplingFields(thermal_fields), scale_params); auto saw_thermal_values = detail::applyCouplingTimeRules( solid_coupling, TimeInfo(0.0, 2.0, 0), [](auto temperature, auto temperature_dot, auto scale) { @@ -62,7 +62,7 @@ TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) 7.0, 1.0, 11.0); EXPECT_TRUE(saw_thermal_values); - auto thermal_coupling = detail::collectCouplingFields(field_store, couplingFields(solid_fields)); + auto thermal_coupling = detail::collectCouplingFields(couplingFields(solid_fields)); auto saw_solid_values = detail::applyCouplingTimeRules( thermal_coupling, TimeInfo(0.0, 2.0, 0), [](auto displacement, auto velocity, auto /*acceleration*/) { @@ -74,6 +74,24 @@ TEST(CouplingTimeRuleInterpolation, AppliesEachForeignPhysicsRuleBeforeCallback) EXPECT_TRUE(saw_solid_values); } +TEST(ParameterFieldsRegistration, RegistersImmediatelyAndReturnsPrefixedFields) +{ + axom::sidre::DataStore datastore; + smith::StateManager::initialize(datastore, "parameter_registration"); + auto mesh = + std::make_shared(mfem::Mesh::MakeCartesian3D(1, 1, 1, mfem::Element::HEXAHEDRON), "mesh", 0, 0); + auto field_store = std::make_shared(mesh, 100, "coupled"); + + auto param_fields = registerParameterFields(field_store, FieldType>("scale"), FieldType>("offset")); + + static_assert(std::is_same_v, ParamFields, L2<0>>>); + EXPECT_EQ(field_store->getParameterFields().size(), 2); + EXPECT_EQ(std::get<0>(param_fields.fields).name, field_store->prefix("param_scale")); + EXPECT_EQ(std::get<1>(param_fields.fields).name, field_store->prefix("param_offset")); + EXPECT_EQ(field_store->getParameterFields()[0].get()->name(), field_store->prefix("param_scale")); + EXPECT_EQ(field_store->getParameterFields()[1].get()->name(), field_store->prefix("param_offset")); +} + TEST(CouplingTimeRuleInterpolation, PreservesForeignPacksWithSameTimeRuleType) { axom::sidre::DataStore datastore; @@ -88,7 +106,7 @@ TEST(CouplingTimeRuleInterpolation, PreservesForeignPacksWithSameTimeRuleType) PhysicsFields thermal_b{ field_store, FieldType("temperature_b_solve_state"), FieldType("temperature_b")}; - auto same_rule_coupling = detail::collectCouplingFields(field_store, couplingFields(thermal_a, thermal_b)); + auto same_rule_coupling = detail::collectCouplingFields(couplingFields(thermal_a, thermal_b)); auto saw_all_values = detail::applyCouplingTimeRules( same_rule_coupling, TimeInfo(0.0, 2.0, 0), [](auto temperature_a, auto temperature_a_dot, auto temperature_b, auto temperature_b_dot) { @@ -174,9 +192,9 @@ TEST_F(ThermoMechanicsMeshFixture, CreateDifferentiablePhysicsAllocatesReactionI { FieldType> thermal_expansion_scaling("thermal_expansion_scaling"); - auto param_fields = registerParameterFields(thermal_expansion_scaling); auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); + auto param_fields = registerParameterFields(field_store_, thermal_expansion_scaling); auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), param_fields); @@ -209,9 +227,9 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) { FieldType> youngs_modulus("youngs_modulus"); - auto param_fields = registerParameterFields(youngs_modulus); auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); + auto param_fields = registerParameterFields(field_store_, youngs_modulus); auto solid_system = buildSolidMechanicsSystem(makeSolver(newtonNonlinOpts, directLinOpts), SolidMechanicsOptions{}, solid_fields, couplingFields(thermal_fields), param_fields); @@ -260,9 +278,9 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) { FieldType> thermal_expansion_scaling("thermal_expansion_scaling"); - auto param_fields = registerParameterFields(thermal_expansion_scaling); auto solid_fields = registerSolidMechanicsFields(field_store_); auto thermal_fields = registerThermalFields(field_store_); + auto param_fields = registerParameterFields(field_store_, thermal_expansion_scaling); LinearSolverOptions solid_lin_opts{.linear_solver = LinearSolver::CG, .preconditioner = Preconditioner::HypreAMG, diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 06ab33405a..9105391d85 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -176,9 +176,9 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; - auto param_fields = - registerParameterFields(FieldType("bulk"), FieldType("shear")); auto field_store = fieldStore(mesh); + auto param_fields = registerParameterFields(field_store, FieldType("bulk"), + FieldType("shear")); SolidMechanicsOptions solid_opts{.enable_stress_output = true}; auto solid_fields = registerSolidMechanicsFields(field_store, solid_opts); @@ -344,8 +344,8 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr(mesh, 100, physics_name); - auto param_fields = - registerParameterFields(FieldType("bulk"), FieldType("shear")); + auto param_fields = registerParameterFields(field_store, FieldType("bulk"), + FieldType("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); auto solver = diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index 247aff38a0..bde38bd801 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -140,8 +140,8 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) FieldType> conductivity_param("conductivity"); - auto param_fields = registerParameterFields(conductivity_param); auto field_store = fieldStore(mesh); + auto param_fields = registerParameterFields(field_store, conductivity_param); auto thermal_fields = registerThermalFields<2, 1, QuasiStaticFirstOrderTimeIntegrationRule>(field_store); auto solver = std::make_shared(buildNonlinearBlockSolver(solver_options, linear_options, *mesh)); auto thermal_system = buildThermalSystem(solver, ThermalOptions{}, thermal_fields, param_fields); diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 0008497b2e..16c5719d70 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -34,7 +34,7 @@ namespace smith { * @tparam dim Spatial dimension. * @tparam temp_order Order of the temperature basis. * @tparam TemperatureTimeRule Time integration rule type (must have num_states == 2). - * @tparam Coupling CouplingParams listing fields borrowed from other physics (default: none). + * @tparam Coupling Tuple of coupling and parameter packs (default: none). * Coupling fields occupy leading positions in the tail after the 2 time-rule state fields, * before user parameter_space fields. */ @@ -249,17 +249,65 @@ auto buildThermalSystemImpl(std::shared_ptr field_store, const Coupl * solver, opts, thermal_fields, couplingFields(solid_fields)); * @endcode */ -template - requires(detail::has_time_rule_v && detail::trailing_coupling_args_valid_v) +template + requires(detail::has_time_rule_v) auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, - const SelfFields& self_fields, const Trailing&... trailing) + const SelfFields& self_fields) { constexpr int dim = SelfFields::dim; constexpr int temp_order = SelfFields::order; using TemperatureTimeRule = typename std::decay_t::time_rule_type; auto field_store = self_fields.field_store; - detail::registerParamsIfNeeded(field_store, trailing...); - auto coupling = detail::collectCouplingFields(field_store, trailing...); + auto coupling = detail::collectCouplingFields(); + return detail::buildThermalSystemImpl(field_store, coupling, solver, options); +} + +/** + * @brief Build a ThermalSystem from registered self fields plus coupled physics fields. + */ +template + requires(detail::has_time_rule_v) +auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, + const SelfFields& self_fields, const CouplingFields& coupled) +{ + constexpr int dim = SelfFields::dim; + constexpr int temp_order = SelfFields::order; + using TemperatureTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(coupled); + return detail::buildThermalSystemImpl(field_store, coupling, solver, options); +} + +/** + * @brief Build a ThermalSystem from registered self fields plus registered parameter fields. + */ +template + requires(detail::has_time_rule_v) +auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, + const SelfFields& self_fields, const ParamFields& params) +{ + constexpr int dim = SelfFields::dim; + constexpr int temp_order = SelfFields::order; + using TemperatureTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(params); + return detail::buildThermalSystemImpl(field_store, coupling, solver, options); +} + +/** + * @brief Build a ThermalSystem from registered self fields, coupled physics fields, and parameter fields. + */ +template + requires(detail::has_time_rule_v) +auto buildThermalSystem(std::shared_ptr solver, const ThermalOptions& options, + const SelfFields& self_fields, const CouplingFields& coupled, + const ParamFields& params) +{ + constexpr int dim = SelfFields::dim; + constexpr int temp_order = SelfFields::order; + using TemperatureTimeRule = typename std::decay_t::time_rule_type; + auto field_store = self_fields.field_store; + auto coupling = detail::collectCouplingFields(coupled, params); return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } From 83f7a67ad6c0994fb8beddaeffbb5d26f22a526b Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Wed, 13 May 2026 15:02:20 -0600 Subject: [PATCH 61/67] Allow for a simpler build system interface to better match legacy interface style. --- .../composable_solid_mechanics.cpp | 11 ++------ .../coupling_params.hpp | 4 +-- .../solid_mechanics_system.hpp | 28 +++++++++++++++++++ .../tests/test_solid_dynamics.cpp | 19 ++++--------- .../tests/test_thermal_static.cpp | 20 ++----------- .../thermal_system.hpp | 28 +++++++++++++++++++ 6 files changed, 68 insertions(+), 42 deletions(-) diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index ec5b07cc0d..20b5fb68ad 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -90,18 +90,11 @@ int main(int argc, char* argv[]) .print_level = 0}; smith::SolidMechanicsOptions output_options{.enable_stress_output = true, .output_cauchy_stress = true}; - - auto field_store = std::make_shared(mesh, 100); - auto solid_fields = - smith::registerSolidMechanicsFields( - field_store, output_options); - auto param_fields = smith::registerParameterFields(field_store, smith::FieldType>("youngs_modulus")); // _solver_end // _build_start - auto solver = - std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); - auto solid_system = smith::buildSolidMechanicsSystem(solver, output_options, solid_fields, param_fields); + auto solid_system = smith::buildSolidMechanicsSystem( + nonlinear_options, linear_options, output_options, mesh, smith::FieldType>("youngs_modulus")); constexpr double E = 100.0; constexpr double nu = 0.25; diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index f9d9be8920..0498e000ef 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -9,7 +9,7 @@ * @brief Coupling pack types and helpers for injecting explicit coupled-physics fields into weak form parameter packs. * * Builders accept at most two optional trailing arguments after `self_fields`: - * 1. `couplingFields(foreign_physics_fields...)` — foreign physics contributions + * 1. `couplingFields(coupled_physics_fields...)` — coupled physics contributions * 2. `param_fields` (a `ParamFields<...>`) — registered user parameter fields (must be last) * * Tail of each material/source closure: `(coupling_fields..., parameter_fields...)`. @@ -71,7 +71,7 @@ template ParamFields(FieldType...) -> ParamFields; /** - * @brief Bundle of foreign `PhysicsFields` packs supplied to a builder as a single coupling arg. + * @brief Bundle of coupled `PhysicsFields` packs supplied to a builder as a single coupling arg. * * Order is preserved. Each entry contributes its fields and an interpolation segment governed * by its own `time_rule_type`. diff --git a/src/smith/differentiable_numerics/solid_mechanics_system.hpp b/src/smith/differentiable_numerics/solid_mechanics_system.hpp index a885768bd2..cc3a885984 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_system.hpp +++ b/src/smith/differentiable_numerics/solid_mechanics_system.hpp @@ -462,4 +462,32 @@ auto buildSolidMechanicsSystem(std::shared_ptr solver, const Solid has_stress_output); } +/** + * @brief Build a SolidMechanicsSystem from solver options and a mesh, registering parameter fields inline. + * + * Constructs the FieldStore, nonlinear block solver, system solver, and registers solid mechanics + * and parameter fields, then delegates to the existing field-pack overload. + * + * @tparam dim Spatial dimension. + * @tparam order Polynomial order for displacement. + * @tparam DisplacementTimeRule Time integration rule (defaults to ImplicitNewmarkSecondOrderTimeIntegrationRule). + * @tparam ParamSpaces Parameter function-space tags deduced from FieldType arguments. + */ +template +auto buildSolidMechanicsSystem(const NonlinearSolverOptions& nonlinear_opts, const LinearSolverOptions& linear_opts, + const SolidMechanicsOptions& options, std::shared_ptr mesh, + FieldType... params) +{ + auto field_store = std::make_shared(mesh); + auto solver = std::make_shared(buildNonlinearBlockSolver(nonlinear_opts, linear_opts, *mesh)); + auto solid_fields = registerSolidMechanicsFields(field_store, options); + if constexpr (sizeof...(ParamSpaces) > 0) { + auto param_fields = registerParameterFields(field_store, std::move(params)...); + return buildSolidMechanicsSystem(solver, options, solid_fields, param_fields); + } else { + return buildSolidMechanicsSystem(solver, options, solid_fields); + } +} + } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 9105391d85..4895ce9e8c 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -176,15 +176,10 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) { SMITH_MARK_FUNCTION; - auto field_store = fieldStore(mesh); - auto param_fields = registerParameterFields(field_store, FieldType("bulk"), - FieldType("shear")); SolidMechanicsOptions solid_opts{.enable_stress_output = true}; - auto solid_fields = - registerSolidMechanicsFields(field_store, solid_opts); - auto solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - auto solid_system = buildSolidMechanicsSystem(solver, solid_opts, solid_fields, param_fields); + auto solid_system = buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, solid_opts, + mesh, FieldType("bulk"), + FieldType("shear")); static constexpr double gravity = -9.0; @@ -282,12 +277,8 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditions) { SolidMechanicsOptions solid_options{.enable_stress_output = true}; - auto field_store = fieldStore(mesh); - auto solid_fields = registerSolidMechanicsFields( - field_store, solid_options); - auto solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); - auto solid_system = buildSolidMechanicsSystem(solver, solid_options, solid_fields); + auto solid_system = + buildSolidMechanicsSystem(solid_nonlinear_opts, solid_linear_options, solid_options, mesh); static constexpr double gravity = -9.0; double E = 100.0; diff --git a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp index bde38bd801..6ad9936af1 100644 --- a/src/smith/differentiable_numerics/tests/test_thermal_static.cpp +++ b/src/smith/differentiable_numerics/tests/test_thermal_static.cpp @@ -7,18 +7,12 @@ #include #include #include "smith/differentiable_numerics/thermal_system.hpp" -#include "smith/differentiable_numerics/nonlinear_solve.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" -#include "smith/differentiable_numerics/system_solver.hpp" #include "smith/physics/mesh.hpp" #include "smith/physics/common.hpp" #include "smith/physics/state/state_manager.hpp" -#include "smith/numerics/functional/functional.hpp" #include "smith/numerics/solver_config.hpp" #include "smith/infrastructure/application_manager.hpp" -#include "axom/slic.hpp" -#include "axom/fmt.hpp" -#include "axom/sidre.hpp" using namespace smith; @@ -49,10 +43,7 @@ struct ThermalStaticFixture : public testing::Test { solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); - auto field_store = fieldStore(mesh); - auto thermal_fields = registerThermalFields<2, temp_order, QuasiStaticFirstOrderTimeIntegrationRule>(field_store); - auto solver = std::make_shared(buildNonlinearBlockSolver(solver_options, linear_options, *mesh)); - auto thermal_system = buildThermalSystem(solver, ThermalOptions{}, thermal_fields); + auto thermal_system = buildThermalSystem<2, temp_order>(solver_options, linear_options, ThermalOptions{}, mesh); double k = 1.0; // Material returns {heat_capacity, heat_flux} consistent with heat_transfer.hpp convention. @@ -138,13 +129,8 @@ TEST_F(ThermalStaticFixture, HeatSourceWithDependsOn) solver_options.relative_tol = 1e-12; auto linear_options = LinearSolverOptions(); - FieldType> conductivity_param("conductivity"); - - auto field_store = fieldStore(mesh); - auto param_fields = registerParameterFields(field_store, conductivity_param); - auto thermal_fields = registerThermalFields<2, 1, QuasiStaticFirstOrderTimeIntegrationRule>(field_store); - auto solver = std::make_shared(buildNonlinearBlockSolver(solver_options, linear_options, *mesh)); - auto thermal_system = buildThermalSystem(solver, ThermalOptions{}, thermal_fields, param_fields); + auto thermal_system = buildThermalSystem<2, 1>(solver_options, linear_options, ThermalOptions{}, mesh, + FieldType>("conductivity")); // Set the conductivity parameter field to k=1.0 thermal_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( diff --git a/src/smith/differentiable_numerics/thermal_system.hpp b/src/smith/differentiable_numerics/thermal_system.hpp index 16c5719d70..1a7f54baee 100644 --- a/src/smith/differentiable_numerics/thermal_system.hpp +++ b/src/smith/differentiable_numerics/thermal_system.hpp @@ -311,4 +311,32 @@ auto buildThermalSystem(std::shared_ptr solver, const ThermalOptio return detail::buildThermalSystemImpl(field_store, coupling, solver, options); } +/** + * @brief Build a ThermalSystem from solver options and a mesh, registering parameter fields inline. + * + * Constructs the FieldStore, nonlinear block solver, system solver, and registers thermal + * and parameter fields, then delegates to the existing field-pack overload. + * + * @tparam dim Spatial dimension. + * @tparam temp_order Polynomial order for temperature. + * @tparam TemperatureTimeRule Time integration rule (defaults to QuasiStaticFirstOrderTimeIntegrationRule). + * @tparam ParamSpaces Parameter function-space tags deduced from FieldType arguments. + */ +template +auto buildThermalSystem(const NonlinearSolverOptions& nonlinear_opts, const LinearSolverOptions& linear_opts, + const ThermalOptions& options, std::shared_ptr mesh, + FieldType... params) +{ + auto field_store = std::make_shared(mesh); + auto solver = std::make_shared(buildNonlinearBlockSolver(nonlinear_opts, linear_opts, *mesh)); + auto thermal_fields = registerThermalFields(field_store, options); + if constexpr (sizeof...(ParamSpaces) > 0) { + auto param_fields = registerParameterFields(field_store, std::move(params)...); + return buildThermalSystem(solver, options, thermal_fields, param_fields); + } else { + return buildThermalSystem(solver, options, thermal_fields); + } +} + } // namespace smith From 5356443c926a5235f2f141490a8a0b9d6cb72a73 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 14 May 2026 09:21:51 -0700 Subject: [PATCH 62/67] Improve coupling param templating. --- .../coupling_params.hpp | 81 ++++++++++--------- 1 file changed, 42 insertions(+), 39 deletions(-) diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 0498e000ef..dc851db89e 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -300,39 +300,50 @@ decltype(auto) applyTimeRuleAndCoupling(const Rule& rule, const Coupling& coupli template struct FlattenCoupling; +template +struct TupleToParameters; + +template +struct TupleToParameters> { + using type = Parameters; +}; + +template +using tuple_to_parameters_t = typename TupleToParameters>::type; + +template +struct pack_tuple; + +template +struct pack_tuple> { + using type = std::tuple; +}; + +template +struct pack_tuple> { + using type = std::tuple; +}; + +template +using pack_tuple_t = typename pack_tuple>::type; + +template +using tuple_cat_t = decltype(std::tuple_cat(std::declval()...)); + +template +struct AppendParameters; + +template +struct AppendParameters, Parameters> { + using type = Parameters; +}; + /// @brief Specialization of FlattenCoupling for a tuple of coupling packs. template struct FlattenCoupling> { - private: - template - struct pack_spaces; - template - struct pack_spaces> { - using type = Parameters; - }; - template - struct pack_spaces> { - using type = Parameters; - }; - - template - struct concat; - template <> - struct concat<> { - using type = Parameters<>; - }; - template - struct concat> { - using type = Parameters; - }; - template - struct concat, Parameters, Rest...> { - using type = typename concat, Rest...>::type; - }; - public: - using parameters = - typename concat>::type...>::type; ///< Flattened parameter spaces. + using tuple_type = tuple_cat_t...>; + using parameters = tuple_to_parameters_t; ///< Flattened parameter spaces. }; /// @brief Typedef for flattened coupling parameter spaces. @@ -360,16 +371,8 @@ struct AppendCouplingToParams; /// @brief Specialization of AppendCouplingToParams. template struct AppendCouplingToParams> { - private: - template - struct expand; - template - struct expand> { - using type = Parameters; - }; - - public: - using type = typename expand>::type; ///< The appended parameter list. + using type = typename AppendParameters, Parameters>::type; + ///< The appended parameter list. }; } // namespace detail From 64d58f0c20c95e910b3988d5f24cb64225f4173c Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 14 May 2026 10:51:26 -0700 Subject: [PATCH 63/67] Docs. --- .../coupling_params.hpp | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index dc851db89e..120ef6bc84 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -300,50 +300,60 @@ decltype(auto) applyTimeRuleAndCoupling(const Rule& rule, const Coupling& coupli template struct FlattenCoupling; +/// @brief Converts a `std::tuple<...>` of spaces into `Parameters<...>`. template struct TupleToParameters; +/// @brief Specialization of TupleToParameters for a tuple of spaces. template struct TupleToParameters> { - using type = Parameters; + using type = Parameters; ///< The converted parameter pack. }; +/// @brief Typedef for converting a tuple of spaces into `Parameters<...>`. template using tuple_to_parameters_t = typename TupleToParameters>::type; +/// @brief Maps a coupling pack type to a `std::tuple<...>` of its spaces. template struct pack_tuple; +/// @brief Specialization of pack_tuple for physics coupling fields. template struct pack_tuple> { - using type = std::tuple; + using type = std::tuple; ///< The coupling spaces as a tuple. }; +/// @brief Specialization of pack_tuple for parameter-only fields. template struct pack_tuple> { - using type = std::tuple; + using type = std::tuple; ///< The parameter spaces as a tuple. }; +/// @brief Typedef for extracting a tuple of spaces from a coupling pack. template using pack_tuple_t = typename pack_tuple>::type; +/// @brief Typedef for concatenating space tuples with `std::tuple_cat`. template using tuple_cat_t = decltype(std::tuple_cat(std::declval()...)); +/// @brief Appends coupling parameter spaces to an existing `Parameters<...>` list. template struct AppendParameters; +/// @brief Specialization of AppendParameters for two `Parameters<...>` packs. template struct AppendParameters, Parameters> { - using type = Parameters; + using type = Parameters; ///< The appended parameter list. }; /// @brief Specialization of FlattenCoupling for a tuple of coupling packs. template struct FlattenCoupling> { public: - using tuple_type = tuple_cat_t...>; - using parameters = tuple_to_parameters_t; ///< Flattened parameter spaces. + using tuple_type = tuple_cat_t...>; ///< The flattened tuple of coupling spaces. + using parameters = tuple_to_parameters_t; ///< Flattened parameter spaces. }; /// @brief Typedef for flattened coupling parameter spaces. From 62eed8b46c2d6a18b4fb0e73cce0df6bf4e2fb68 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 21 May 2026 11:00:05 -0600 Subject: [PATCH 64/67] Try to cleanup a few interface details, simpler TimeInfo material, some duplication removal. --- .../coupling_params.hpp | 32 ++++++------- .../make_time_info_material.hpp | 34 ++++++++++++++ .../multiphysics_time_integrator.hpp | 19 ++------ .../tests/test_solid_dynamics.cpp | 43 +++++++++--------- .../test_solid_static_with_internal_vars.cpp | 45 ++++++++++--------- 5 files changed, 98 insertions(+), 75 deletions(-) create mode 100644 src/smith/differentiable_numerics/make_time_info_material.hpp diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 120ef6bc84..0ba811de45 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -49,12 +49,6 @@ struct PhysicsFields { } }; -template -struct is_physics_fields_arg : std::false_type {}; - -template -struct is_physics_fields_arg> : std::true_type {}; - /** * @brief Registered parameter-only field bundle. */ @@ -81,12 +75,24 @@ struct CouplingFields { std::tuple packs; ///< The coupling packs. }; +namespace detail { + +template +struct is_physics_fields_impl : std::false_type {}; + +template +struct is_physics_fields_impl> : std::true_type {}; + +template +inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; + +} // namespace detail + /// Helper to construct a CouplingFields bundle template auto couplingFields(const PFs&... pfs) { - static_assert((is_physics_fields_arg>::value && ...), - "couplingFields(...) only accepts PhysicsFields packs"); + static_assert((detail::is_physics_fields_v && ...), "couplingFields(...) only accepts PhysicsFields packs"); return CouplingFields{std::make_tuple(pfs...)}; } @@ -106,16 +112,6 @@ auto registerParameterFields(const std::shared_ptr& field_store, Fie namespace detail { -template -struct is_physics_fields_impl : std::false_type {}; - -template -struct is_physics_fields_impl> : std::true_type {}; - -/// @brief True if T is a PhysicsFields type. -template -inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; - template struct is_parameter_pack_impl : std::false_type {}; diff --git a/src/smith/differentiable_numerics/make_time_info_material.hpp b/src/smith/differentiable_numerics/make_time_info_material.hpp new file mode 100644 index 0000000000..fcfcf90e73 --- /dev/null +++ b/src/smith/differentiable_numerics/make_time_info_material.hpp @@ -0,0 +1,34 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#pragma once + +#include "smith/physics/common.hpp" +#include "smith/infrastructure/accelerator.hpp" + +namespace smith { + +template +struct TimeInfoMaterial { + using State = typename Material::State; + + Material material; + + template + SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, StateType&& state, GradUType&& grad_u, + const GradVType& /*grad_v*/, Args&&... args) const + { + return material(std::forward(state), std::forward(grad_u), std::forward(args)...); + } +}; + +template +TimeInfoMaterial makeTimeInfoMaterial(Material mat) +{ + return TimeInfoMaterial{std::move(mat)}; +} + +} // namespace smith diff --git a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp index 3a224b5edd..e92e5519e9 100644 --- a/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp +++ b/src/smith/differentiable_numerics/multiphysics_time_integrator.hpp @@ -60,22 +60,11 @@ class MultiphysicsTimeIntegrator : public StateAdvancer { std::map main_unknown_name_to_local_idx_; }; -/** - * @brief Build a `MultiphysicsTimeIntegrator` from system-owned or explicit auxiliary systems. - * - * Missing optional arguments fall back to `system->cycle_zero_systems` and - * `system->post_solve_systems`. - */ -inline std::shared_ptr makeAdvancer( - std::shared_ptr system, std::vector> cycle_zero_systems = {}, - std::vector> post_solve_systems = {}) +/// @brief Build a `MultiphysicsTimeIntegrator` using the system's own auxiliary systems. +inline std::shared_ptr makeAdvancer(std::shared_ptr system) { - if (cycle_zero_systems.empty()) { - cycle_zero_systems = system->cycle_zero_systems; - } - if (post_solve_systems.empty()) { - post_solve_systems = system->post_solve_systems; - } + auto cycle_zero_systems = system->cycle_zero_systems; + auto post_solve_systems = system->post_solve_systems; return std::make_shared(std::move(system), std::move(cycle_zero_systems), std::move(post_solve_systems)); } diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 4895ce9e8c..eb59a029ab 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -45,19 +45,6 @@ inline void checkUnconstrainedReactions(const FiniteElementDual& reaction, const << "Reaction forces should be zero at non-Dirichlet DOFs. Max violation: " << max_unconstrained; } -LinearSolverOptions solid_linear_options{.linear_solver = LinearSolver::CG, - .preconditioner = Preconditioner::HypreJacobi, - .relative_tol = 1e-11, - .absolute_tol = 1e-11, - .max_iterations = 10000, - .print_level = 0}; - -NonlinearSolverOptions solid_nonlinear_opts{.nonlin_solver = NonlinearSolver::TrustRegion, - .relative_tol = 1.0e-10, - .absolute_tol = 1.0e-10, - .max_iterations = 500, - .print_level = 0}; - static constexpr int dim = 3; static constexpr int order = 1; @@ -66,6 +53,19 @@ using VectorSpace = H1; using ScalarParameterSpace = L2<0>; struct SolidMechanicsMeshFixture : public testing::Test { + LinearSolverOptions solid_linear_options{.linear_solver = LinearSolver::CG, + .preconditioner = Preconditioner::HypreJacobi, + .relative_tol = 1e-11, + .absolute_tol = 1e-11, + .max_iterations = 10000, + .print_level = 0}; + + NonlinearSolverOptions solid_nonlinear_opts{.nonlin_solver = NonlinearSolver::TrustRegion, + .relative_tol = 1.0e-10, + .absolute_tol = 1.0e-10, + .max_iterations = 500, + .print_level = 0}; + double length = 1.0; double width = 0.04; int num_elements_x = 12; @@ -331,7 +331,9 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi EXPECT_NEAR(0.0, vector_error("freefall_acceleration_error", states[3], gravity), 1e-6); } -auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr mesh) +auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr mesh, + const NonlinearSolverOptions& nonlinear_opts, + const LinearSolverOptions& linear_opts) { auto field_store = std::make_shared(mesh, 100, physics_name); @@ -339,8 +341,7 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptr("shear")); auto solid_fields = registerSolidMechanicsFields(field_store); - auto solver = - std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh)); + auto solver = std::make_shared(buildNonlinearBlockSolver(nonlinear_opts, linear_opts, *mesh)); auto solid_system = buildSolidMechanicsSystem(solver, SolidMechanicsOptions{}, solid_fields, param_fields); auto physics = makeDifferentiablePhysics(solid_system, physics_name); @@ -386,7 +387,8 @@ TEST_F(SolidMechanicsMeshFixture, SensitivitiesGretl) { SMITH_MARK_FUNCTION; std::string physics_name = "solid"; - auto [physics, shape_disp, initial_states, params, bcs] = createSolidMechanicsBasePhysics(physics_name, mesh); + auto [physics, shape_disp, initial_states, params, bcs] = + createSolidMechanicsBasePhysics(physics_name, mesh, solid_nonlinear_opts, solid_linear_options); auto pv_writer = smith::createParaviewWriter(*mesh, physics->getFieldStatesAndParamStates(), physics_name); pv_writer.write(0, physics->time(), physics->getFieldStatesAndParamStates()); @@ -465,7 +467,8 @@ TEST_F(SolidMechanicsMeshFixture, SensitivitiesBasePhysics) { SMITH_MARK_FUNCTION; std::string physics_name = "solid"; - auto [physics, shape_disp, initial_states, params, bcs] = createSolidMechanicsBasePhysics(physics_name, mesh); + auto [physics, shape_disp, initial_states, params, bcs] = + createSolidMechanicsBasePhysics(physics_name, mesh, solid_nonlinear_opts, solid_linear_options); double qoi = integrateForward(*physics, num_steps_, dt_, physics_name + "_reactions"); SLIC_INFO_ROOT(axom::fmt::format("{}", qoi)); @@ -504,7 +507,7 @@ TEST_F(SolidMechanicsMeshFixture, SensitivitiesComparison) // 1. Calculate sensitivities using Gretl auto [physicsGretl, shape_dispG, initial_statesG, paramsG, bcsG] = - createSolidMechanicsBasePhysics(physics_name + "_gretl", mesh); + createSolidMechanicsBasePhysics(physics_name + "_gretl", mesh, solid_nonlinear_opts, solid_linear_options); // Forward pass for (size_t m = 0; m < num_steps_; ++m) { @@ -520,7 +523,7 @@ TEST_F(SolidMechanicsMeshFixture, SensitivitiesComparison) // 2. Calculate sensitivities using BasePhysics manual adjoint auto [physicsBase, shape_dispB, initial_statesB, paramsB, bcsB] = - createSolidMechanicsBasePhysics(physics_name + "_base", mesh); + createSolidMechanicsBasePhysics(physics_name + "_base", mesh, solid_nonlinear_opts, solid_linear_options); // Forward pass double qoiB = integrateForward(*physicsBase, num_steps_, dt_, physics_name + "_base_reactions"); diff --git a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp index 6d402d07b1..d4a085c238 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_static_with_internal_vars.cpp @@ -17,20 +17,6 @@ namespace smith { -LinearSolverOptions solid_linear_options{.linear_solver = LinearSolver::SuperLU, - .preconditioner = Preconditioner::None, - .relative_tol = 1e-12, - .absolute_tol = 1e-12, - .max_iterations = 2000, - .print_level = 0}; - -NonlinearSolverOptions solid_nonlinear_opts{.nonlin_solver = NonlinearSolver::NewtonLineSearch, - .relative_tol = 1.0e-10, - .absolute_tol = 1.0e-10, - .max_iterations = 100, - .max_line_search_iterations = 50, - .print_level = 1}; - static constexpr int dim = 3; static constexpr int disp_order = 1; static constexpr int state_order = 0; @@ -40,6 +26,20 @@ using DispRule = QuasiStaticSecondOrderTimeIntegrationRule; using InternalVariableRule = BackwardEulerFirstOrderTimeIntegrationRule; struct SolidStaticWithInternalVarsFixture : public testing::Test { + LinearSolverOptions solid_linear_options{.linear_solver = LinearSolver::SuperLU, + .preconditioner = Preconditioner::None, + .relative_tol = 1e-12, + .absolute_tol = 1e-12, + .max_iterations = 2000, + .print_level = 0}; + + NonlinearSolverOptions solid_nonlinear_opts{.nonlin_solver = NonlinearSolver::NewtonLineSearch, + .relative_tol = 1.0e-10, + .absolute_tol = 1.0e-10, + .max_iterations = 100, + .max_line_search_iterations = 50, + .print_level = 1}; + void SetUp() override { StateManager::initialize(datastore, "solid_static_with_internal_vars"); @@ -103,9 +103,10 @@ struct StrainNormEvolution { } }; -std::shared_ptr makeSystemSolver(const Mesh& mesh) +std::shared_ptr makeSystemSolver(const NonlinearSolverOptions& nonlinear_opts, + const LinearSolverOptions& linear_opts, const Mesh& mesh) { - return std::make_shared(buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, mesh)); + return std::make_shared(buildNonlinearBlockSolver(nonlinear_opts, linear_opts, mesh)); } auto registerFields(const std::shared_ptr& field_store) @@ -139,8 +140,8 @@ void setPullBoundaryConditions(const std::shared_ptr& solid_sys TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) { - auto solid_solver = makeSystemSolver(*mesh); - auto internal_variable_solver = makeSystemSolver(*mesh); + auto solid_solver = makeSystemSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto internal_variable_solver = makeSystemSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "solid_static_with_internal_vars_"); @@ -170,8 +171,8 @@ TEST_F(SolidStaticWithInternalVarsFixture, CoupledSolve) TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) { - auto solid_solver = makeSystemSolver(*mesh); - auto internal_variable_solver = makeSystemSolver(*mesh); + auto solid_solver = makeSystemSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto internal_variable_solver = makeSystemSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto disp_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto internal_variable_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); @@ -202,8 +203,8 @@ TEST_F(SolidStaticWithInternalVarsFixture, StaggeredSolveWithRelaxation) TEST_F(SolidStaticWithInternalVarsFixture, BodyForceAndTraction) { - auto solid_solver = makeSystemSolver(*mesh); - auto internal_variable_solver = makeSystemSolver(*mesh); + auto solid_solver = makeSystemSolver(solid_nonlinear_opts, solid_linear_options, *mesh); + auto internal_variable_solver = makeSystemSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto nonlinear_block_solver = buildNonlinearBlockSolver(solid_nonlinear_opts, solid_linear_options, *mesh); auto coupled_solver = std::make_shared(nonlinear_block_solver); auto field_store = std::make_shared(mesh, 100, "body_force_test_"); From 0d4ebeb0ed1eb4615b86d12a44b4a3294825fe05 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 21 May 2026 12:14:37 -0600 Subject: [PATCH 65/67] Try to be better about using existing material models with the new interface. --- .../make_time_info_material.hpp | 7 ++-- .../tests/CMakeLists.txt | 1 + .../tests/test_combined_thermo_mechanics.cpp | 19 ++++++----- .../tests/test_make_time_info_material.cpp | 33 +++++++++++++++++++ .../tests/test_solid_dynamics.cpp | 13 ++++---- 5 files changed, 55 insertions(+), 18 deletions(-) create mode 100644 src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp diff --git a/src/smith/differentiable_numerics/make_time_info_material.hpp b/src/smith/differentiable_numerics/make_time_info_material.hpp index fcfcf90e73..83d5dd1e8b 100644 --- a/src/smith/differentiable_numerics/make_time_info_material.hpp +++ b/src/smith/differentiable_numerics/make_time_info_material.hpp @@ -6,21 +6,22 @@ #pragma once +#include + #include "smith/physics/common.hpp" #include "smith/infrastructure/accelerator.hpp" namespace smith { template -struct TimeInfoMaterial { +struct TimeInfoMaterial : Material { using State = typename Material::State; - Material material; - template SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, StateType&& state, GradUType&& grad_u, const GradVType& /*grad_v*/, Args&&... args) const { + const Material& material = *this; return material(std::forward(state), std::forward(grad_u), std::forward(args)...); } }; diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index 135e0771c8..28a97dc795 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -9,6 +9,7 @@ set(differentiable_numerics_test_depends smith_physics smith_differentiable_nume set(differentiable_numerics_test_source test_solver_convergence.cpp test_field_state.cpp + test_make_time_info_material.cpp test_solid_dynamics.cpp test_explicit_dynamics.cpp test_porous_heat_sink.cpp diff --git a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp index 95494b0085..37d8cef6e5 100644 --- a/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp +++ b/src/smith/differentiable_numerics/tests/test_combined_thermo_mechanics.cpp @@ -25,8 +25,7 @@ #include "smith/differentiable_numerics/differentiable_test_utils.hpp" #include "smith/differentiable_numerics/evaluate_objective.hpp" #include "smith/differentiable_numerics/nonlinear_solve.hpp" -#include "smith/differentiable_numerics/time_info_solid_materials.hpp" -#include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" +#include "smith/differentiable_numerics/make_time_info_material.hpp" #include "smith/physics/functional_objective.hpp" #include "gretl/wang_checkpoint_strategy.hpp" @@ -238,8 +237,8 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughPhysics) thermal_fields, couplingFields(solid_fields), param_fields); auto coupled_system = combineSystems(solid_system, thermal_system); - thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + auto material = makeTimeInfoMaterial( + thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}); setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); coupled_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -307,8 +306,8 @@ TEST_F(ThermoMechanicsMeshFixture, BackpropagateThroughStaggeredPhysics) coupled_solver->addSubsystemSolver({1}, buildNonlinearBlockSolver(thermal_nonlin_opts, thermal_lin_opts, *mesh_)); auto coupled_system = combineSystems(coupled_solver, solid_system, thermal_system); - thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + auto material = makeTimeInfoMaterial( + thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}); setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); coupled_system->field_store->getParameterFields()[0].get()->setFromFieldFunction( @@ -385,7 +384,8 @@ TEST_F(ThermoMechanicsMeshFixture, StaggeredBucklingChallenge) thermal_fields, couplingFields(solid_fields)); auto coupled_system = combineSystems(solid_system, thermal_system); - thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + auto material = makeTimeInfoMaterial( + thermomechanics::GreenSaintVenantThermoelasticMaterial{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}); setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); applyBucklingLoads(solid_system, thermal_system, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); @@ -413,7 +413,8 @@ TEST_F(ThermoMechanicsMeshFixture, MonolithicBucklingChallenge) auto thermal_system = buildThermalSystem(nullptr, ThermalOptions{}, thermal_fields, couplingFields(solid_fields)); auto coupled_system = combineSystems(solver_ptr, solid_system, thermal_system); - thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + auto material = makeTimeInfoMaterial( + thermomechanics::GreenSaintVenantThermoelasticMaterial{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}); setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh_->entireBodyName()); applyBucklingLoads(solid_system, thermal_system, kBucklingTraction, kBucklingBodyForce, kBucklingHeatSource); @@ -438,7 +439,7 @@ TEST_F(ThermoMechanicsMeshFixture, CauchyStressOutput) constexpr double G = E / (2.0 * (1.0 + nu)); constexpr double K = E / (3.0 * (1.0 - 2.0 * nu)); - solid_system->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, + solid_system->setMaterial(makeTimeInfoMaterial(solid_mechanics::NeoHookean{.density = 1.0, .K = K, .G = G}), mesh_->entireBodyName()); solid_system->setDisplacementBC(mesh_->domain("left")); diff --git a/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp b/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp new file mode 100644 index 0000000000..c18b50c85f --- /dev/null +++ b/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp @@ -0,0 +1,33 @@ +// Copyright (c) Lawrence Livermore National Security, LLC and +// other Smith Project Developers. See the top-level LICENSE file for +// details. +// +// SPDX-License-Identifier: (BSD-3-Clause) + +#include "gtest/gtest.h" + +#include "smith/differentiable_numerics/make_time_info_material.hpp" +#include "smith/physics/common.hpp" + +namespace smith { + +struct TestMaterialState {}; + +struct StaticMaterial { + using State = TestMaterialState; + + double density = 2.0; + + double operator()(State&, double grad_u, double param) const { return density + grad_u + param; } +}; + +TEST(TimeInfoMaterial, ForwardsStaticMaterial) +{ + auto material = makeTimeInfoMaterial(StaticMaterial{}); + StaticMaterial::State state; + + EXPECT_EQ(material.density, 2.0); + EXPECT_EQ(material(TimeInfo(10.0, 20.0), state, 1.0, 100.0, 3.0), 6.0); +} + +} // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index eb59a029ab..1d3bc605d2 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -16,6 +16,8 @@ #include "smith/physics/state/state_manager.hpp" #include "smith/physics/functional_objective.hpp" #include "smith/physics/boundary_conditions/boundary_condition_manager.hpp" +#include "smith/physics/materials/parameterized_solid_material.hpp" +#include "smith/physics/materials/solid_material.hpp" #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/system_solver.hpp" @@ -23,7 +25,7 @@ #include "smith/differentiable_numerics/paraview_writer.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" -#include "smith/differentiable_numerics/time_info_solid_materials.hpp" +#include "smith/differentiable_numerics/make_time_info_material.hpp" namespace smith { @@ -187,8 +189,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) double nu = 0.25; auto K = E / (3.0 * (1.0 - 2.0 * nu)); auto G = E / (2.0 * (1.0 + nu)); - using MaterialType = solid_mechanics::TimeInfoParameterizedNeoHookeanSolid; - MaterialType material{.density = 1.0, .K0 = K, .G0 = G}; + auto material = makeTimeInfoMaterial(solid_mechanics::ParameterizedNeoHookeanSolid{.density = 1.0, .K0 = K, .G0 = G}); // Set parameters auto params = solid_system->field_store->getParameterFields(); @@ -285,7 +286,7 @@ TEST_F(SolidMechanicsMeshFixture, TransientFreefallWithConsistentBoundaryConditi double nu = 0.25; auto K = E / (3.0 * (1.0 - 2.0 * nu)); auto G = E / (2.0 * (1.0 + nu)); - solid_system->setMaterial(solid_mechanics::TimeInfoNeoHookean{.density = 1.0, .K = K, .G = G}, + solid_system->setMaterial(makeTimeInfoMaterial(solid_mechanics::NeoHookean{.density = 1.0, .K = K, .G = G}), mesh->entireBodyName()); solid_system->addBodyForce(mesh->entireBodyName(), @@ -359,8 +360,8 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptrsetMaterial(material, mesh->entireBodyName()); From 3b0c7e91f661a39d6c3b6edd4e50007263e47d6b Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 21 May 2026 12:31:32 -0600 Subject: [PATCH 66/67] Try to cleanup material wrappers even more. --- .../composable_solid_mechanics.cpp | 2 +- .../composable_thermo_mechanics.cpp | 6 +- .../composable_thermo_mechanics_advanced.cpp | 7 ++- .../differentiable_numerics/CMakeLists.txt | 3 +- .../make_time_info_material.hpp | 9 ++- .../tests/test_make_time_info_material.cpp | 1 + .../tests/test_solid_dynamics.cpp | 15 ++--- .../time_info_solid_materials.hpp | 52 ---------------- .../time_info_thermo_mechanical_materials.hpp | 61 ------------------- 9 files changed, 24 insertions(+), 132 deletions(-) delete mode 100644 src/smith/differentiable_numerics/time_info_solid_materials.hpp delete mode 100644 src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp diff --git a/examples/solid_mechanics/composable_solid_mechanics.cpp b/examples/solid_mechanics/composable_solid_mechanics.cpp index 20b5fb68ad..28a99f4368 100644 --- a/examples/solid_mechanics/composable_solid_mechanics.cpp +++ b/examples/solid_mechanics/composable_solid_mechanics.cpp @@ -23,11 +23,11 @@ #include "smith/differentiable_numerics/nonlinear_block_solver.hpp" #include "smith/differentiable_numerics/system_solver.hpp" #include "smith/differentiable_numerics/solid_mechanics_system.hpp" -#include "smith/differentiable_numerics/time_info_solid_materials.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/differentiable_numerics/evaluate_objective.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" #include "smith/differentiable_numerics/paraview_writer.hpp" +#include "smith/physics/materials/solid_material.hpp" // _includes_end namespace { diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index 777dd84737..c3c199e9c8 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -21,7 +21,7 @@ #include "smith/differentiable_numerics/solid_mechanics_system.hpp" #include "smith/differentiable_numerics/thermal_system.hpp" #include "smith/differentiable_numerics/thermo_mechanics_system.hpp" -#include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" +#include "smith/differentiable_numerics/make_time_info_material.hpp" #include "smith/differentiable_numerics/combined_system.hpp" #include "smith/differentiable_numerics/differentiable_physics.hpp" #include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" @@ -79,8 +79,8 @@ int main(int argc, char* argv[]) auto coupled_system = smith::combineSystems(solid_system, thermal_system); - smith::thermomechanics::GreenSaintVenantThermoelasticMaterialWithTimeInfo material{1.0, 100.0, 0.25, 1.0, - 0.0025, 0.0, 0.05}; + auto material = smith::makeTimeInfoMaterial( + smith::thermomechanics::GreenSaintVenantThermoelasticMaterial{1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}); smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); // _build_end diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 4a1ebc1e79..aa051363f9 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -31,7 +31,7 @@ #include "smith/differentiable_numerics/paraview_writer.hpp" #include "smith/differentiable_numerics/evaluate_objective.hpp" #include "smith/differentiable_numerics/differentiable_test_utils.hpp" -#include "smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp" +#include "smith/differentiable_numerics/make_time_info_material.hpp" #include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" // _includes_end @@ -85,8 +85,9 @@ int main(int argc, char* argv[]) auto thermal_system = smith::buildThermalSystem(nullptr, smith::ThermalOptions{}, thermal_fields, smith::couplingFields(solid_fields), param_fields); - smith::thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{ - 1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; + auto material = + smith::makeTimeInfoMaterial(smith::thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterial{ + 1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}); smith::setCoupledThermoMechanicsMaterial(solid_system, thermal_system, material, mesh->entireBodyName()); auto coupled_solver = std::make_shared(10); diff --git a/src/smith/differentiable_numerics/CMakeLists.txt b/src/smith/differentiable_numerics/CMakeLists.txt index c9421cd26d..71ade68cc9 100644 --- a/src/smith/differentiable_numerics/CMakeLists.txt +++ b/src/smith/differentiable_numerics/CMakeLists.txt @@ -42,8 +42,7 @@ set(differentiable_numerics_headers thermal_system.hpp thermo_mechanics_system.hpp thermo_mechanics_with_internal_vars_system.hpp - time_info_solid_materials.hpp - time_info_thermo_mechanical_materials.hpp + make_time_info_material.hpp coupling_params.hpp combined_system.hpp system_base.hpp diff --git a/src/smith/differentiable_numerics/make_time_info_material.hpp b/src/smith/differentiable_numerics/make_time_info_material.hpp index 83d5dd1e8b..72d57e4ef8 100644 --- a/src/smith/differentiable_numerics/make_time_info_material.hpp +++ b/src/smith/differentiable_numerics/make_time_info_material.hpp @@ -14,14 +14,16 @@ namespace smith { template -struct TimeInfoMaterial : Material { +struct TimeInfoMaterial { using State = typename Material::State; + Material material; ///< Wrapped material. + double density; ///< Density forwarded for solid mechanics inertial terms. + template SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, StateType&& state, GradUType&& grad_u, const GradVType& /*grad_v*/, Args&&... args) const { - const Material& material = *this; return material(std::forward(state), std::forward(grad_u), std::forward(args)...); } }; @@ -29,7 +31,8 @@ struct TimeInfoMaterial : Material { template TimeInfoMaterial makeTimeInfoMaterial(Material mat) { - return TimeInfoMaterial{std::move(mat)}; + const double density = mat.density; + return TimeInfoMaterial{std::move(mat), density}; } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp b/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp index c18b50c85f..3cbdb0fee4 100644 --- a/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp +++ b/src/smith/differentiable_numerics/tests/test_make_time_info_material.cpp @@ -28,6 +28,7 @@ TEST(TimeInfoMaterial, ForwardsStaticMaterial) EXPECT_EQ(material.density, 2.0); EXPECT_EQ(material(TimeInfo(10.0, 20.0), state, 1.0, 100.0, 3.0), 6.0); + EXPECT_EQ(material.material.density, 2.0); } } // namespace smith diff --git a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp index 1d3bc605d2..333e8d39c0 100644 --- a/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp +++ b/src/smith/differentiable_numerics/tests/test_solid_dynamics.cpp @@ -189,12 +189,13 @@ TEST_F(SolidMechanicsMeshFixture, TransientConstantGravity) double nu = 0.25; auto K = E / (3.0 * (1.0 - 2.0 * nu)); auto G = E / (2.0 * (1.0 + nu)); - auto material = makeTimeInfoMaterial(solid_mechanics::ParameterizedNeoHookeanSolid{.density = 1.0, .K0 = K, .G0 = G}); + solid_mechanics::ParameterizedNeoHookeanSolid material_base{.density = 1.0, .K0 = K, .G0 = G}; + auto material = makeTimeInfoMaterial(material_base); // Set parameters auto params = solid_system->field_store->getParameterFields(); - params[0].get()->setFromFieldFunction([=](tensor) { return material.K0; }); - params[1].get()->setFromFieldFunction([=](tensor) { return material.G0; }); + params[0].get()->setFromFieldFunction([=](tensor) { return material_base.K0; }); + params[1].get()->setFromFieldFunction([=](tensor) { return material_base.G0; }); solid_system->setMaterial(material, mesh->entireBodyName()); @@ -360,8 +361,8 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptrsetMaterial(material, mesh->entireBodyName()); @@ -371,12 +372,12 @@ auto createSolidMechanicsBasePhysics(std::string physics_name, std::shared_ptrsetFromFieldFunction([=](tensor) { double scaling = 1.0; - return scaling * material.K0; + return scaling * material_base.K0; }); params[1].get()->setFromFieldFunction([=](tensor) { double scaling = 1.0; - return scaling * material.G0; + return scaling * material_base.G0; }); physics->resetStates(); diff --git a/src/smith/differentiable_numerics/time_info_solid_materials.hpp b/src/smith/differentiable_numerics/time_info_solid_materials.hpp deleted file mode 100644 index a4a3f048cd..0000000000 --- a/src/smith/differentiable_numerics/time_info_solid_materials.hpp +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#pragma once - -#include "smith/physics/common.hpp" -#include "smith/physics/materials/parameterized_solid_material.hpp" -#include "smith/physics/materials/solid_material.hpp" - -namespace smith::solid_mechanics { - -/// @brief TimeInfo-aware wrapper for `NeoHookean`. -struct TimeInfoNeoHookean { - /// State type reused from wrapped material. - using State = NeoHookean::State; - - double density; ///< Mass density. - double K; ///< Bulk modulus. - double G; ///< Shear modulus. - - template - /// @brief Evaluate wrapped material, ignoring velocity gradient. - SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, State& state, const tensor& grad_u, - const GradVType& /*grad_v*/) const - { - return NeoHookean{.density = density, .K = K, .G = G}(state, grad_u); - } -}; - -/// @brief TimeInfo-aware wrapper for `ParameterizedNeoHookeanSolid`. -struct TimeInfoParameterizedNeoHookeanSolid { - /// State type reused from wrapped material. - using State = ParameterizedNeoHookeanSolid::State; - - double density; ///< Mass density. - double K0; ///< Base bulk modulus. - double G0; ///< Base shear modulus. - - template - /// @brief Evaluate wrapped material, ignoring velocity gradient. - SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, State& state, - const smith::tensor& grad_u, const GradVType& /*grad_v*/, - const BulkType& delta_k, const ShearType& delta_g) const - { - return ParameterizedNeoHookeanSolid{.density = density, .K0 = K0, .G0 = G0}(state, grad_u, delta_k, delta_g); - } -}; - -} // namespace smith::solid_mechanics diff --git a/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp b/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp deleted file mode 100644 index a8607e79a3..0000000000 --- a/src/smith/differentiable_numerics/time_info_thermo_mechanical_materials.hpp +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Lawrence Livermore National Security, LLC and -// other Smith Project Developers. See the top-level LICENSE file for -// details. -// -// SPDX-License-Identifier: (BSD-3-Clause) - -#pragma once - -#include "smith/physics/common.hpp" -#include "smith/physics/materials/green_saint_venant_thermoelastic.hpp" - -namespace smith::thermomechanics { - -/// @brief TimeInfo-aware wrapper for `GreenSaintVenantThermoelasticMaterial`. -struct GreenSaintVenantThermoelasticMaterialWithTimeInfo { - /// State type reused from wrapped material. - using State = GreenSaintVenantThermoelasticMaterial::State; - - double density; ///< Mass density. - double E; ///< Young's modulus. - double nu; ///< Poisson ratio. - double C_v; ///< Heat capacity. - double alpha; ///< Thermal expansion coefficient. - double theta_ref; ///< Reference temperature. - double kappa; ///< Thermal conductivity. - - template - /// @brief Evaluate wrapped material, ignoring velocity gradient. - auto operator()(const TimeInfo& /*t_info*/, State& state, const tensor& grad_u, - const GradVType& /*grad_v*/, T2 theta, const tensor& grad_theta) const - { - return GreenSaintVenantThermoelasticMaterial{density, E, nu, C_v, alpha, theta_ref, kappa}(state, grad_u, theta, - grad_theta); - } -}; - -/// @brief TimeInfo-aware wrapper for `ParameterizedGreenSaintVenantThermoelasticMaterial`. -struct ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo { - /// State type reused from wrapped material. - using State = ParameterizedGreenSaintVenantThermoelasticMaterial::State; - - double density; ///< Mass density. - double E; ///< Young's modulus. - double nu; ///< Poisson ratio. - double C_v; ///< Heat capacity. - double alpha0; ///< Reference thermal expansion coefficient. - double theta_ref; ///< Reference temperature. - double kappa; ///< Thermal conductivity. - - template - /// @brief Evaluate wrapped material, ignoring velocity gradient. - auto operator()(const TimeInfo& /*t_info*/, State& state, const tensor& grad_u, - const GradVType& /*grad_v*/, T2 theta, const tensor& grad_theta, - T4 thermal_expansion_scaling) const - { - return ParameterizedGreenSaintVenantThermoelasticMaterial{density, E, nu, C_v, alpha0, theta_ref, kappa}( - state, grad_u, theta, grad_theta, thermal_expansion_scaling); - } -}; - -} // namespace smith::thermomechanics From a2ab27acf09dafd9e66f5868ef4b1bd9b21bb5ba Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 21 May 2026 12:49:15 -0600 Subject: [PATCH 67/67] fix docs. --- src/smith/differentiable_numerics/coupling_params.hpp | 1 + src/smith/differentiable_numerics/make_time_info_material.hpp | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 0ba811de45..a96d409661 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -83,6 +83,7 @@ struct is_physics_fields_impl : std::false_type {}; template struct is_physics_fields_impl> : std::true_type {}; +/// True when T is a PhysicsFields pack. template inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; diff --git a/src/smith/differentiable_numerics/make_time_info_material.hpp b/src/smith/differentiable_numerics/make_time_info_material.hpp index 72d57e4ef8..f442dd6c68 100644 --- a/src/smith/differentiable_numerics/make_time_info_material.hpp +++ b/src/smith/differentiable_numerics/make_time_info_material.hpp @@ -13,13 +13,16 @@ namespace smith { +/// @brief Adapter that lets a material without time information satisfy the TimeInfo material interface. template struct TimeInfoMaterial { + /// State type forwarded from the wrapped material. using State = typename Material::State; Material material; ///< Wrapped material. double density; ///< Density forwarded for solid mechanics inertial terms. + /// @brief Evaluate the wrapped material, ignoring TimeInfo and velocity gradient. template SMITH_HOST_DEVICE auto operator()(const TimeInfo& /*t_info*/, StateType&& state, GradUType&& grad_u, const GradVType& /*grad_v*/, Args&&... args) const @@ -28,6 +31,7 @@ struct TimeInfoMaterial { } }; +/// @brief Create a TimeInfoMaterial adapter for a material with signature material(state, grad_u, args...). template TimeInfoMaterial makeTimeInfoMaterial(Material mat) {

{}, "theta"); diff --git a/src/smith/physics/tests/solid.cpp b/src/smith/physics/tests/solid.cpp index 98b76bdffa..e48bed601f 100644 --- a/src/smith/physics/tests/solid.cpp +++ b/src/smith/physics/tests/solid.cpp @@ -185,7 +185,7 @@ void functional_parameterized_solid_test(double expected_disp_norm) solid_solver.setParameter(1, user_defined_shear_modulus); solid_mechanics::ParameterizedLinearIsotropicSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(mat, mesh->entireBody()); + solid_solver.setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); // Specify initial / boundary conditions mesh->addDomainOfBoundaryElements("essential_boundary", by_attr(1)); @@ -206,8 +206,10 @@ void functional_parameterized_solid_test(double expected_disp_norm) // add some nonexistent body forces / tractions to check that // these parameterized versions compile and run without error - solid_solver.addBodyForce([](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, mesh->entireBody()); - solid_solver.addBodyForce(ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, mesh->entireBody()); + solid_solver.addBodyForce( + DependsOn<0>{}, [](const auto& x, double /*t*/, auto /* bulk */) { return x * 0.0; }, mesh->entireBody()); + solid_solver.addBodyForce(DependsOn<1>{}, ParameterizedBodyForce{[](const auto& x) { return 0.0 * x; }}, + mesh->entireBody()); solid_solver.setTraction(DependsOn<1>{}, [](const auto& x, auto...) { return 0 * x; }, mesh->entireBoundary()); // Finalize the data structures diff --git a/src/smith/physics/tests/solid_finite_diff.cpp b/src/smith/physics/tests/solid_finite_diff.cpp index 839f0d9d63..2c4ee797f5 100644 --- a/src/smith/physics/tests/solid_finite_diff.cpp +++ b/src/smith/physics/tests/solid_finite_diff.cpp @@ -83,7 +83,7 @@ TEST(SolidMechanics, FiniteDifferenceParameter) constexpr int bulk_parameter_index = 0; solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(mat, mesh->entireBody()); + solid_solver.setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); // Define a boundary attribute set and specify initial / boundary conditions mesh->addDomainOfBoundaryElements("essential_boundary", by_attr(1)); diff --git a/src/smith/physics/tests/solid_periodic.cpp b/src/smith/physics/tests/solid_periodic.cpp index 3de6216e15..9d6c56c341 100644 --- a/src/smith/physics/tests/solid_periodic.cpp +++ b/src/smith/physics/tests/solid_periodic.cpp @@ -81,7 +81,7 @@ void periodic_test(mfem::Element::Type element_type) solid_solver.setParameter(1, user_defined_shear_modulus); solid_mechanics::ParameterizedNeoHookeanSolid mat{1.0, 0.0, 0.0}; - solid_solver.setMaterial(mat, mesh->entireBody()); + solid_solver.setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); mesh->addDomainOfBoundaryElements("support", by_attr(2)); solid_solver.setFixedBCs(mesh->domain("support")); diff --git a/src/smith/physics/tests/solid_reaction_adjoint.cpp b/src/smith/physics/tests/solid_reaction_adjoint.cpp index fc40979a2d..62d31c8f5d 100644 --- a/src/smith/physics/tests/solid_reaction_adjoint.cpp +++ b/src/smith/physics/tests/solid_reaction_adjoint.cpp @@ -64,7 +64,7 @@ std::unique_ptr createNonlinearSolidMechanicsSolver(std::sha solid->setParameter(0, user_defined_bulk_modulus); solid->setParameter(1, user_defined_shear_modulus); - solid->setMaterial(mat, mesh->entireBody()); + solid->setMaterial(DependsOn<0, 1>{}, mat, mesh->entireBody()); solid->addBodyForce( [](auto X, auto /* t */) { diff --git a/src/smith/physics/tests/thermal_finite_diff.cpp b/src/smith/physics/tests/thermal_finite_diff.cpp index 0a252b42db..ceaaafe17d 100644 --- a/src/smith/physics/tests/thermal_finite_diff.cpp +++ b/src/smith/physics/tests/thermal_finite_diff.cpp @@ -77,7 +77,7 @@ TEST(Thermal, FiniteDifference) // Construct a potentially user-defined parameterized material and send it to the thermal module heat_transfer::ParameterizedLinearIsotropicConductor mat; - thermal_solver.setMaterial(mat, mesh->entireBody()); + thermal_solver.setMaterial(DependsOn<0>{}, mat, mesh->entireBody()); // Define a constant source term heat_transfer::ConstantSource source{1.0}; diff --git a/src/smith/physics/tests/thermal_nonlinear_solve.cpp b/src/smith/physics/tests/thermal_nonlinear_solve.cpp index e1de3b1aa3..c9ea761dcd 100644 --- a/src/smith/physics/tests/thermal_nonlinear_solve.cpp +++ b/src/smith/physics/tests/thermal_nonlinear_solve.cpp @@ -65,7 +65,7 @@ void functional_thermal_test_nonlinear() thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, mesh->entireBody()); // clang-format off - thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, [&](auto, auto, auto temperature, auto) { + thermal_solver.addCustomBoundaryIntegral(smith::DependsOn<>{}, [&](auto, auto, auto temperature, auto) { static constexpr double radiateConstant = 5.0-7; static constexpr double T0 = 21.0; using std::pow; diff --git a/src/smith/physics/tests/thermal_robin_condition.cpp b/src/smith/physics/tests/thermal_robin_condition.cpp index 952325e59b..2d27a583d1 100644 --- a/src/smith/physics/tests/thermal_robin_condition.cpp +++ b/src/smith/physics/tests/thermal_robin_condition.cpp @@ -64,7 +64,8 @@ void functional_thermal_test_robin_condition() thermal_solver.setSource([](auto, auto, auto, auto) { return 2.0; }, mesh->entireBody()); // clang-format off - thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, [](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { + thermal_solver.addCustomBoundaryIntegral(DependsOn<>{}, + [](double /* t */, auto /*position*/, auto temperature, auto /*temperature_rate*/) { auto [T, dT_dxi] = temperature; auto q = 5.0*(T-25.0); return q; // define a convective (temperature-proportional) heat flux diff --git a/src/smith/physics/thermomechanics.hpp b/src/smith/physics/thermomechanics.hpp index 266d9a5570..1d21cd1286 100644 --- a/src/smith/physics/thermomechanics.hpp +++ b/src/smith/physics/thermomechanics.hpp @@ -379,8 +379,10 @@ class Thermomechanics : public BasePhysics { // note: these parameter indices are offset by 1 since, internally, this module uses the first parameter // to communicate the temperature and displacement field information to the other physics module // - thermal_.setMaterial(ThermalMaterialInterface{material}, domain); - solid_.setMaterial(MechanicalMaterialInterface{material}, domain, qdata); + thermal_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, ThermalMaterialInterface{material}, + domain); + solid_.setMaterial(DependsOn<0, active_parameters + 1 ...>{}, MechanicalMaterialInterface{material}, + domain, qdata); } /// @overload @@ -388,7 +390,7 @@ class Thermomechanics : public BasePhysics { void setMaterial(const MaterialType& material, Domain& domain, std::shared_ptr> qdata = EmptyQData) { - setMaterial(material, domain, qdata); + setMaterial(DependsOn<>{}, material, domain, qdata); } /** diff --git a/src/smith/physics/thermomechanics_monolithic.hpp b/src/smith/physics/thermomechanics_monolithic.hpp index 6d348871bd..4d9b0fe16f 100644 --- a/src/smith/physics/thermomechanics_monolithic.hpp +++ b/src/smith/physics/thermomechanics_monolithic.hpp @@ -567,7 +567,7 @@ class ThermomechanicsMonolithic, template void setMaterial(const MaterialType& material, Domain& domain) { - setMaterial(material, domain); + setMaterial(DependsOn<>{}, material, domain); } /** @@ -651,7 +651,7 @@ class ThermomechanicsMonolithic, template void addBodyForce(BodyForceType body_force, Domain& domain) { - addBodyForce(body_force, domain); + addBodyForce(DependsOn<>{}, body_force, domain); } /// @overload From 21752aafa33b1b5d7856c9a5ea17c57c71dbf95d Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 12 May 2026 09:09:10 -0600 Subject: [PATCH 55/67] Trying to simplify and clarify templating. --- .../composable_thermo_mechanics.cpp | 8 +- .../composable_thermo_mechanics_advanced.cpp | 8 +- .../combined_system.hpp | 4 +- .../coupling_params.hpp | 337 +++++++++--------- .../solid_mechanics_system.hpp | 28 +- .../state_variable_system.hpp | 14 +- .../tests/test_combined_thermo_mechanics.cpp | 80 +++-- .../test_solid_static_with_internal_vars.cpp | 15 +- ...st_thermo_mechanics_with_internal_vars.cpp | 11 +- .../thermal_system.hpp | 28 +- 10 files changed, 288 insertions(+), 245 deletions(-) diff --git a/examples/thermo_mechanics/composable_thermo_mechanics.cpp b/examples/thermo_mechanics/composable_thermo_mechanics.cpp index f762eb0388..ceab8dd294 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics.cpp @@ -72,10 +72,10 @@ int main(int argc, char* argv[]) auto thermal_solver = std::make_shared(smith::buildNonlinearBlockSolver(nonlinear_options, linear_options, *mesh)); - auto solid_system = smith::buildSolidMechanicsSystem(solid_solver, smith::SolidMechanicsOptions{}, - solid_fields, thermal_fields); - auto thermal_system = - smith::buildThermalSystem(thermal_solver, smith::ThermalOptions{}, thermal_fields, solid_fields); + auto solid_system = smith::buildSolidMechanicsSystem( + solid_solver, smith::SolidMechanicsOptions{}, solid_fields, smith::couplingFields(thermal_fields)); + auto thermal_system = smith::buildThermalSystem( + thermal_solver, smith::ThermalOptions{}, thermal_fields, smith::couplingFields(solid_fields)); auto coupled_system = smith::combineSystems(solid_system, thermal_system); diff --git a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp index 8bc82bb342..f90868ff0e 100644 --- a/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp +++ b/examples/thermo_mechanics/composable_thermo_mechanics_advanced.cpp @@ -65,10 +65,10 @@ int main(int argc, char* argv[]) smith::registerThermalFields(field_store); auto param_fields = smith::registerParameterFields(smith::FieldType>("thermal_expansion_scaling")); - auto solid_system = - smith::buildSolidMechanicsSystem(nullptr, solid_options, solid_fields, param_fields, thermal_fields); - auto thermal_system = smith::buildThermalSystem(nullptr, smith::ThermalOptions{}, thermal_fields, - param_fields, solid_fields); + auto solid_system = smith::buildSolidMechanicsSystem( + nullptr, solid_options, solid_fields, smith::couplingFields(thermal_fields), param_fields); + auto thermal_system = smith::buildThermalSystem( + nullptr, smith::ThermalOptions{}, thermal_fields, smith::couplingFields(solid_fields), param_fields); smith::thermomechanics::ParameterizedGreenSaintVenantThermoelasticMaterialWithTimeInfo material{ 1.0, 100.0, 0.25, 1.0, 0.0025, 0.0, 0.05}; diff --git a/src/smith/differentiable_numerics/combined_system.hpp b/src/smith/differentiable_numerics/combined_system.hpp index eaf48cab31..fa1e1398bb 100644 --- a/src/smith/differentiable_numerics/combined_system.hpp +++ b/src/smith/differentiable_numerics/combined_system.hpp @@ -19,10 +19,10 @@ * auto thermal_fields = registerThermalFields(field_store); * * auto solid_system = buildSolidMechanicsSystem( - * solid_solver, solid_opts, solid_fields, params..., thermal_fields); + * solid_solver, solid_opts, solid_fields, couplingFields(thermal_fields), param_fields); * * auto thermal_system = buildThermalSystem( - * thermal_solver, thermal_opts, thermal_fields, solid_fields); + * thermal_solver, thermal_opts, thermal_fields, couplingFields(solid_fields)); * * auto coupled = combineSystems(solid, thermal); * coupled->setMaterial(thermo_mech_material, domain); // tight coupling diff --git a/src/smith/differentiable_numerics/coupling_params.hpp b/src/smith/differentiable_numerics/coupling_params.hpp index 2ffa12eac6..626e1714d2 100644 --- a/src/smith/differentiable_numerics/coupling_params.hpp +++ b/src/smith/differentiable_numerics/coupling_params.hpp @@ -6,15 +6,13 @@ /** * @file coupling_params.hpp - * @brief CouplingParams type and helpers for injecting coupled-physics fields into weak form parameter packs. + * @brief Coupling pack types and helpers for injecting explicit coupled-physics fields into weak form parameter packs. * - * Convention: coupling fields occupy the *leading* positions of the "tail" parameter pack in every - * weak form constructed with a non-empty CouplingParams. Concretely, after the time-rule state fields - * (e.g. u, u_old, v_old, a_old for solid) come the coupling fields in the order declared in - * CouplingParams::fields, and only then come the user-supplied parameter_space fields. + * Builders accept at most two optional trailing arguments after `self_fields`: + * 1. `couplingFields(foreign_physics_fields...)` — foreign physics contributions + * 2. `param_fields` (a `CouplingParams<...>`) — user parameter fields (must be last) * - * This ordering must be respected in every setMaterial / addBodyForce / addTraction / addPressure - * closure: the `auto...` tail pack is partitioned as (coupling_fields..., user_params...). + * Tail of each material/source closure: `(coupling_fields..., parameter_fields...)`. */ #pragma once @@ -29,82 +27,70 @@ namespace smith { /** - * @brief Declares the finite element spaces and field names of fields borrowed from another physics. - * - * @tparam Spaces FE space types of the coupling fields (e.g. H1, H1). - * - * Usage: - * @code - * CouplingParams solid_coupling{FieldType>("temperature"), - * FieldType>("temperature_old")}; - * @endcode - * - * The default CouplingParams<> (empty) leaves weak form parameter packs unchanged. + * @brief Declares the finite element spaces and field names of parameter fields. */ template struct CouplingParams { - static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); ///< Number of borrowed or parameter fields. - std::tuple...> fields; ///< Coupling field descriptors in weak-form argument order. - /// @brief Construct a coupling pack from field descriptors. + static constexpr std::size_t num_fields = sizeof...(Spaces); + static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); + std::tuple...> fields; CouplingParams(FieldType... fs) : fields(std::move(fs)...) {} }; -/** - * @brief Deduction guide for `CouplingParams`. - * - * Example: - * @code - * CouplingParams{FieldType("a"), FieldType("b")} - * @endcode - * yields `CouplingParams`. - */ template CouplingParams(FieldType...) -> CouplingParams; /// Sentinel: no time integration rule (used for parameter-only packs). struct NoTimeRule { - static constexpr int num_states = 0; ///< Number of time states contributed by this sentinel rule. + static constexpr int num_states = 0; }; /** * @brief Fields returned by a physics register function, carrying time rule type information. * - * Unlike CouplingParams, PhysicsFields knows which time integration rule governs its fields. - * This lets variadic build functions deduce which pack is "self" vs coupling, and enables - * compile-time interpolation of coupling fields in traction/body force wrappers. - * - * @tparam TimeRule The time integration rule type (e.g. QuasiStaticSecondOrderTimeIntegrationRule). - * @tparam Spaces FE space types of the fields (e.g. H1 repeated num_states times). + * Doubles as a per-physics coupling segment: when supplied via `couplingFields(...)`, the + * builder interpolates `TimeRule::num_states` raw arguments before passing the values to + * the user callback. */ template struct PhysicsFields { - using time_rule_type = TimeRule; ///< Time integration rule governing these fields. - static constexpr std::size_t num_rule_states = TimeRule::num_states; ///< Number of state slots from `TimeRule`. - static constexpr std::size_t num_fields = sizeof...(Spaces); ///< Total number of exported fields. - static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); ///< Number of fields exposed for coupling. - std::shared_ptr field_store; ///< Store owning the registered fields. - std::tuple...> fields; ///< Exported field descriptors in registration order. - - /// @brief Construct a registered-physics field pack. + using time_rule_type = TimeRule; + static constexpr std::size_t num_rule_states = TimeRule::num_states; + static constexpr std::size_t num_fields = sizeof...(Spaces); + static constexpr std::size_t num_coupling_fields = sizeof...(Spaces); + std::shared_ptr field_store; + std::tuple...> fields; + PhysicsFields(std::shared_ptr fs, FieldType... f) : field_store(std::move(fs)), fields(std::move(f)...) { } }; -template -struct PhysicsCouplingSegment { - using time_rule_type = TimeRule; - static constexpr std::size_t num_fields = sizeof...(Spaces); - std::tuple...> fields; +/** + * @brief Bundle of foreign `PhysicsFields` packs supplied to a builder as a single coupling arg. + * + * Order is preserved. Each entry contributes its fields and an interpolation segment governed + * by its own `time_rule_type`. + */ +template +struct CouplingFields { + std::tuple packs; }; -template -struct ParameterCouplingSegment { - static constexpr std::size_t num_fields = sizeof...(Spaces); - std::tuple...> fields; -}; +template +auto couplingFields(const PFs&... pfs) +{ + return CouplingFields{std::make_tuple(pfs...)}; +} +/** + * @brief Aggregate of caller-ordered segments plus a flat field tuple. + * + * `segments` holds the foreign `PhysicsFields<...>` in caller order, optionally followed by a + * single (name-qualified) `CouplingParams<...>`. `fields` is the concatenation of each segment's + * `fields`, used directly as trailing weak-form parameter spaces. + */ template struct CouplingDescriptor; @@ -117,9 +103,6 @@ struct CouplingDescriptor, Segments...> { /** * @brief Register parameter fields as type-level tokens. - * - * Actual FieldStore registration is deferred to the build function. - * Returns a CouplingParams carrying the parameter field types. */ template auto registerParameterFields(FieldType... param_types) @@ -129,36 +112,42 @@ auto registerParameterFields(FieldType... param_types) namespace detail { -/// Type trait: true if T is a CouplingParams<...> or PhysicsFields<...> specialization. -/// Both carry a `fields` tuple and can be used as coupling input to build functions. template -struct is_coupling_params_impl : std::false_type {}; +struct is_physics_fields_impl : std::false_type {}; -template -struct is_coupling_params_impl> : std::true_type {}; +template +struct is_physics_fields_impl> : std::true_type {}; -template -struct is_coupling_params_impl> : std::true_type {}; +template +inline constexpr bool is_physics_fields_v = is_physics_fields_impl>::value; -template -struct is_coupling_params_impl> : std::true_type {}; +template +struct is_parameter_pack_impl : std::false_type {}; + +template +struct is_parameter_pack_impl> : std::true_type {}; template -inline constexpr bool is_coupling_params_v = - is_coupling_params_impl>::value; ///< True for `CouplingParams` and `PhysicsFields`. +inline constexpr bool is_parameter_pack_v = is_parameter_pack_impl>::value; -/// Type trait: true if T is a PhysicsFields<...> specialization. template -struct is_physics_fields_impl : std::false_type {}; +struct is_coupling_fields_impl : std::false_type {}; -template -struct is_physics_fields_impl> : std::true_type {}; +template +struct is_coupling_fields_impl> : std::true_type {}; template -inline constexpr bool is_physics_fields_v = - is_physics_fields_impl>::value; ///< True only for `PhysicsFields`. +inline constexpr bool is_coupling_fields_v = is_coupling_fields_impl>::value; + +template +struct is_coupling_descriptor_impl : std::false_type {}; + +template +struct is_coupling_descriptor_impl> : std::true_type {}; + +template +inline constexpr bool is_coupling_params_v = is_coupling_descriptor_impl>::value; -/// True if T is a PhysicsFields with a real time rule (not NoTimeRule). template inline constexpr bool has_time_rule_v = false; @@ -172,6 +161,23 @@ inline constexpr bool is_field_store_ptr_v = std::is_same_v, std template inline constexpr bool is_mesh_ptr_v = std::is_same_v, std::shared_ptr>; +/// Trailing args must be one of: {}, {CouplingFields}, {CouplingParams}, {CouplingFields, CouplingParams}. +template +inline constexpr bool trailing_coupling_args_valid_v = [] { + if constexpr (sizeof...(Trailing) == 0) { + return true; + } else if constexpr (sizeof...(Trailing) == 1) { + using T0 = std::tuple_element_t<0, std::tuple>; + return is_coupling_fields_v || is_parameter_pack_v; + } else if constexpr (sizeof...(Trailing) == 2) { + using T0 = std::tuple_element_t<0, std::tuple>; + using T1 = std::tuple_element_t<1, std::tuple>; + return is_coupling_fields_v && is_parameter_pack_v; + } else { + return false; + } +}(); + template auto getOrCreateFieldStore(T source, std::string prefix = "", size_t storage_size = 100) { @@ -183,83 +189,102 @@ auto getOrCreateFieldStore(T source, std::string prefix = "", size_t storage_siz } // ------------------------------------------------------------------------- -// Helpers for variadic build functions +// Trailing-arg extraction // ------------------------------------------------------------------------- -template -auto collectPhysicsSegmentFromPack(const Pack& pack) +template +constexpr bool hasCouplingFields() +{ + return ((is_coupling_fields_v) || ...); +} + +template +constexpr bool hasParamPack() +{ + return ((is_parameter_pack_v) || ...); +} + +inline auto extractCouplingPacks() { return std::tuple<>{}; } + +template +auto extractCouplingPacks(const First& first, const Rest&... rest) { - if constexpr (is_physics_fields_v) { - if constexpr (std::is_same_v::time_rule_type, TargetRule>) { - return std::tuple{}; // skip self - } else { - using Rule = typename std::decay_t::time_rule_type; - return std::apply( - [](auto... fields) { - return std::make_tuple( - PhysicsCouplingSegment{std::make_tuple(fields...)}); + if constexpr (is_coupling_fields_v) { + return first.packs; + } else { + return extractCouplingPacks(rest...); + } +} + +template +auto qualifyParams(const std::shared_ptr& fs, const CouplingParams& pack) +{ + return std::apply( + [&](auto... pts) { + auto qualify = [&](auto pt) { + pt.name = fs->prefix("param_" + pt.name); + return pt; + }; + return CouplingParams{qualify(pts)...}; + }, + pack.fields); +} + +/// Register parameter fields from any trailing CouplingParams into the FieldStore. +template +void registerParamsIfNeeded(std::shared_ptr fs, const Trailing&... trailing) +{ + auto register_one = [&](const auto& pack) { + using P = std::decay_t; + if constexpr (is_parameter_pack_v