Skip to content

Conversation

@tupek2
Copy link
Collaborator

@tupek2 tupek2 commented Dec 5, 2025

Add implicit (differentiable) linear and nonlinear solvers. Add time discretization for second order systems. Add the ability to compute resultant forces. Dynamic patch test and design sensitivity tests are included.

/// adjoint solves
class LinearDifferentiableSolver : public DifferentiableSolver {
public:
/// @brief Construct from a linear solver and linear precondition which may be used by the linear solver
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This sentence needs more linear!

nonlinear_solver_; ///< the nonlinear equation solver used for the forward pass
};

/// @brief Abstract interface to DifferentiableBlockSolver inteface. Each dfferenriable block solve should provide
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @brief Abstract interface to DifferentiableBlockSolver inteface. Each dfferenriable block solve should provide
/// @brief Abstract interface to DifferentiableBlockSolver interface. Each differentiable block solve should provide

using DualPtr = std::shared_ptr<FieldD>; ///< using
using MatrixPtr = std::unique_ptr<mfem::HypreParMatrix>; ///< using

/// @brief required for certain solvers/preconditions, e.g. when multigrid algorithms want a near null-space
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @brief required for certain solvers/preconditions, e.g. when multigrid algorithms want a near null-space
/// @brief Required for certain solvers/preconditions, e.g. when multigrid algorithms want a near null-space

{
}

} // namespace smith No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} // namespace smith
} // namespace smith

I know this is your favorite thing i nitpick....

DirichletBoundaryConditions(const Mesh& mesh, mfem::ParFiniteElementSpace& space);

/// @brief Specify time and space varying Dirichlet boundary conditions over a domain.
/// @param domain domain All dofs in this domain have boundary conditions applied to it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @param domain domain All dofs in this domain have boundary conditions applied to it.
/// @param domain All dofs in this domain have boundary conditions applied to it.

pretty sure thats not supposed to be there.

}

/// @brief Specify time and space varying Dirichlet boundary conditions over a domain.
/// @param domain domain All dofs in this domain have boundary conditions applied to it.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @param domain domain All dofs in this domain have boundary conditions applied to it.
/// @param domain All dofs in this domain have boundary conditions applied to it.

mfem::ParFiniteElementSpace& space_; ///< save the space for the field which will be constrained
};

} // namespace smith No newline at end of file
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
} // namespace smith
} // namespace smith

/// @brief initialize on the gretl::DataStore a ResultantState with values from s
inline ResultantState create_field_resultant(gretl::DataStore& dataStore, const smith::FEDualPtr& s)
/// @brief initialize on the gretl::DataStore a ReactionState with values from s
inline ReactionState create_reaction_state(gretl::DataStore& dataStore, const smith::FEDualPtr& s)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As much as i like pot-hole case, Smith does camel case.

@@ -0,0 +1,574 @@
#include "smith/physics/weak_form.hpp"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "smith/physics/weak_form.hpp"
// 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/physics/weak_form.hpp"


auto eval_residuals = [=](const std::vector<FEFieldPtr>& unknowns) {
SLIC_ERROR_IF(unknowns.size() != num_rows,
"block solver unknown size must match the number or residuals in block_solve");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"block solver unknown size must match the number or residuals in block_solve");
"block solver unknowns size must match the number or residuals in block_solve");

.. i think...

@@ -0,0 +1,66 @@
// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
// Copyright (c) Lawrence Livermore National Security, LLC and

@@ -0,0 +1,89 @@
// Copyright (c), Lawrence Livermore National Security, LLC and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright (c), Lawrence Livermore National Security, LLC and
// Copyright (c) Lawrence Livermore National Security, LLC and


/// @brief gretl-function implementation which evaluates the residual force (which is minus the mechanical force)
/// given
/// shape displacement, states and params. The field_for_residual_space Field is only used to set the approriate size
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// shape displacement, states and params. The field_for_residual_space Field is only used to set the approriate size
/// shape displacement, states and params. The field_for_residual_space Field is only used to set the appropriate size

