From 18622066d9a049fb43ddec9d03e2b40fefee2ab7 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 30 Dec 2025 16:11:00 -0800 Subject: [PATCH 1/7] Adding a block nonlinear solver. --- .../differentiable_solver.cpp | 125 +++++++++++++++++- .../differentiable_solver.hpp | 30 +++++ 2 files changed, 150 insertions(+), 5 deletions(-) diff --git a/src/smith/differentiable_numerics/differentiable_solver.cpp b/src/smith/differentiable_numerics/differentiable_solver.cpp index 9cd3798776..3ce4860799 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.cpp +++ b/src/smith/differentiable_numerics/differentiable_solver.cpp @@ -141,8 +141,6 @@ std::shared_ptr NonlinearDifferentiableSolver::solve( nonlinear_solver_->setOperator(*residual_op_); nonlinear_solver_->solve(*u); - // std::cout << "solution norm = " << u->Norml2() << std::endl; - return u; } @@ -269,6 +267,116 @@ std::vector LinearDifferentiableBlockSolver return u_duals; } + +NonlinearDifferentiableBlockSolver::NonlinearDifferentiableBlockSolver(std::unique_ptr s) + : nonlinear_solver_(std::move(s)) +{ +} + +void NonlinearDifferentiableBlockSolver::completeSetup(const std::vector&) +{ + //initializeSolver(&nonlinear_solver_->preconditioner(), u); +} + +std::vector NonlinearDifferentiableBlockSolver::solve( + const std::vector& u_guesses, + std::function(const std::vector&)> residual_funcs, + std::function>(const std::vector&)> jacobian_funcs) const +{ + SMITH_MARK_FUNCTION; + + int num_rows = static_cast(u_guesses.size()); + SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative"); + + mfem::Array block_offsets; + block_offsets.SetSize(num_rows + 1); + block_offsets[0] = 0; + for (int row_i = 0; row_i < num_rows; ++row_i) { + block_offsets[row_i + 1] = u_guesses[static_cast(row_i)]->space().TrueVSize(); + } + block_offsets.PartialSum(); + + auto block_du = std::make_unique(block_offsets); + for (int row_i = 0; row_i < num_rows; ++row_i) { + block_du->GetBlock(row_i) = *u_guesses[static_cast(row_i)]; + } + + auto residuals = residual_funcs(u_guesses); + auto block_r = std::make_unique(block_offsets); + for (int row_i = 0; row_i < num_rows; ++row_i) { + block_r->GetBlock(row_i) = residuals[static_cast(row_i)]; + } + + auto jacs = jacobian_funcs(u_guesses); + 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, jacs[static_cast(i)][static_cast(j)].get()); + } + } + + auto& linear_solver = nonlinear_solver_->linearSolver(); + linear_solver.SetOperator(*block_jac); + linear_solver.Mult(*block_r, *block_du); + + for (int row_i = 0; row_i < num_rows; ++row_i) { + *u_guesses[static_cast(row_i)] -= block_du->GetBlock(row_i); + } + *block_du = 0.0; + + return u_guesses; +} + +std::vector NonlinearDifferentiableBlockSolver::solveAdjoint( + const std::vector& u_bars, std::vector>& jacobian_transposed) const +{ + SMITH_MARK_FUNCTION; + + int num_rows = static_cast(u_bars.size()); + SLIC_ERROR_IF(num_rows < 0, "Number of residual rows must be non-negative"); + + std::vector u_duals(static_cast(num_rows)); + for (int row_i = 0; row_i < num_rows; ++row_i) { + u_duals[static_cast(row_i)] = std::make_shared( + u_bars[static_cast(row_i)]->space(), "u_dual_" + std::to_string(row_i)); + } + + mfem::Array block_offsets; + block_offsets.SetSize(num_rows + 1); + block_offsets[0] = 0; + for (int row_i = 0; row_i < num_rows; ++row_i) { + block_offsets[row_i + 1] = u_bars[static_cast(row_i)]->space().TrueVSize(); + } + block_offsets.PartialSum(); + + auto block_ds = std::make_unique(block_offsets); + *block_ds = 0.0; + + auto block_r = std::make_unique(block_offsets); + for (int row_i = 0; row_i < num_rows; ++row_i) { + block_r->GetBlock(row_i) = *u_bars[static_cast(row_i)]; + } + + 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& linear_solver = nonlinear_solver_->linearSolver(); + linear_solver.SetOperator(*block_jac); + linear_solver.Mult(*block_r, *block_ds); + + for (int row_i = 0; row_i < num_rows; ++row_i) { + *u_duals[static_cast(row_i)] = block_ds->GetBlock(row_i); + } + + return u_duals; +} + + + std::shared_ptr buildDifferentiableLinearSolver(LinearSolverOptions linear_opts, const smith::Mesh& mesh) { @@ -277,10 +385,17 @@ std::shared_ptr buildDifferentiableLinearSolver(Line } std::shared_ptr buildDifferentiableNonlinearSolver( - smith::NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh) + NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh) +{ + auto solid_solver = std::make_unique(nonlinear_opts, linear_opts, mesh.getComm()); + return std::make_shared(std::move(solid_solver)); +} + +std::shared_ptr buildDifferentiableNonlinearBlockSolver( + NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh) { - auto solid_solver = std::make_unique(nonlinear_opts, linear_opts, mesh.getComm()); - return std::make_shared(std::move(solid_solver)); + auto solid_solver = std::make_unique(nonlinear_opts, linear_opts, mesh.getComm()); + return std::make_shared(std::move(solid_solver)); } } // namespace smith diff --git a/src/smith/differentiable_numerics/differentiable_solver.hpp b/src/smith/differentiable_numerics/differentiable_solver.hpp index a58540918a..bee488b5d9 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.hpp +++ b/src/smith/differentiable_numerics/differentiable_solver.hpp @@ -175,6 +175,30 @@ class LinearDifferentiableBlockSolver : public DifferentiableBlockSolver { mutable std::unique_ptr mfem_preconditioner; ///< stored mfem block preconditioner }; +/// @brief Implementation of the DifferentiableBlockSolver interface for the special case of nonlinear solves with linear +/// adjoint solves +class NonlinearDifferentiableBlockSolver : public DifferentiableBlockSolver { + public: + /// @brief Construct from a linear solver and linear block precondition which may be used by the linear solver + NonlinearDifferentiableBlockSolver(std::unique_ptr s); + + /// @overload + void completeSetup(const std::vector& us) override; + + /// @overload + std::vector solve( + const std::vector& u_guesses, + std::function(const std::vector&)> residuals, + std::function>(const std::vector&)> jacobians) const override; + + /// @overload + std::vector solveAdjoint(const std::vector& u_bars, + std::vector>& jacobian_transposed) const override; + + mutable std::unique_ptr + nonlinear_solver_; ///< the nonlinear equation solver used for the forward pass +}; + /// @brief Create a differentiable linear solver /// @param linear_opts linear options struct /// @param mesh mesh @@ -189,4 +213,10 @@ std::shared_ptr buildDifferentiableNonlinearSolve LinearSolverOptions linear_opts, const smith::Mesh& mesh); +/// @brief Create a differentiable nonlinear block solver +/// @param nonlinear_opts nonlinear options struct +/// @param linear_opts linear options struct +/// @param mesh mesh +std::shared_ptr buildDifferentiableNonlinearBlockSolver(NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh); + } // namespace smith From 8299a9808f48f1c8a06692ae0071a33923ea8e64 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 8 Jan 2026 13:37:01 -0800 Subject: [PATCH 2/7] Fix style, adding differentiable nonlinear block solver. --- .../differentiable_solver.cpp | 69 ++++++++++++------- .../differentiable_solver.hpp | 11 ++- .../nonlinear_solve.cpp | 54 +++++++++------ .../nonlinear_solve.hpp | 5 +- 4 files changed, 90 insertions(+), 49 deletions(-) diff --git a/src/smith/differentiable_numerics/differentiable_solver.cpp b/src/smith/differentiable_numerics/differentiable_solver.cpp index 3ce4860799..61c984e85b 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.cpp +++ b/src/smith/differentiable_numerics/differentiable_solver.cpp @@ -267,7 +267,6 @@ std::vector LinearDifferentiableBlockSolver return u_duals; } - NonlinearDifferentiableBlockSolver::NonlinearDifferentiableBlockSolver(std::unique_ptr s) : nonlinear_solver_(std::move(s)) { @@ -275,7 +274,7 @@ NonlinearDifferentiableBlockSolver::NonlinearDifferentiableBlockSolver(std::uniq void NonlinearDifferentiableBlockSolver::completeSetup(const std::vector&) { - //initializeSolver(&nonlinear_solver_->preconditioner(), u); + // initializeSolver(&nonlinear_solver_->preconditioner(), u); } std::vector NonlinearDifferentiableBlockSolver::solve( @@ -296,33 +295,56 @@ std::vector NonlinearDifferentiableBlockSol } block_offsets.PartialSum(); - auto block_du = std::make_unique(block_offsets); + auto block_u = std::make_unique(block_offsets); for (int row_i = 0; row_i < num_rows; ++row_i) { - block_du->GetBlock(row_i) = *u_guesses[static_cast(row_i)]; + block_u->GetBlock(row_i) = *u_guesses[static_cast(row_i)]; } - auto residuals = residual_funcs(u_guesses); auto block_r = std::make_unique(block_offsets); - for (int row_i = 0; row_i < num_rows; ++row_i) { - block_r->GetBlock(row_i) = residuals[static_cast(row_i)]; - } - auto jacs = jacobian_funcs(u_guesses); - 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, jacs[static_cast(i)][static_cast(j)].get()); - } - } + auto residual_op_ = std::make_unique( + block_u->Size(), + [residual_funcs, num_rows, &u_guesses, &block_r](const mfem::Vector& u_, mfem::Vector& r_) { + const mfem::BlockVector* u = dynamic_cast(&u_); + SLIC_ERROR_IF(!u, "Invalid u cast in block differentiable solver to a blocl vector"); + for (int row_i = 0; row_i < num_rows; ++row_i) { + *u_guesses[static_cast(row_i)] = u->GetBlock(row_i); + } + auto residuals = residual_funcs(u_guesses); + // auto block_r = std::make_unique(block_offsets); + // auto block_r = dynamic_cast(&r_); + SLIC_ERROR_IF(!block_r, "Invalid r cast in block differentiable solver to a block vector"); + for (int row_i = 0; row_i < num_rows; ++row_i) { + auto r = residuals[static_cast(row_i)]; + block_r->GetBlock(row_i) = r; + } + r_ = *block_r; + }, + [this, &block_offsets, &u_guesses, jacobian_funcs, num_rows](const mfem::Vector& u_) -> mfem::Operator& { + const mfem::BlockVector* u = dynamic_cast(&u_); + SLIC_ERROR_IF(!u, "Invalid u cast in block differentiable solver to a block vector"); + for (int row_i = 0; row_i < num_rows; ++row_i) { + *u_guesses[static_cast(row_i)] = u->GetBlock(row_i); + } + block_jac_ = std::make_unique(block_offsets); + matrix_of_jacs_ = jacobian_funcs(u_guesses); + for (int i = 0; i < num_rows; ++i) { + for (int j = 0; j < num_rows; ++j) { + auto& J = matrix_of_jacs_[static_cast(i)][static_cast(j)]; + if (J) { + block_jac_->SetBlock(i, j, J.get()); + } + } + } + return *block_jac_; + }); - auto& linear_solver = nonlinear_solver_->linearSolver(); - linear_solver.SetOperator(*block_jac); - linear_solver.Mult(*block_r, *block_du); + nonlinear_solver_->setOperator(*residual_op_); + nonlinear_solver_->solve(*block_u); for (int row_i = 0; row_i < num_rows; ++row_i) { - *u_guesses[static_cast(row_i)] -= block_du->GetBlock(row_i); + *u_guesses[static_cast(row_i)] = block_u->GetBlock(row_i); } - *block_du = 0.0; return u_guesses; } @@ -375,8 +397,6 @@ std::vector NonlinearDifferentiableBlockSol return u_duals; } - - std::shared_ptr buildDifferentiableLinearSolver(LinearSolverOptions linear_opts, const smith::Mesh& mesh) { @@ -384,8 +404,9 @@ std::shared_ptr buildDifferentiableLinearSolver(Line return std::make_shared(std::move(linear_solver), std::move(precond)); } -std::shared_ptr buildDifferentiableNonlinearSolver( - NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh) +std::shared_ptr buildDifferentiableNonlinearSolver(NonlinearSolverOptions nonlinear_opts, + LinearSolverOptions linear_opts, + const smith::Mesh& mesh) { auto solid_solver = std::make_unique(nonlinear_opts, linear_opts, mesh.getComm()); return std::make_shared(std::move(solid_solver)); diff --git a/src/smith/differentiable_numerics/differentiable_solver.hpp b/src/smith/differentiable_numerics/differentiable_solver.hpp index bee488b5d9..745e0509c2 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.hpp +++ b/src/smith/differentiable_numerics/differentiable_solver.hpp @@ -19,6 +19,7 @@ namespace mfem { class Solver; class Vector; class HypreParMatrix; +class BlockOperator; } // namespace mfem namespace smith { @@ -175,8 +176,8 @@ class LinearDifferentiableBlockSolver : public DifferentiableBlockSolver { mutable std::unique_ptr mfem_preconditioner; ///< stored mfem block preconditioner }; -/// @brief Implementation of the DifferentiableBlockSolver interface for the special case of nonlinear solves with linear -/// adjoint solves +/// @brief Implementation of the DifferentiableBlockSolver interface for the special case of nonlinear solves with +/// linear adjoint solves class NonlinearDifferentiableBlockSolver : public DifferentiableBlockSolver { public: /// @brief Construct from a linear solver and linear block precondition which may be used by the linear solver @@ -195,6 +196,9 @@ class NonlinearDifferentiableBlockSolver : public DifferentiableBlockSolver { std::vector solveAdjoint(const std::vector& u_bars, std::vector>& jacobian_transposed) const override; + mutable std::unique_ptr block_jac_; + mutable std::vector> matrix_of_jacs_; + mutable std::unique_ptr nonlinear_solver_; ///< the nonlinear equation solver used for the forward pass }; @@ -217,6 +221,7 @@ std::shared_ptr buildDifferentiableNonlinearSolve /// @param nonlinear_opts nonlinear options struct /// @param linear_opts linear options struct /// @param mesh mesh -std::shared_ptr buildDifferentiableNonlinearBlockSolver(NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh); +std::shared_ptr buildDifferentiableNonlinearBlockSolver( + NonlinearSolverOptions nonlinear_opts, LinearSolverOptions linear_opts, const smith::Mesh& mesh); } // namespace smith diff --git a/src/smith/differentiable_numerics/nonlinear_solve.cpp b/src/smith/differentiable_numerics/nonlinear_solve.cpp index 7d2ed606a9..c73d0b8d84 100644 --- a/src/smith/differentiable_numerics/nonlinear_solve.cpp +++ b/src/smith/differentiable_numerics/nonlinear_solve.cpp @@ -274,7 +274,7 @@ std::vector block_solve(const std::vector& residual_evals const std::vector>& states, const std::vector>& params, const TimeInfo& time_info, const DifferentiableBlockSolver* solver, - const std::vector bc_managers) + const std::vector bc_managers) { SMITH_MARK_FUNCTION; size_t num_rows_ = residual_evals.size(); @@ -326,10 +326,10 @@ std::vector block_solve(const std::vector& residual_evals SMITH_MARK_BEGIN("solve forward"); const size_t num_rows = num_state_inputs.size(); std::vector> input_fields(num_rows); - SLIC_ERROR_IF(num_rows != num_param_inputs.size(), "row count for params and columns are inconsistent"); + SLIC_ERROR_IF(num_rows != num_param_inputs.size(), "row count for params and states are inconsistent"); - // The order of inputs in upstreams seems to be: - // states of residual 0 -> states of residual 1 -> params of residual 0 -> params of residual 1 + // The order of inputs in upstreams is: + // states of residual 0, states of residual 1, ... , params of residual 0, params of residual 1, ... size_t field_count = 0; for (size_t row_i = 0; row_i < num_rows; ++row_i) { for (size_t state_i = 0; state_i < num_state_inputs[row_i]; ++state_i) { @@ -344,12 +344,18 @@ std::vector block_solve(const std::vector& residual_evals std::vector diagonal_fields(num_rows); for (size_t row_i = 0; row_i < num_rows; ++row_i) { - diagonal_fields[row_i] = std::make_shared(*input_fields[row_i][block_indices[row_i][row_i]]); + size_t prime_unknown_row_i = block_indices[row_i][row_i]; + SLIC_ERROR_IF(prime_unknown_row_i == invalid_block_index, + "The primary unknown field (field index for block_index[n][n], must not be invalid)"); + diagonal_fields[row_i] = std::make_shared(*input_fields[row_i][prime_unknown_row_i]); } 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]; + size_t prime_unknown_ij = block_indices[row_i][col_j]; + if (prime_unknown_ij != invalid_block_index) { + input_fields[row_i][block_indices[row_i][col_j]] = diagonal_fields[col_j]; + } } } @@ -371,7 +377,6 @@ std::vector block_solve(const std::vector& residual_evals getConstFieldPointers(input_fields[row_i])); residuals[row_i].SetSubVector(bc_managers[row_i]->allEssentialTrueDofs(), 0.0); } - return residuals; }; @@ -391,32 +396,39 @@ std::vector block_solve(const std::vector& residual_evals std::vector tangent_weights(row_field_inputs.size(), 0.0); for (size_t col_j = 0; col_j < num_rows; ++col_j) { size_t field_index_to_diff = block_indices[row_i][col_j]; - tangent_weights[field_index_to_diff] = 1.0; - auto jac_ij = residual_evals[row_i]->jacobian(time_info, shape_disp_ptr.get(), - getConstFieldPointers(row_field_inputs), tangent_weights); - jacobians[row_i].emplace_back(std::move(jac_ij)); - tangent_weights[field_index_to_diff] = 0.0; + if (field_index_to_diff != invalid_block_index) { + tangent_weights[field_index_to_diff] = 1.0; + auto jac_ij = residual_evals[row_i]->jacobian(time_info, shape_disp_ptr.get(), + getConstFieldPointers(row_field_inputs), tangent_weights); + jacobians[row_i].emplace_back(std::move(jac_ij)); + tangent_weights[field_index_to_diff] = 0.0; + } else { + jacobians[row_i].emplace_back(nullptr); + } } } // Apply BCs to the block system for (size_t row_i = 0; row_i < num_rows; ++row_i) { - mfem::HypreParMatrix* Jii = - jacobians[row_i][row_i]->EliminateRowsCols(bc_managers[row_i]->allEssentialTrueDofs()); - delete Jii; + if (jacobians[row_i][row_i]) { + jacobians[row_i][row_i]->EliminateBC(bc_managers[row_i]->allEssentialTrueDofs(), + mfem::Operator::DiagonalPolicy::DIAG_ONE); + } for (size_t col_j = 0; col_j < num_rows; ++col_j) { if (col_j != row_i) { - jacobians[row_i][col_j]->EliminateRows(bc_managers[row_i]->allEssentialTrueDofs()); - mfem::HypreParMatrix* Jji = - jacobians[col_j][row_i]->EliminateCols(bc_managers[row_i]->allEssentialTrueDofs()); - delete Jji; + if (jacobians[row_i][col_j]) { + jacobians[row_i][col_j]->EliminateRows(bc_managers[row_i]->allEssentialTrueDofs()); + } + if (jacobians[col_j][row_i]) { + mfem::HypreParMatrix* Jji = + jacobians[col_j][row_i]->EliminateCols(bc_managers[row_i]->allEssentialTrueDofs()); + delete Jji; + } } } } - return jacobians; }; - diagonal_fields = solver->solve(diagonal_fields, eval_residuals, eval_jacobians); downstream.set, std::vector>(diagonal_fields); diff --git a/src/smith/differentiable_numerics/nonlinear_solve.hpp b/src/smith/differentiable_numerics/nonlinear_solve.hpp index 07027b274e..df94c229f5 100644 --- a/src/smith/differentiable_numerics/nonlinear_solve.hpp +++ b/src/smith/differentiable_numerics/nonlinear_solve.hpp @@ -24,6 +24,9 @@ class DifferentiableBlockSolver; class BoundaryConditionManager; class DirichletBoundaryConditions; +/// @brief magic number for represending a field which is not an argument of the weak form. +static constexpr size_t invalid_block_index = std::numeric_limits::max() - 1; + /// @brief Solve a nonlinear system of equations as defined by the weak form, assuming that the field indexed by /// unknown_index is the unknown field /// @param residual_eval The weak form which defines the equations to be solved @@ -61,6 +64,6 @@ std::vector block_solve(const std::vector& residual_evals const std::vector>& states, const std::vector>& params, const TimeInfo& time_info, const DifferentiableBlockSolver* solver, - const std::vector bc_managers); + const std::vector bc_managers); } // namespace smith From a94e2fb2ddcf15e82e0e08e96d4febd8a698b08d Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 8 Jan 2026 13:42:47 -0800 Subject: [PATCH 3/7] Fix style. --- .../differentiable_numerics/differentiable_solver.hpp | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/smith/differentiable_numerics/differentiable_solver.hpp b/src/smith/differentiable_numerics/differentiable_solver.hpp index 745e0509c2..1eb2b877be 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.hpp +++ b/src/smith/differentiable_numerics/differentiable_solver.hpp @@ -196,8 +196,11 @@ class NonlinearDifferentiableBlockSolver : public DifferentiableBlockSolver { std::vector solveAdjoint(const std::vector& u_bars, std::vector>& jacobian_transposed) const override; - mutable std::unique_ptr block_jac_; - mutable std::vector> matrix_of_jacs_; + mutable std::unique_ptr + block_jac_; ///< Need to hold an instance of a block operator to work with the mfem solver interface + mutable std::vector> + matrix_of_jacs_; ///< Holding vectors of block matrices to that do not going out of scope before the mfem solver + ///< is done with using them in the block_jac_ mutable std::unique_ptr nonlinear_solver_; ///< the nonlinear equation solver used for the forward pass From 6aa3047fac45842457b2e2c31c5ba54b18bdad7a Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Thu, 8 Jan 2026 14:19:38 -0800 Subject: [PATCH 4/7] Remove boundary condition option that causes some template priority order issues. --- .../dirichlet_boundary_conditions.hpp | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp index c148350df2..d79fc72b2f 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp @@ -72,39 +72,21 @@ class DirichletBoundaryConditions { /// @brief Specify time and space varying Dirichlet boundary conditions over a domain. /// @param domain All dofs in this domain have boundary conditions applied to it. - /// @param component component direction to apply boundary condition to if the underlying field is a vector-field. /// @param applied_displacement applied_displacement is a functor which takes time, and a /// smith::tensor corresponding to the spatial coordinate. The functor must return a double. For /// example: [](double t, smith::tensor X) { return 1.0; } template - void setVectorBCs(const Domain& domain, int component, AppliedDisplacementFunction applied_displacement) + void setScalarBCs(const Domain& domain, AppliedDisplacementFunction applied_displacement) { - const int field_dim = space_.GetVDim(); - SLIC_ERROR_IF(component >= field_dim, - axom::fmt::format("Trying to set boundary conditions on a field with dim {}, using component {}", - field_dim, component)); auto mfem_coefficient_function = [applied_displacement](const mfem::Vector& X_mfem, double t) { auto X = make_tensor([&X_mfem](int k) { return X_mfem[k]; }); return applied_displacement(t, X); }; auto dof_list = domain.dof_list(&space_); - // scalar ldofs -> vector ldofs - space_.DofsToVDofs(component, dof_list); auto component_disp_bdr_coef_ = std::make_shared(mfem_coefficient_function); - bcs_.addEssential(dof_list, component_disp_bdr_coef_, space_, component); - } - - /// @brief Specify time and space varying Dirichlet boundary conditions over a domain. - /// @param domain All dofs in this domain have boundary conditions applied to it. - /// @param applied_displacement applied_displacement is a functor which takes time, and a - /// smith::tensor corresponding to the spatial coordinate. The functor must return a double. For - /// example: [](double t, smith::tensor X) { return 1.0; } - template - void setScalarBCs(const Domain& domain, AppliedDisplacementFunction applied_displacement) - { - setScalarBCs(domain, 0, applied_displacement); + bcs_.addEssential(dof_list, component_disp_bdr_coef_, space_); } /// @brief Constrain the dofs of a scalar field over a domain From 218b75ec8dadbb55c4b94a31b4b935657a3f1259 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Mon, 12 Jan 2026 14:10:10 -0800 Subject: [PATCH 5/7] Still working on block nonlinear solver. --- .../differentiable_numerics/differentiable_solver.cpp | 3 +-- .../dirichlet_boundary_conditions.hpp | 3 ++- src/smith/differentiable_numerics/nonlinear_solve.cpp | 8 ++++++-- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/smith/differentiable_numerics/differentiable_solver.cpp b/src/smith/differentiable_numerics/differentiable_solver.cpp index ebcd7941a5..7a3b256002 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.cpp +++ b/src/smith/differentiable_numerics/differentiable_solver.cpp @@ -303,7 +303,7 @@ std::vector NonlinearDifferentiableBlockSol auto residual_op_ = std::make_unique( block_u->Size(), - [residual_funcs, num_rows, &u_guesses, &block_r](const mfem::Vector& u_, mfem::Vector& r_) { + [&residual_funcs, num_rows, &u_guesses, &block_r](const mfem::Vector& u_, mfem::Vector& r_) { const mfem::BlockVector* u = dynamic_cast(&u_); SLIC_ERROR_IF(!u, "Invalid u cast in block differentiable solver to a blocl vector"); for (int row_i = 0; row_i < num_rows; ++row_i) { @@ -337,7 +337,6 @@ std::vector NonlinearDifferentiableBlockSol } return *block_jac_; }); - nonlinear_solver_->setOperator(*residual_op_); nonlinear_solver_->solve(*block_u); diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp index d79fc72b2f..d7ddec9a55 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp @@ -84,9 +84,10 @@ class DirichletBoundaryConditions { }; auto dof_list = domain.dof_list(&space_); + space_.DofsToVDofs(static_cast(0), dof_list); auto component_disp_bdr_coef_ = std::make_shared(mfem_coefficient_function); - bcs_.addEssential(dof_list, component_disp_bdr_coef_, space_); + bcs_.addEssential(dof_list, component_disp_bdr_coef_, space_, 0); } /// @brief Constrain the dofs of a scalar field over a domain diff --git a/src/smith/differentiable_numerics/nonlinear_solve.cpp b/src/smith/differentiable_numerics/nonlinear_solve.cpp index 4bf4643d88..d28af788e5 100644 --- a/src/smith/differentiable_numerics/nonlinear_solve.cpp +++ b/src/smith/differentiable_numerics/nonlinear_solve.cpp @@ -305,7 +305,6 @@ std::vector block_solve(const std::vector& residual_evals } } allFields.push_back(shape_disp); - struct ZeroDualVectors { std::vector operator()(const std::vector& fs) { @@ -319,7 +318,6 @@ std::vector block_solve(const std::vector& residual_evals FieldVecState sol = shape_disp.create_state, std::vector>(allFields, ZeroDualVectors()); - sol.set_eval([=](const gretl::UpstreamStates& upstreams, gretl::DownstreamState& downstream) { SMITH_MARK_BEGIN("solve forward"); const size_t num_rows = num_state_inputs.size(); @@ -371,6 +369,11 @@ std::vector block_solve(const std::vector& residual_evals } for (size_t row_i = 0; row_i < num_rows; ++row_i) { + //std::cout << "r " << row_i << " inputs = "; + //for (auto& f : input_fields[row_i]) { + // std::cout << f->name() << " " << f->Size() << " "; + //} + //std::cout << std::endl; residuals[row_i] = residual_evals[row_i]->residual(time_info, shape_disp_ptr.get(), getConstFieldPointers(input_fields[row_i])); residuals[row_i].SetSubVector(bc_managers[row_i]->allEssentialTrueDofs(), 0.0); @@ -427,6 +430,7 @@ std::vector block_solve(const std::vector& residual_evals } return jacobians; }; + diagonal_fields = solver->solve(diagonal_fields, eval_residuals, eval_jacobians); downstream.set, std::vector>(diagonal_fields); From b3e83493513aba7ceba83b6c8ea1a953fc50bda9 Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Fri, 16 Jan 2026 09:23:41 -0800 Subject: [PATCH 6/7] SOme minor updates. --- .../nonlinear_solve.cpp | 17 ++++++----- .../solid_mechanics_state_advancer.cpp | 4 +-- .../time_discretized_weak_form.hpp | 29 ++++++++++--------- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/smith/differentiable_numerics/nonlinear_solve.cpp b/src/smith/differentiable_numerics/nonlinear_solve.cpp index d28af788e5..1f1f34a558 100644 --- a/src/smith/differentiable_numerics/nonlinear_solve.cpp +++ b/src/smith/differentiable_numerics/nonlinear_solve.cpp @@ -346,6 +346,11 @@ std::vector block_solve(const std::vector& residual_evals diagonal_fields[row_i] = std::make_shared(*input_fields[row_i][prime_unknown_row_i]); } + for (size_t row_i = 0; row_i < num_rows; ++row_i) { + FEFieldPtr primal_field_row_i = diagonal_fields[row_i]; + applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr); + } + for (size_t row_i = 0; row_i < num_rows; ++row_i) { for (size_t col_j = 0; col_j < num_rows; ++col_j) { size_t prime_unknown_ij = block_indices[row_i][col_j]; @@ -367,13 +372,7 @@ std::vector block_solve(const std::vector& residual_evals *primal_field_row_i = *unknowns[row_i]; applyBoundaryConditions(time_info.time(), bc_managers[row_i], primal_field_row_i, nullptr); } - for (size_t row_i = 0; row_i < num_rows; ++row_i) { - //std::cout << "r " << row_i << " inputs = "; - //for (auto& f : input_fields[row_i]) { - // std::cout << f->name() << " " << f->Size() << " "; - //} - //std::cout << std::endl; residuals[row_i] = residual_evals[row_i]->residual(time_info, shape_disp_ptr.get(), getConstFieldPointers(input_fields[row_i])); residuals[row_i].SetSubVector(bc_managers[row_i]->allEssentialTrueDofs(), 0.0); @@ -574,7 +573,11 @@ std::vector block_solve(const std::vector& residual_evals for (size_t i = 0; i < num_rows_; ++i) { FieldState s = gretl::create_state( zero_dual_from_state(), - [i](const std::vector& sols) { return std::make_shared(*sols[i]); }, + [i](const std::vector& sols) { + auto state_copy = std::make_shared(sols[i]->space(), sols[i]->name()); + *state_copy = *sols[i]; + return state_copy; + }, [i](const std::vector&, const FEFieldPtr&, std::vector& sols_, const FEDualPtr& output_) { *sols_[i] += *output_; }, sol); diff --git a/src/smith/differentiable_numerics/solid_mechanics_state_advancer.cpp b/src/smith/differentiable_numerics/solid_mechanics_state_advancer.cpp index 7a9b888e4d..d60e9dfd1e 100644 --- a/src/smith/differentiable_numerics/solid_mechanics_state_advancer.cpp +++ b/src/smith/differentiable_numerics/solid_mechanics_state_advancer.cpp @@ -31,7 +31,7 @@ std::vector SolidMechanicsStateAdvancer::advanceState(const TimeInfo { std::vector states_old = states_old_; if (time_info.cycle() == 0) { - states_old[ACCELERATION] = solve(*solid_dynamic_weak_forms_->quasi_static_weak_form, shape_disp, states_old_, + states_old[ACCELERATION] = solve(*solid_dynamic_weak_forms_->final_reaction_weak_form, shape_disp, states_old_, params, time_info, *solver_, *vector_bcs_, ACCELERATION); } @@ -61,7 +61,7 @@ std::vector SolidMechanicsStateAdvancer::computeReactions(const T { std::vector solid_inputs{states[DISPLACEMENT], states[VELOCITY], states[ACCELERATION]}; solid_inputs.insert(solid_inputs.end(), params.begin(), params.end()); - return {evaluateWeakForm(solid_dynamic_weak_forms_->quasi_static_weak_form, time_info, shape_disp, solid_inputs, + return {evaluateWeakForm(solid_dynamic_weak_forms_->final_reaction_weak_form, time_info, shape_disp, solid_inputs, states[DISPLACEMENT])}; } diff --git a/src/smith/differentiable_numerics/time_discretized_weak_form.hpp b/src/smith/differentiable_numerics/time_discretized_weak_form.hpp index bde1c31fec..40e4774fd7 100644 --- a/src/smith/differentiable_numerics/time_discretized_weak_form.hpp +++ b/src/smith/differentiable_numerics/time_discretized_weak_form.hpp @@ -67,10 +67,12 @@ class TimeDiscretizedWeakForm time_discretized_weak_form; ///< this publically available abstract weak form is a + std::shared_ptr time_discretized_weak_form; ///< this publicaly available abstract weak form is a ///< functions of the current u, u_old, v_old, and a_old, - std::shared_ptr quasi_static_weak_form; ///< this publically available abstract weak form is structly a - ///< function of the current u, v, and a (no time discretization) + std::shared_ptr + final_reaction_weak_form; ///< this publicaly available abstract weak form is structly a + ///< function of the current u, v, and a (no time discretization) + ///< its main purpose is to compute reaction forces after the solve is completed }; template > @@ -93,9 +95,8 @@ class SecondOrderTimeDiscretizedWeakForm>; ///< using - using QuasiStaticWeakFormT = - TimeDiscretizedWeakForm>; ///< using + TimeDiscretizedWeakForm>; ///< using + using FinalReactionFormT = TimeDiscretizedWeakForm>; ///< using /// @brief Constructor SecondOrderTimeDiscretizedWeakForm(std::string physics_name, std::shared_ptr mesh, @@ -110,9 +111,9 @@ class SecondOrderTimeDiscretizedWeakForm(physics_name, mesh, output_mfem_space, input_mfem_spaces_trial_removed); - quasi_static_weak_form = quasi_static_weak_form_; + final_reaction_weak_form_ = + std::make_shared(physics_name, mesh, output_mfem_space, input_mfem_spaces_trial_removed); + final_reaction_weak_form = final_reaction_weak_form_; } /// @overload @@ -129,8 +130,8 @@ class SecondOrderTimeDiscretizedWeakFormaddBodyIntegral(DependsOn<0, 1, 2, NUM_STATE_VARS - 1 + active_parameters...>{}, body_name, - integrand); + final_reaction_weak_form_->addBodyIntegral(DependsOn<0, 1, 2, NUM_STATE_VARS - 1 + active_parameters...>{}, + body_name, integrand); } /// @overload @@ -144,9 +145,9 @@ class SecondOrderTimeDiscretizedWeakForm time_discretized_weak_form_; ///< fully templated time discretized weak form (with time integration rule injected ///< into the q-function) - std::shared_ptr - quasi_static_weak_form_; ///< fully template underlying weak form (no time integration included, a function of - ///< current u, v, and a) + std::shared_ptr + final_reaction_weak_form_; ///< fully template underlying weak form (no time integration included, a function of + ///< current u, v, and a) SecondOrderTimeIntegrationRule time_rule_; ///< encodes the time integration rule }; From c2ced3f6a2e7850a35c087b97e8df76bbe3746eb Mon Sep 17 00:00:00 2001 From: Michael Tupek Date: Tue, 20 Jan 2026 14:08:37 -0800 Subject: [PATCH 7/7] Trying to simplify adding new physics even more. --- .../differentiable_numerics/field_state.hpp | 10 +- .../tests/CMakeLists.txt | 1 + .../tests/test_thermo_mechanics.cpp | 233 ++++++++++++++++++ .../time_discretized_weak_form.hpp | 3 +- 4 files changed, 244 insertions(+), 3 deletions(-) create mode 100644 src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp diff --git a/src/smith/differentiable_numerics/field_state.hpp b/src/smith/differentiable_numerics/field_state.hpp index e9528faf89..a2937cad27 100644 --- a/src/smith/differentiable_numerics/field_state.hpp +++ b/src/smith/differentiable_numerics/field_state.hpp @@ -212,22 +212,28 @@ inline std::vector spaces(const std::vector< }; /// @brief Get a vector of FieldPtr or DualFieldPtr from a vector of FieldState -inline std::vector getFieldPointers(std::vector& states) +inline std::vector getFieldPointers(std::vector& states, std::vector params = {}) { std::vector pointers; for (auto& t : states) { pointers.push_back(t.get().get()); } + for (auto& p : params) { + pointers.push_back(p.get().get()); + } return pointers; } /// @brief Get a vector of ConstFieldPtr or ConstDualFieldPtr from a vector of FieldState -inline std::vector getConstFieldPointers(const std::vector& states) +inline std::vector getConstFieldPointers(const std::vector& states, const std::vector& params = {}) { std::vector pointers; for (auto& t : states) { pointers.push_back(t.get().get()); } + for (auto& p : params) { + pointers.push_back(p.get().get()); + } return pointers; } diff --git a/src/smith/differentiable_numerics/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index dc5925a793..06681ec856 100644 --- a/src/smith/differentiable_numerics/tests/CMakeLists.txt +++ b/src/smith/differentiable_numerics/tests/CMakeLists.txt @@ -10,6 +10,7 @@ set(differentiable_numerics_test_source test_field_state.cpp test_explicit_dynamics.cpp test_solid_mechanics_state_advancer.cpp + test_thermo_mechanics.cpp ) smith_add_tests( SOURCES ${differentiable_numerics_test_source} diff --git a/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp new file mode 100644 index 0000000000..a344054348 --- /dev/null +++ b/src/smith/differentiable_numerics/tests/test_thermo_mechanics.cpp @@ -0,0 +1,233 @@ +// 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/smith_config.hpp" +#include "smith/infrastructure/application_manager.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_weak_form.hpp" +#include "smith/differentiable_numerics/field_state.hpp" +#include "smith/differentiable_numerics/differentiable_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" + +namespace smith { + +smith::LinearSolverOptions solid_linear_options{.linear_solver = smith::LinearSolver::CG, + .preconditioner = smith::Preconditioner::HypreJacobi, + .relative_tol = 1e-11, + .absolute_tol = 1e-11, + .max_iterations = 10000, + .print_level = 0}; + +smith::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; + +struct SolidMechanicsMeshFixture : public testing::Test { + double length = 1.0; + double width = 0.04; + int num_elements_x = 12; + int num_elements_y = 2; + int num_elements_z = 2; + double elem_size = length / num_elements_x; + + void SetUp() + { + smith::StateManager::initialize(datastore, "solid"); + auto mfem_shape = mfem::Element::QUADRILATERAL; + mesh = std::make_shared( + mfem::Mesh::MakeCartesian3D(num_elements_x, num_elements_y, num_elements_z, mfem_shape, length, width, width), + "mesh", 0, 0); + mesh->addDomainOfBoundaryElements("left", smith::by_attr(3)); + mesh->addDomainOfBoundaryElements("right", smith::by_attr(5)); + } + + static constexpr double total_simulation_time_ = 1.1; + static constexpr size_t num_steps_ = 4; + static constexpr double dt_ = total_simulation_time_ / num_steps_; + + axom::sidre::DataStore datastore; + std::shared_ptr mesh; +}; + +template +struct FieldType { + FieldType(std::string n) : name(n) {} + std::string name; +}; + +enum class SecondOrderTimeDiscretization +{ + QuasiStatic, + ImplicitNewmark +}; + +enum class FirstOrderTimeDiscretization +{ + QuasiStatic, + BackwardEuler +}; + +struct FieldStore { + FieldStore(std::shared_ptr mesh, size_t storage_size = 50) + : mesh_(mesh), data_store_(std::make_shared(storage_size)) + { + } + + template + void addShapeDisp(FieldType type) + { + shape_disp_.push_back(smith::createFieldState(*data_store_, Space{}, type.name, mesh_->tag())); + } + + template + void addUnknown(FieldType type) + { + to_unknowns_[type.name] = unknowns_.size(); + unknowns_.push_back(smith::createFieldState(*data_store_, Space{}, type.name, mesh_->tag())); + } + + template + void addDerived(FieldType type, SecondOrderTimeDiscretization time_discretization, std::vector custom_names = {}) + { + if (!custom_names.size()) { + custom_names.push_back(type.name + "_dot"); + custom_names.push_back(type.name + "_ddot"); + } + SLIC_ERROR_IF(custom_names.size()!=2, "Second order time discretized fields must add two derived fields f_dot and f_ddot"); + to_derived_[custom_names[0]] = derived_.size(); + derived_.push_back(smith::createFieldState(*data_store_, Space{}, custom_names[0], mesh_->tag())); + + to_derived_[custom_names[1]] = derived_.size(); + derived_.push_back(smith::createFieldState(*data_store_, Space{}, custom_names[1], mesh_->tag())); + } + + template + void addDerived(FieldType type, FirstOrderTimeDiscretization time_discretization, std::vector custom_names = {}) + { + if (!custom_names.size()) { + custom_names.push_back(type.name + "_dot"); + } + SLIC_ERROR_IF(custom_names.size()!=1, "First order time discretized fields must add exactly one derived field: f_dot"); + to_derived_[custom_names[0]] = derived_.size(); + derived_.push_back(smith::createFieldState(*data_store_, Space{}, custom_names[0], mesh_->tag())); + } + + std::shared_ptr mesh_; + std::shared_ptr data_store_; + + std::vector shape_disp_; + + std::vector unknowns_; + std::vector derived_; + std::vector params_; + + std::map to_unknowns_; + std::map to_derived_; + std::map to_params_; +}; + +struct NewmarkDot +{ + template + SMITH_HOST_DEVICE auto dot([[maybe_unused]] const TimeInfo& t, [[maybe_unused]] const T1& field_new, + [[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; + } +}; + + +template +void createSpaces(const FieldStore& field_store, std::vector& spaces, FirstType type) +{ + const size_t test_index = field_store.to_unknowns_.at(type.name); + spaces.push_back(&field_store.unknowns_[test_index].get()->space()); +} + + +template +void createSpaces(const FieldStore& field_store, std::vector& spaces, FirstType type, Types... types) +{ + const size_t test_index = field_store.to_unknowns_.at(type.name); + spaces.insert(spaces.begin(), &field_store.unknowns_[test_index].get()->space()); + createSpaces(field_store, spaces, types...); +} + + +template +auto createWeakForm(const FieldStore& field_store, std::string name, FieldType test_type, FieldType... field_types) +{ + const size_t test_index = field_store.to_unknowns_.at(test_type.name); + const mfem::ParFiniteElementSpace& test_space = field_store.unknowns_[test_index].get()->space(); + std::vector input_spaces; + createSpaces(field_store, input_spaces, field_types...); + auto weak_form = std::make_shared > >(name, field_store.mesh_, test_space, input_spaces); + return weak_form; +} + + +TEST_F(SolidMechanicsMeshFixture, A) +{ + SMITH_MARK_FUNCTION; + + FieldType> shape_disp_type("shape_displacement"); + FieldType> disp_type("displacement"); + FieldType> temperature_type("temperature"); + + FieldStore field_store(mesh, 100); + + field_store.addShapeDisp(shape_disp_type); + + field_store.addUnknown(disp_type); + field_store.addUnknown(temperature_type); + + field_store.addDerived(disp_type, SecondOrderTimeDiscretization::ImplicitNewmark, {"velocity", "acceleration"}); + field_store.addDerived(temperature_type, FirstOrderTimeDiscretization::BackwardEuler); + + auto weak_form = createWeakForm(field_store, "solid", disp_type, disp_type, temperature_type); + + weak_form->addBodyIntegral(smith::DependsOn<0,1>{}, mesh->entireBodyName(), [](auto /*t*/, auto /*X*/, auto u, auto temperature) { + auto ones = 0.0*get(u); + ones[0] = 1.0; + return smith::tuple{get(u) + ones, get(u)}; + }); + + auto r = weak_form->residual(TimeInfo(0.0,0.0,0), field_store.shape_disp_[0].get().get(), getConstFieldPointers(field_store.unknowns_, field_store.derived_)); + // std::cout << "norm = " << r.Norml2() << std::endl; + + EXPECT_EQ(0,0); + + // auto derived_types = createDerivedFieldTypes(ddot(disp_type), dot(temperature_type)); + + // FieldType> kappa_type; + // FieldType> mu_type; + // auto params = + + // auto weak_form = + // createWeakForm("solid", smith::DependsOnFields(disp_type, dot(disp_type), ddot(disp_type), temperature_type)); +} + +} // 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/time_discretized_weak_form.hpp b/src/smith/differentiable_numerics/time_discretized_weak_form.hpp index 40e4774fd7..8afac770dc 100644 --- a/src/smith/differentiable_numerics/time_discretized_weak_form.hpp +++ b/src/smith/differentiable_numerics/time_discretized_weak_form.hpp @@ -56,9 +56,10 @@ class TimeDiscretizedWeakForm + template // int... all_active_parameters> void addBodyIntegral(std::string body_name, BodyForceType body_integral) { + auto input_indices = std::make_integer_sequence{}; addBodyIntegral(DependsOn<>{}, body_name, body_integral); } };