Skip to content

Commit

Permalink
Implement DynamicElasticityModel and NewmarkScheme (RobotLocomotion#1…
Browse files Browse the repository at this point in the history
…4619)

Implement DynamicElasticityModel and NewmarkScheme

- DynamicElasticityModel implements ElasticityModel.
- NewmarkScheme implements StateUpdater.
- Add a new method to StateUpdater, IntegrateTime.
- Add a new helper method to FemElement, ExtractElementDofs.
- Fix a bug in DynamicElasticityElement that the entire state (instead
 of the state dofs corresponding to the element) multiplies the
 mass/damping matrix when calculating the element residual.
  • Loading branch information
xuchenhan-tri authored Feb 13, 2021
1 parent ed84c9f commit db7995b
Show file tree
Hide file tree
Showing 13 changed files with 611 additions and 40 deletions.
47 changes: 47 additions & 0 deletions multibody/fixed_fem/dev/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,18 @@ drake_cc_library(
],
)

drake_cc_library(
name = "dynamic_elasticity_model",
hdrs = [
"dynamic_elasticity_model.h",
],
deps = [
":damping_model",
":elasticity_model",
"//geometry/proximity:volume_mesh",
],
)

drake_cc_library(
name = "eigen_conjugate_gradient_solver",
hdrs = [
Expand Down Expand Up @@ -211,6 +223,16 @@ drake_cc_library(
],
)

drake_cc_library(
name = "newmark_scheme",
hdrs = [
"newmark_scheme.h",
],
deps = [
":state_updater",
],
)

drake_cc_library(
name = "quadrature",
hdrs = [
Expand Down Expand Up @@ -308,6 +330,21 @@ drake_cc_googletest(
],
)

drake_cc_googletest(
name = "dynamic_elasticity_model_test",
deps = [
":dynamic_elasticity_element",
":dynamic_elasticity_model",
":linear_constitutive_model",
":linear_simplex_element",
":newmark_scheme",
":simplex_gaussian_quadrature",
"//common/test_utilities:eigen_matrix_compare",
"//geometry/proximity:make_box_mesh",
"//math:gradient",
],
)

drake_cc_googletest(
name = "eigen_conjugate_gradient_solver_test",
deps = [
Expand Down Expand Up @@ -384,6 +421,15 @@ drake_cc_googletest(
],
)

drake_cc_googletest(
name = "newmark_scheme_test",
deps = [
":dummy_element",
":newmark_scheme",
"//common/test_utilities:eigen_matrix_compare",
],
)

drake_cc_googletest(
name = "simplex_gaussian_quadrature_test",
deps = [
Expand Down Expand Up @@ -425,6 +471,7 @@ drake_cc_googletest(
":dummy_element",
":zeroth_order_state_updater",
"//common/test_utilities:eigen_matrix_compare",
"//common/test_utilities:expect_throws_message",
],
)

Expand Down
7 changes: 5 additions & 2 deletions multibody/fixed_fem/dev/dynamic_elasticity_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ class DynamicElasticityElement final
/* residual = Ma-fₑ(x)-fᵥ(x, v)+fₑₓₜ, where M is the mass matrix, fₑ(x) is
the elastic force, fᵥ(x, v) is the damping force and fₑₓₜ is the external
force. */
*residual += this->mass_matrix() * state.qddot();
*residual += this->mass_matrix() *
this->ExtractElementDofs(this->node_indices(), state.qddot());
this->AddNegativeElasticForce(state, residual);
AddNegativeDampingForce(state, residual);
this->AddExternalForce(state, residual);
Expand All @@ -100,7 +101,9 @@ class DynamicElasticityElement final
/* Note that the damping force fᵥ = -D * v, where D is the damping matrix.
As we are accumulating the negative damping force here, the `+=` sign
should be used. */
*negative_damping_force += damping_matrix * state.qdot();
*negative_damping_force +=
damping_matrix *
this->ExtractElementDofs(this->node_indices(), state.qdot());
}

/* Implements FemElement::CalcStiffnessMatrix().
Expand Down
96 changes: 96 additions & 0 deletions multibody/fixed_fem/dev/dynamic_elasticity_model.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#pragma once

#include <array>
#include <utility>

#include "drake/common/eigen_types.h"
#include "drake/geometry/proximity/volume_mesh.h"
#include "drake/multibody/fixed_fem/dev/damping_model.h"
#include "drake/multibody/fixed_fem/dev/elasticity_model.h"

namespace drake {
namespace multibody {
namespace fixed_fem {
/** The FEM model for dynamic 3D elasticity problems. Implements the interface
in FemModel. It is assumed that elements are only added to, but never deleted
from, the model.
@tparam Element The type of DynamicElasticityElement used in this
%DynamicElasticityModel, must be an instantiation of DynamicElasticityElement.
*/
template <class Element>
class DynamicElasticityModel : public ElasticityModel<Element> {
public:
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(DynamicElasticityModel);

using T = typename Element::Traits::T;
using ConstitutiveModel = typename Element::Traits::ConstitutiveModel;

DynamicElasticityModel() = default;
~DynamicElasticityModel() = default;

/** Add tetrahedral DynamicElasticityElements to the %DynamicElasticityModel
from a mesh. The positions of the vertices in the mesh are used as reference
positions for the volume as well as the generalized positions for the model
in MakeFemState().
@param mesh The input tetrahedral mesh that describes the connectivity and
the positions of the vertices. Each geometry::VolumeElement in the input
`mesh` will generate a DynamicElasticityElement in this
%DynamicElasticityModel.
@param constitutive_model The ConstitutiveModel to be used for all the
DynamicElasticityElements created from the input `mesh`.
@param density The mass density of the new elements, in unit kg/m³.
@param damping_model The DampingModel to be used for all the
DynamicElasticityElements created from the input `mesh`.
@throw std::exception if Element::Traits::kNumNodes != 4. */
void AddDynamicElasticityElementsFromTetMesh(
const geometry::VolumeMesh<T>& mesh,
const ConstitutiveModel& constitutive_model, const T& density,
const DampingModel<T>& damping_model) {
/* Alias for more readability. */
constexpr int kDim = Element::Traits::kSolutionDimension;
constexpr int kNumNodes = Element::Traits::kNumNodes;
constexpr int kNumDofs = kDim * kNumNodes;
DRAKE_THROW_UNLESS(kNumNodes == 4);

using geometry::VolumeElementIndex;
/* Record the reference positions of the input mesh. The returned offset is
from before the new tets are added. */
const NodeIndex node_index_offset(this->ParseTetMesh(mesh));

/* Builds and adds new elements. */
const VectorX<T>& X = this->reference_positions();
std::array<NodeIndex, kNumNodes> element_node_indices;
for (VolumeElementIndex i(0); i < mesh.num_elements(); ++i) {
for (int j = 0; j < kNumNodes; ++j) {
/* To obtain the global node index, offset the local index of the nodes
in the mesh (starting from 0) by the existing number of nodes
before the current mesh is added. */
const int node_id = mesh.element(i).vertex(j) + node_index_offset;
element_node_indices[j] = NodeIndex(node_id);
}
const Vector<T, kNumDofs> element_reference_positions =
Element::ExtractElementDofs(element_node_indices, X);
const ElementIndex next_element_index =
ElementIndex(this->num_elements());
const auto& element_reference_positions_reshaped =
Eigen::Map<const Eigen::Matrix<T, kDim, kNumNodes>>(
element_reference_positions.data(), kDim, kNumNodes);
this->AddElement(next_element_index, element_node_indices,
constitutive_model, element_reference_positions_reshaped,
density, this->gravity(), damping_model);
}
}

private:
/* Implements FemModel::DoMakeFemState(). Generalized positions are
initialized to be reference positions of the input mesh vertices. Velocities
and accelerations are initialized to 0. */
FemState<Element> DoMakeFemState() const final {
const VectorX<T>& X = this->reference_positions();
return FemState<Element>(X, VectorX<T>::Zero(X.size()),
VectorX<T>::Zero(X.size()));
}
};
} // namespace fixed_fem
} // namespace multibody
} // namespace drake
18 changes: 7 additions & 11 deletions multibody/fixed_fem/dev/elasticity_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -407,20 +407,16 @@ class ElasticityElement : public FemElement<DerivedElement, DerivedTraits> {
element. */
std::array<Matrix3<T>, Traits::kNumQuadraturePoints> CalcDeformationGradient(
const FemState<DerivedElement>& state) const {
// TODO(xuchenhan-tri): Consider abstracting this potential common operation
// into FemElement.
std::array<Matrix3<T>, Traits::kNumQuadraturePoints> F;
Eigen::Matrix<T, Traits::kSolutionDimension, Traits::kNumNodes> element_x;
const VectorX<T>& x_tmp = state.q();
const auto& x = Eigen::Map<const Matrix3X<T>>(
x_tmp.data(), Traits::kSolutionDimension,
x_tmp.size() / Traits::kSolutionDimension);
for (int i = 0; i < Traits::kNumNodes; ++i) {
element_x.col(i) = x.col(this->node_indices()[i]);
}
constexpr int kNumDofs = Traits::kSolutionDimension * Traits::kNumNodes;
const Vector<T, kNumDofs> element_x =
this->ExtractElementDofs(this->node_indices(), state.q());
const auto& element_x_reshaped = Eigen::Map<
const Eigen::Matrix<T, Traits::kSolutionDimension, Traits::kNumNodes>>(
element_x.data(), Traits::kSolutionDimension, Traits::kNumNodes);
const std::array<typename IsoparametricElementType::JacobianMatrix,
Traits::kNumQuadraturePoints>
dxdxi = isoparametric_element_.CalcJacobian(element_x);
dxdxi = isoparametric_element_.CalcJacobian(element_x_reshaped);
for (int q = 0; q < Traits::kNumQuadraturePoints; ++q) {
F[q] = dxdxi[q] * dxidX_[q];
}
Expand Down
34 changes: 33 additions & 1 deletion multibody/fixed_fem/dev/elasticity_model.h
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,40 @@ class ElasticityModel : public FemModel<Element> {
ElasticityModel() = default;
virtual ~ElasticityModel() = default;

/** Parse a tetrahedral volume mesh, store the positions of the vertices in
the mesh as the reference positions for the vertices, and increment the total
number of vertices in the model. Returns the total number of vertices
*before* the input `mesh` is parsed.
@param mesh The input tetrahedral mesh that describes the connectivity and
the positions of the vertices. Each geometry::VolumeElement in the input
`mesh` will generate an ElasticityElement in this ElasticityModel.
@throw std::exception if Element::Traits::kNumNodes != 4. */
int ParseTetMesh(const geometry::VolumeMesh<T>& mesh) {
/* Alias for more readability. */
constexpr int kDim = Element::Traits::kSolutionDimension;
constexpr int kNumNodes = Element::Traits::kNumNodes;
DRAKE_THROW_UNLESS(kNumNodes == 4);

using geometry::VolumeVertexIndex;
/* Record the reference positions of the input mesh. */
const int num_new_vertices = mesh.num_vertices();
reference_positions_.conservativeResize(reference_positions_.size() +
kDim * num_new_vertices);
const NodeIndex node_index_offset = NodeIndex(this->num_nodes());
for (VolumeVertexIndex i(0); i < num_new_vertices; ++i) {
reference_positions_.template segment<kDim>(
kDim * (i + node_index_offset)) = mesh.vertex(i).r_MV();
}
/* Record the number of vertices *before* the input mesh is parsed. */
const int num_vertices = this->num_nodes();
this->increment_num_nodes(num_new_vertices);
return num_vertices;
}

const VectorX<T>& reference_positions() const { return reference_positions_; }

private:
/* The gravitational acceleration in the world frame. */
VectorX<T> reference_positions_{};
Vector<T, Element::Traits::kSpatialDimension> gravity_{0, 0, -9.81};
};
} // namespace fixed_fem
Expand Down
16 changes: 16 additions & 0 deletions multibody/fixed_fem/dev/fem_element.h
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ class FemElement {
static_cast<const DerivedElement*>(this)->DoCalcMassMatrix(state, M);
}

/** Extract the dofs corresponding to the nodes given by `node_indices` from
the given `state_dofs`. */
static Vector<T, Traits::kSolutionDimension * Traits::kNumNodes>
ExtractElementDofs(
const std::array<NodeIndex, Traits::kNumNodes>& node_indices,
const VectorX<T>& state_dofs) {
constexpr int kDim = Traits::kSolutionDimension;
Vector<T, kDim * Traits::kNumNodes> element_dofs;
for (int i = 0; i < Traits::kNumNodes; ++i) {
DRAKE_ASSERT((node_indices[i] + 1) * kDim <= state_dofs.size());
element_dofs.template segment<kDim>(i * kDim) =
state_dofs.template segment<kDim>(node_indices[i] * kDim);
}
return element_dofs;
}

protected:
/** Assignment and copy constructions are prohibited.
Move constructor is allowed so that FemElement can be stored in
Expand Down
77 changes: 77 additions & 0 deletions multibody/fixed_fem/dev/newmark_scheme.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
#pragma once

#include "drake/multibody/fixed_fem/dev/state_updater.h"

namespace drake {
namespace multibody {
namespace fixed_fem {
/** Implements the interface StateUpdater with Newmark-beta time integration
scheme. Given the value for the current time step accleration `a`, the states
are calculated from states from the previous time step according to the
following equations:
v = vₙ + dt ⋅ (γ ⋅ a + (1−γ) ⋅ aₙ)
x = xₙ + dt ⋅ vₙ + dt² ⋅ [β ⋅ a + (0.5−β) ⋅ aₙ].
See [Newmark, 1959] for the original reference for the method.
[Newmark, 1959] Newmark, Nathan M. "A method of computation for structural
dynamics." Journal of the engineering mechanics division 85.3 (1959): 67-94.
@tparam State The type of FemState to be updated by the %NewmarkScheme. The
template parameter State must be an instantiation of FemState.
@pre State::ode_order() == 2. */
template <class State>
class NewmarkScheme final : public StateUpdater<State> {
public:
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(NewmarkScheme);

static_assert(State::ode_order() == 2);
using T = typename State::T;

/** Construct a Newmark scheme with the provided timestep, `gamma` and `beta`.
@pre dt > 0.
@pre 0 <= gamma <= 1.
@pre 0 <= beta <= 0.5. */
NewmarkScheme(double dt, double gamma, double beta)
: dt_(dt), gamma_(gamma), beta_(beta) {
DRAKE_DEMAND(dt > 0);
DRAKE_DEMAND(0 <= gamma && gamma <= 1);
DRAKE_DEMAND(0 <= beta && beta <= 0.5);
}

~NewmarkScheme() = default;

private:
/* Implements StateUpdater::weights(). */
Vector3<T> do_get_weights() const final {
return {beta_ * dt_ * dt_, gamma_ * dt_, 1.0};
}

/* Implements StateUpdater::DoUpdateState(). */
void DoUpdateState(const VectorX<T>& dz, State* state) const final {
const VectorX<T>& a = state->qddot();
const VectorX<T>& v = state->qdot();
const VectorX<T>& x = state->q();
state->set_qddot(a + dz);
state->set_qdot(v + dt_ * gamma_ * dz);
state->set_q(x + dt_ * dt_ * beta_ * dz);
}

/* Implements StateUpdater::DoAdvanceOneTimeStep(). */
void DoAdvanceOneTimeStep(const State& prev_state, State* state) const final {
const VectorX<T>& an = prev_state.qddot();
const VectorX<T>& vn = prev_state.qdot();
const VectorX<T>& xn = prev_state.q();
const VectorX<T>& a = state->qddot();
state->set_qdot(vn + dt_ * (gamma_ * a + (1.0 - gamma_) * an));
state->set_q(xn + dt_ * vn + dt_ * dt_ * (beta_ * a + (0.5 - beta_) * an));
}

double dt_{0};
double gamma_{0.5};
double beta_{0.25};
};
} // namespace fixed_fem
} // namespace multibody
} // namespace drake
Loading

0 comments on commit db7995b

Please sign in to comment.