FEDualPtr R = std::make_shared<FiniteElementDual>(field_for_residual_space_->space(),
"residual"); // set up output pointer
// evaluate the residual with zero acceleration contribution
// std::cout << "time info = " << time_info.time() << std::endl;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Delete dead code... does this comment go with the dead code or line below?

@@ -0,0 +1,62 @@
#include "smith/physics/weak_form.hpp"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#include "smith/physics/weak_form.hpp"
// 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/physics/weak_form.hpp"

@@ -0,0 +1,118 @@
// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
// Copyright (c) Lawrence Livermore National Security, LLC and

* @file solid_mechanics_state_advancer.hpp
* .hpp
*
* @brief Specifies parametrized residuals and various linearized evaluations for arbitrary nonlinear systems of
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
* @brief Specifies parametrized residuals and various linearized evaluations for arbitrary nonlinear systems of
* @brief Specifies parameterized residuals and various linearized evaluations for arbitrary nonlinear systems of

}

/// @brief Utility function to consistently construct all the weak forms and FieldStates for a solid mechanics
/// application You will get back: shape_disp, states, params, time, and solid_mechanics_weak_form
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// application You will get back: shape_disp, states, params, time, and solid_mechanics_weak_form
/// application you will get back: shape_disp, states, params, time, and solid_mechanics_weak_form

@@ -0,0 +1,154 @@
// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Copyright (c) 2019-2024, Lawrence Livermore National Security, LLC and
// Copyright (c) Lawrence Livermore National Security, LLC and

