diff --git a/src/smith/differentiable_numerics/differentiable_solver.cpp b/src/smith/differentiable_numerics/differentiable_solver.cpp index f06563dcb..7a3b25600 100644 --- a/src/smith/differentiable_numerics/differentiable_solver.cpp +++ b/src/smith/differentiable_numerics/differentiable_solver.cpp @@ -266,6 +266,135 @@ 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_u = std::make_unique(block_offsets); + for (int row_i = 0; row_i < num_rows; ++row_i) { + block_u->GetBlock(row_i) = *u_guesses[static_cast(row_i)]; + } + + auto block_r = std::make_unique(block_offsets); + + 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_; + }); + 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_u->GetBlock(row_i); + } + + 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) { @@ -273,11 +402,19 @@ std::shared_ptr buildDifferentiableLinearSolver(Line return std::make_shared(std::move(linear_solver), std::move(precond)); } -std::shared_ptr buildDifferentiableNonlinearSolver( - smith::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)); +} + +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 bf6b738aa..2276a205f 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,6 +176,36 @@ 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 + 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 +}; + /// @brief Create a differentiable linear solver /// @param linear_opts linear options struct /// @param mesh mesh @@ -189,4 +220,11 @@ 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 diff --git a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp index c148350df..d7ddec9a5 100644 --- a/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp +++ b/src/smith/differentiable_numerics/dirichlet_boundary_conditions.hpp @@ -72,39 +72,22 @@ 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); + 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_, 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_, 0); } /// @brief Constrain the dofs of a scalar field over a domain diff --git a/src/smith/differentiable_numerics/field_state.hpp b/src/smith/differentiable_numerics/field_state.hpp index e9528faf8..a2937cad2 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/nonlinear_solve.cpp b/src/smith/differentiable_numerics/nonlinear_solve.cpp index 2f31498fc..1f1f34a55 100644 --- a/src/smith/differentiable_numerics/nonlinear_solve.cpp +++ b/src/smith/differentiable_numerics/nonlinear_solve.cpp @@ -272,7 +272,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(); @@ -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,15 +318,14 @@ 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(); 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) { @@ -342,12 +340,23 @@ 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) { + 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) { - 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]; + } } } @@ -363,13 +372,11 @@ 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) { 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); } - return residuals; }; @@ -389,29 +396,37 @@ 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; }; @@ -558,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/nonlinear_solve.hpp b/src/smith/differentiable_numerics/nonlinear_solve.hpp index 3529a3006..b89ef7857 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 diff --git a/src/smith/differentiable_numerics/solid_mechanics_state_advancer.cpp b/src/smith/differentiable_numerics/solid_mechanics_state_advancer.cpp index 7a9b888e4..d60e9dfd1 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/tests/CMakeLists.txt b/src/smith/differentiable_numerics/tests/CMakeLists.txt index dc5925a79..06681ec85 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 000000000..a34405434 --- /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 bde1c31fe..8afac770d 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); } }; @@ -67,10 +68,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 +96,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 +112,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 +131,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 +146,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 };