[[maybe_unused]] const T4& accel_old) const
{
return (2.0 / t.dt()) * (field_new - field_old) - velo_old;
// if (method_ == SecondOrderTimeIntegrationMethod::IMPLICIT_NEWMARK) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dead code?

{
auto dt = t.dt();
return (4.0 / (dt * dt)) * (field_new - field_old) - (4.0 / dt) * velo_old - accel_old;
// auto accel = (4.0 / (dt * dt)) * (field_new - field_old) - (4.0 / dt) * velo_old - accel_old;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

More

size_t cycle() const { return cycle_; }

/// @brief return the time info corresponding to the end of this cycle (time + dt, dt, cycle)
TimeInfo end_time_info() const { return TimeInfo(time() + dt(), dt(), cycle()); }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wrong case

@white238
Copy link
Member

white238 commented Jan 6, 2026

The moving of smith/differentiable_numerics/test/differentiable_test_utils.hpp to smith/differentiable_numerics/differentiable_test_utils.hpp confuses me. Should it not be named that?

@white238 white238 closed this Jan 6, 2026
Comment on lines +109 to +115
SLIC_ERROR_IF(
reaction_name_to_reaction_index_.find(dual_name) == reaction_name_to_reaction_index_.end(),
axom::fmt::format("Could not find dual named {0} in mesh with tag \"{1}\" to get", dual_name, mesh_->tag()));
size_t reaction_index = reaction_name_to_reaction_index_.at(dual_name);
SLIC_ERROR_IF(
reaction_index >= reaction_names_.size(),
"Dual reactions not correctly allocated yet, cannot get dual until after initializationStep is called.");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The second error check appears unnecessary to me: the constructor generates the indices in 1:1 correspondence with the names. Also, I don't see a method called initializationStep in this class or the base class. The first error check is not strictly needed either, since the at method raises an exception if the key is not found.

More generally, I prefer having the data validity checking on the data structure itself to keep these kinds of checks from being duplicated at every use.

Comment on lines +177 to +178
SLIC_ERROR_IF(state_name_to_field_index_.find(field_name) == state_name_to_field_index_.end(),
axom::fmt::format("Could not find dual named {0} in mesh with tag {1}", field_name, mesh_->tag()));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the error check duplication I was referring to. There are several more.

/// @brief destructor
virtual ~DifferentiableSolver() {}

/// @brief required for certain solvers/preconditions, e.g. when multigrid algorithms want a near null-space
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/// @brief required for certain solvers/preconditions, e.g. when multigrid algorithms want a near null-space
/// @brief required for certain solvers/preconditioners, e.g. when multigrid algorithms want a near null-space

Comment on lines +55 to +56
// there should be one less input state, as the higher time derivative term (e.g., acceleration), does not have a
// predictor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this comment up to date? The check below ensures things have the same size, not "one less" as the comment states. If the comment is correct, then I don't understand what it's trying to say. Can you rephrase?

std::function<std::unique_ptr<mfem::HypreParMatrix>(const smith::FiniteElementState&)> jacobian) const override;

/// @overload
std::shared_ptr<smith::FiniteElementState> solveAdjoint(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment is not a point requiring changes in the PR, just a conversation I want to start while it's in mind: Now that we have an abstraction for a solver and adjoint solver, it could be a natural way to remove the memory-intensive copy of the sparse jacobian (for the sparse jacobian transpose). The solveAdjoint method could take the jacobian and use TransposeOperator or something similar to get the action of the transpose. We've been noting and complaining about this memory usage for a while. Should I open an issue or this for follow-up?

I like the idea of an abstraction like adjointSolve taking the responsibility of transposing (instead of putting it on the caller), since that's the main difference from the solve method.

/// smith::tensor<double,spatial_dim> corresponding to the spatial coordinate. The functor must return a double. For
/// example: [](double t, smith::tensor<double, dim> X) { return 1.0; }
template <int spatial_dim, typename AppliedDisplacementFunction>
void setScalarBCs(const Domain& domain, int component, AppliedDisplacementFunction applied_displacement)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find it confusing to have a method named setScalarBCs take an argument for a component. In my view, this is an overload for setVectorBCs for the special case of setting one component, saving the typing of { and } characters around the index.

{
int field_dim = space_.GetVDim();
for (auto component : components) {
SLIC_ERROR_IF(component >= field_dim,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should probably also check if component < 0.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's great that this is factored out for all the physics modules! 👍

DoubleState evaluateObjective(const ScalarObjective& objective, const FieldState& shape_disp,
const std::vector<FieldState>& inputs, const TimeInfo& time_info)
{
const ScalarObjective* objective_ptr = &objective;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the indirection? You can't capture objective by reference in the lambdas? I'm not worried about overhead, just trying to clear up my understanding of gretl.

for (auto& p : params) allFields.push_back(p);
allFields.push_back(shape_disp);

bool have_bc_field = bc_field;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just a thought: in the case where BCs are done the classic way, could you use the bc_manager to populate bc_field? Then the code would be unified, no conditionals for have_bc_field.

@white238 white238 reopened this Jan 6, 2026
@white238
Copy link
Member

white238 commented Jan 6, 2026

Sorry! wrong button!

}
setVectorBCs<spatial_dim>(domain, components, applied_displacement);
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For adding dirichlet bc on all components, should we use
bcs_.addEssential(bc_set, coefficient, space)?

Comment on lines +15 to +21
auto constrained_dofs = bc_manager->allEssentialTrueDofs();
if (bc_field_ptr) {
for (int i = 0; i < constrained_dofs.Size(); i++) {
int j = constrained_dofs[i];
(*primal_field)[j] = (*bc_field_ptr)(j);
}
} else {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
auto constrained_dofs = bc_manager->allEssentialTrueDofs();
if (bc_field_ptr) {
for (int i = 0; i < constrained_dofs.Size(); i++) {
int j = constrained_dofs[i];
(*primal_field)[j] = (*bc_field_ptr)(j);
}
} else {
if (bc_field_ptr) {
auto constrained_dofs = bc_manager->allEssentialTrueDofs();
for (int i = 0; i < constrained_dofs.Size(); i++) {
int j = constrained_dofs[i];
(*primal_field)[j] = (*bc_field_ptr)(j);
}
} else {

}
}

TEST_F(SolidMechanicsMeshFixture, SensitivitiesBasePhysics)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be an example instead?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready for review Ready for active inspection by reviewers

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants