You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
Conception/drake-master/examples/rod2d/rod2d.cc

1056 lines
40 KiB

#include "drake/examples/rod2d/rod2d.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <stdexcept>
#include <utility>
#include <vector>
#include "drake/common/drake_assert.h"
#include "drake/systems/framework/basic_vector.h"
// TODO(edrumwri,sherm1) This code is currently written out mostly in scalars
// but should be done in 2D vectors instead to make it more compact, easier to
// understand, and easier to relate to our 3D code.
namespace drake {
namespace examples {
namespace rod2d {
template <typename T>
Rod2D<T>::Rod2D(SystemType system_type, double dt)
: system_type_(system_type), dt_(dt) {
// Verify that the simulation approach is either piecewise DAE or
// compliant ODE.
if (system_type == SystemType::kDiscretized) {
if (dt <= 0.0)
throw std::logic_error(
"Discretization approach must be constructed using"
" strictly positive step size.");
// Discretization approach requires three position variables and
// three velocity variables, all discrete, and periodic update.
this->DeclarePeriodicDiscreteUpdateNoHandler(dt);
auto state_index = this->DeclareDiscreteState(6);
state_output_port_ = &this->DeclareStateOutputPort(
"state_output", state_index);
} else {
if (dt != 0)
throw std::logic_error(
"Piecewise DAE and compliant approaches must be "
"constructed using zero step size.");
// Both piecewise DAE and compliant approach require six continuous
// variables.
auto state_index = this->DeclareContinuousState(
Rod2dStateVector<T>(), 3, 3, 0); // q, v, z
state_output_port_ = &this->DeclareStateOutputPort(
"state_output", state_index);
}
// TODO(edrumwri): When type is SystemType::kPiecewiseDAE, allocate the
// abstract mode variables.
this->DeclareInputPort(systems::kUseDefaultName, systems::kVectorValued, 3);
}
// Computes the external forces on the rod.
// @returns a three dimensional vector corresponding to the forces acting at
// the center-of-mass of the rod (first two components) and the
// last corresponding to the torque acting on the rod (final
// component). All forces and torques are expressed in the world
// frame.
template <class T>
Vector3<T> Rod2D<T>::ComputeExternalForces(
const systems::Context<T>& context) const {
// Compute the external forces (expressed in the world frame).
const int port_index = 0;
const VectorX<T>& input = this->get_input_port(port_index).Eval(context);
const Vector3<T> fgrav(0, mass_ * get_gravitational_acceleration(), 0);
const Vector3<T> fapplied = input.segment(0, 3);
return fgrav + fapplied;
}
template <class T>
int Rod2D<T>::DetermineNumWitnessFunctions(
const systems::Context<T>&) const {
// No witness functions if this is not a piecewise DAE model.
if (system_type_ != SystemType::kPiecewiseDAE)
return 0;
// TODO(edrumwri): Flesh out this stub.
throw std::domain_error("Rod2D<T>::DetermineNumWitnessFunctions is stubbed");
}
/// Gets the integer variable 'k' used to determine the point of contact
/// indicated by the current mode.
/// @throws std::exception if this is a discretized system (implying that
/// modes are unused).
/// @returns the value -1 to indicate the bottom of the rod (when theta = pi/2),
/// +1 to indicate the top of the rod (when theta = pi/2), or 0 to
/// indicate the mode where both endpoints of the rod are contacting
/// the halfspace.
template <class T>
int Rod2D<T>::get_k(const systems::Context<T>& context) const {
if (system_type_ != SystemType::kPiecewiseDAE)
throw std::logic_error("'k' is only valid for piecewise DAE approach.");
const int k = context.template get_abstract_state<int>(1);
DRAKE_DEMAND(std::abs(k) <= 1);
return k;
}
template <class T>
Vector2<T> Rod2D<T>::CalcRodEndpoint(const T& x, const T& y, int k,
const T& ctheta, const T& stheta,
double half_rod_len) {
const T cx = x + k * ctheta * half_rod_len;
const T cy = y + k * stheta * half_rod_len;
return Vector2<T>(cx, cy);
}
template <class T>
Vector2<T> Rod2D<T>::CalcCoincidentRodPointVelocity(
const Vector2<T>& p_WRo, const Vector2<T>& v_WRo,
const T& w_WR, // aka thetadot
const Vector2<T>& p_WC) {
const Vector2<T> p_RoC_W = p_WC - p_WRo; // Vector from R origin to C, in W.
const Vector2<T> v_WRc = v_WRo + w_cross_r(w_WR, p_RoC_W);
return v_WRc;
}
/// Solves MX = B for X, where M is the generalized inertia matrix of the rod
/// and is defined in the following manner:<pre>
/// M = | m 0 0 |
/// | 0 m 0 |
/// | 0 0 J | </pre>
/// where `m` is the mass of the rod and `J` is its moment of inertia.
template <class T>
MatrixX<T> Rod2D<T>::solve_inertia(const MatrixX<T>& B) const {
const T inv_mass = 1.0 / get_rod_mass();
const T inv_J = 1.0 / get_rod_moment_of_inertia();
Matrix3<T> iM;
iM << inv_mass, 0, 0,
0, inv_mass, 0,
0, 0, inv_J;
return iM * B;
}
/// The velocity tolerance for sliding.
template <class T>
T Rod2D<T>::GetSlidingVelocityTolerance() const {
// TODO(edrumwri): Change this coarse tolerance, which is only guaranteed to
// be reasonable for rod locations near the world origin and rod velocities
// of unit magnitude, to a computed tolerance that can account for these
// assumptions being violated.
return 100 * std::numeric_limits<double>::epsilon();
}
// Gets the time derivative of a 2D rotation matrix.
template <class T>
Matrix2<T> Rod2D<T>::GetRotationMatrixDerivative(T theta, T thetadot) {
using std::cos;
using std::sin;
const T cth = cos(theta), sth = sin(theta);
Matrix2<T> Rdot;
Rdot << -sth, -cth, cth, -sth;
return Rdot * thetadot;
}
template <class T>
void Rod2D<T>::GetContactPoints(const systems::Context<T>& context,
std::vector<Vector2<T>>* points) const {
using std::cos;
using std::sin;
DRAKE_DEMAND(points != nullptr);
DRAKE_DEMAND(points->empty());
const Vector3<T> q = GetRodConfig(context);
const T& x = q[0];
const T& y = q[1];
T cth = cos(q[2]), sth = sin(q[2]);
// Get the two rod endpoint locations.
const T half_len = get_rod_half_length();
const Vector2<T> pa = CalcRodEndpoint(x, y, -1, cth, sth, half_len);
const Vector2<T> pb = CalcRodEndpoint(x, y, +1, cth, sth, half_len);
// If an endpoint touches the ground, add it as a contact point.
if (pa[1] <= 0)
points->push_back(pa);
if (pb[1] <= 0)
points->push_back(pb);
}
template <class T>
void Rod2D<T>::GetContactPointsTangentVelocities(
const systems::Context<T>& context,
const std::vector<Vector2<T>>& points, std::vector<T>* vels) const {
DRAKE_DEMAND(vels != nullptr);
const Vector3<T> q = GetRodConfig(context);
const Vector3<T> v = GetRodVelocity(context);
// Get necessary quantities.
const Vector2<T> p_WRo = q.segment(0, 2);
const Vector2<T> v_WRo = v.segment(0, 2);
const T& w_WR = v[2];
vels->resize(points.size());
for (size_t i = 0; i < points.size(); ++i) {
(*vels)[i] = CalcCoincidentRodPointVelocity(p_WRo, v_WRo, w_WR,
points[i])[0];
}
}
// Gets the row of a contact Jacobian matrix, given a point of contact, @p p,
// and projection direction (unit vector), @p dir. Aborts if @p dir is not a
// unit vector.
template <class T>
Vector3<T> Rod2D<T>::GetJacobianRow(const systems::Context<T>& context,
const Vector2<T>& p,
const Vector2<T>& dir) const {
using std::abs;
const double eps = 10 * std::numeric_limits<double>::epsilon();
DRAKE_DEMAND(abs(dir.norm() - 1) < eps);
// Get rod configuration variables.
const Vector3<T> q = GetRodConfig(context);
// Compute cross product of the moment arm (expressed in the world frame)
// and the direction.
const Vector2<T> r(p[0] - q[0], p[1] - q[1]);
return Vector3<T>(dir[0], dir[1], cross2(r, dir));
}
// Gets the time derivative of a row of a contact Jacobian matrix, given a
// point of contact, @p p, and projection direction (unit vector), @p dir.
// Aborts if @p dir is not a unit vector.
template <class T>
Vector3<T> Rod2D<T>::GetJacobianDotRow(const systems::Context<T>& context,
const Vector2<T>& p,
const Vector2<T>& dir) const {
using std::abs;
const double eps = 10 * std::numeric_limits<double>::epsilon();
DRAKE_DEMAND(abs(dir.norm() - 1) < eps);
// Get rod state variables.
const Vector3<T> q = GetRodConfig(context);
const Vector3<T> v = GetRodVelocity(context);
// Get the transformation of vectors from the rod frame to the
// world frame and its time derivative.
const T& theta = q[2];
Eigen::Rotation2D<T> R(theta);
// Get the vector from the rod center-of-mass to the contact point,
// expressed in the rod frame.
const Vector2<T> x = q.segment(0, 2);
const Vector2<T> u = R.inverse() * (p - x);
// Compute the translational velocity of the contact point.
const Vector2<T> xdot = v.segment(0, 2);
const T& thetadot = v[2];
const Matrix2<T> Rdot = GetRotationMatrixDerivative(theta, thetadot);
const Vector2<T> pdot = xdot + Rdot * u;
// Compute cross product of the time derivative of the moment arm (expressed
// in the world frame) and the direction.
const Vector2<T> rdot(pdot[0] - v[0], pdot[1] - v[1]);
return Vector3<T>(0, 0, cross2(rdot, dir));
}
template <class T>
Matrix2<T> Rod2D<T>::GetSlidingContactFrameToWorldTransform(
const T& xaxis_velocity) const {
// Note: the contact normal for the rod with the horizontal plane always
// points along the world +y axis; the sliding tangent vector points along
// either the +/-x (world) axis. The << operator populates the matrix row by
// row, so
// R_WC = | 0 ±1 |
// | 1 0 |
// indicating that the contact normal direction (the +x axis in the contact
// frame) is +y in the world frame and the contact tangent direction (more
// precisely, the direction of sliding, the +y axis in the contact frame) is
// ±x in the world frame.
DRAKE_DEMAND(xaxis_velocity != 0);
Matrix2<T> R_WC;
// R_WC's elements match how they appear lexically: one row per line.
R_WC << 0, ((xaxis_velocity > 0) ? 1 : -1),
1, 0;
return R_WC;
}
template <class T>
Matrix2<T> Rod2D<T>::GetNonSlidingContactFrameToWorldTransform() const {
// Note: the contact normal for the rod with the horizontal plane always
// points along the world +y axis; the non-sliding tangent vector always
// points along the world +x axis. The << operator populates the matrix row by
// row, so
// R_WC = | 0 1 |
// | 1 0 |
// indicating that the contact normal direction is (the +x axis in the contact
// frame) is +y in the world frame and the contact tangent direction (the +y
// axis in the contact frame) is +x in the world frame.
Matrix2<T> R_WC;
// R_WC's elements match how they appear lexically: one row per line.
R_WC << 0, 1,
1, 0;
return R_WC;
}
template <class T>
void Rod2D<T>::CalcConstraintProblemData(
const systems::Context<T>& context,
const std::vector<Vector2<T>>& points,
const std::vector<T>& tangent_vels,
multibody::constraint::ConstraintAccelProblemData<T>* data)
const {
using std::abs;
DRAKE_DEMAND(data != nullptr);
DRAKE_DEMAND(points.size() == tangent_vels.size());
// Set the inertia solver.
data->solve_inertia = [this](const MatrixX<T>& m) {
return solve_inertia(m);
};
// The normal and tangent spanning direction are unique.
const Matrix2<T> R_wc = GetNonSlidingContactFrameToWorldTransform();
const Vector2<T> contact_normal = R_wc.col(0);
const Vector2<T> contact_tangent = R_wc.col(1);
// Verify contact normal and tangent directions are as we expect.
DRAKE_ASSERT(abs(contact_normal.dot(Vector2<T>(0, 1)) - 1) <
std::numeric_limits<double>::epsilon());
DRAKE_ASSERT(abs(contact_tangent.dot(Vector2<T>(1, 0)) - 1) <
std::numeric_limits<double>::epsilon());
// Get the set of contact points.
const int nc = points.size();
// Get the set of tangent velocities.
const T sliding_vel_tol = GetSlidingVelocityTolerance();
// Set sliding and non-sliding contacts.
std::vector<int>& non_sliding_contacts = data->non_sliding_contacts;
std::vector<int>& sliding_contacts = data->sliding_contacts;
non_sliding_contacts.clear();
sliding_contacts.clear();
for (int i = 0; i < nc; ++i) {
if (abs(tangent_vels[i]) < sliding_vel_tol) {
non_sliding_contacts.push_back(i);
} else {
sliding_contacts.push_back(i);
}
}
// Designate sliding and non-sliding contacts.
const int num_sliding = sliding_contacts.size();
const int num_non_sliding = non_sliding_contacts.size();
// Set sliding and non-sliding friction coefficients.
data->mu_sliding.setOnes(num_sliding) *= get_mu_coulomb();
data->mu_non_sliding.setOnes(num_non_sliding) *= get_mu_static();
// Set spanning friction cone directions (set to unity, because rod is 2D).
data->r.resize(num_non_sliding);
for (int i = 0; i < num_non_sliding; ++i)
data->r[i] = 1;
// Form the normal contact Jacobian (N).
const int ngc = 3; // Number of generalized coordinates / velocities.
MatrixX<T> N(nc, ngc);
for (int i = 0; i < nc; ++i)
N.row(i) = GetJacobianRow(context, points[i], contact_normal);
data->N_mult = [N](const VectorX<T>& w) -> VectorX<T> { return N * w; };
// Form Ndot (time derivative of N) and compute Ndot * v.
MatrixX<T> Ndot(nc, ngc);
for (int i = 0; i < nc; ++i)
Ndot.row(i) = GetJacobianDotRow(context, points[i], contact_normal);
const Vector3<T> v = GetRodVelocity(context);
data->kN = Ndot * v;
// Form the tangent directions contact Jacobian (F), its time derivative
// (Fdot), and compute Fdot * v.
const int nr = std::accumulate(data->r.begin(), data->r.end(), 0);
MatrixX<T> F = MatrixX<T>::Zero(nr, ngc);
MatrixX<T> Fdot = MatrixX<T>::Zero(nr, ngc);
for (int i = 0; i < static_cast<int>(non_sliding_contacts.size()); ++i) {
const int contact_index = non_sliding_contacts[i];
F.row(i) = GetJacobianRow(context, points[contact_index], contact_tangent);
Fdot.row(i) = GetJacobianDotRow(
context, points[contact_index], contact_tangent);
}
data->kF = Fdot * v;
data->F_mult = [F](const VectorX<T>& w) -> VectorX<T> { return F * w; };
data->F_transpose_mult = [F](const VectorX<T>& w) -> VectorX<T> {
return F.transpose() * w;
};
// Form N - mu*Q (Q is sliding contact direction Jacobian).
MatrixX<T> N_minus_mu_Q = N;
Vector3<T> Qrow;
for (int i = 0; i < static_cast<int>(sliding_contacts.size()); ++i) {
const int contact_index = sliding_contacts[i];
Matrix2<T> sliding_contract_frame = GetSlidingContactFrameToWorldTransform(
tangent_vels[contact_index]);
const auto& sliding_dir = sliding_contract_frame.col(1);
Qrow = GetJacobianRow(context, points[contact_index], sliding_dir);
N_minus_mu_Q.row(contact_index) -= data->mu_sliding[i] * Qrow;
}
data->N_minus_muQ_transpose_mult = [N_minus_mu_Q](const VectorX<T>& w) ->
VectorX<T> { return N_minus_mu_Q.transpose() * w; };
data->kL.resize(0);
// Set external force vector.
data->tau = ComputeExternalForces(context);
}
template <class T>
void Rod2D<T>::CalcImpactProblemData(
const systems::Context<T>& context,
const std::vector<Vector2<T>>& points,
multibody::constraint::ConstraintVelProblemData<T>* data) const {
using std::abs;
DRAKE_DEMAND(data != nullptr);
// Setup the generalized inertia matrix.
Matrix3<T> M;
M << mass_, 0, 0,
0, mass_, 0,
0, 0, J_;
// Get the generalized velocity.
data->Mv = M * context.get_continuous_state().get_generalized_velocity().
CopyToVector();
// Set the inertia solver.
data->solve_inertia = [this](const MatrixX<T>& m) {
return solve_inertia(m);
};
// The normal and tangent spanning direction are unique for the rod undergoing
// impact (i.e., unlike with non-impacting rigid contact equations, the
// frame does not change depending on sliding velocity direction).
const Matrix2<T> non_sliding_contact_frame =
GetNonSlidingContactFrameToWorldTransform();
const Vector2<T> contact_normal = non_sliding_contact_frame.col(0);
const Vector2<T> contact_tan = non_sliding_contact_frame.col(1);
// Get the set of contact points.
const int num_contacts = points.size();
// Set sliding and non-sliding friction coefficients.
data->mu.setOnes(num_contacts) *= get_mu_coulomb();
// Set spanning friction cone directions (set to unity, because rod is 2D).
data->r.resize(num_contacts);
for (int i = 0; i < num_contacts; ++i)
data->r[i] = 1;
// Form the normal contact Jacobian (N).
const int num_generalized_coordinates = 3;
MatrixX<T> N(num_contacts, num_generalized_coordinates);
for (int i = 0; i < num_contacts; ++i)
N.row(i) = GetJacobianRow(context, points[i], contact_normal);
data->N_mult = [N](const VectorX<T>& w) -> VectorX<T> { return N * w; };
data->N_transpose_mult = [N](const VectorX<T>& w) -> VectorX<T> {
return N.transpose() * w; };
data->kN.setZero(num_contacts);
data->gammaN.setZero(num_contacts);
// Form the tangent directions contact Jacobian (F).
const int nr = std::accumulate(data->r.begin(), data->r.end(), 0);
MatrixX<T> F(nr, num_generalized_coordinates);
for (int i = 0; i < num_contacts; ++i) {
F.row(i) = GetJacobianRow(context, points[i], contact_tan);
}
data->F_mult = [F](const VectorX<T>& w) -> VectorX<T> { return F * w; };
data->F_transpose_mult = [F](const VectorX<T>& w) -> VectorX<T> {
return F.transpose() * w;
};
data->kF.setZero(nr);
data->gammaF.setZero(nr);
data->gammaE.setZero(num_contacts);
data->kL.resize(0);
data->gammaL.resize(0);
}
/// Integrates the Rod 2D example forward in time using a
/// half-explicit discretization scheme.
template <class T>
void Rod2D<T>::DoCalcDiscreteVariableUpdates(
const systems::Context<T>& context,
const std::vector<const systems::DiscreteUpdateEvent<T>*>&,
systems::DiscreteValues<T>* discrete_state) const {
using std::sin;
using std::cos;
// Determine ERP and CFM.
const double erp = get_erp();
const double cfm = get_cfm();
// Get the necessary state variables.
const systems::BasicVector<T>& state = context.get_discrete_state(0);
const auto& q = state.value().template segment<3>(0);
Vector3<T> v = state.value().template segment<3>(3);
const T& x = q(0);
const T& y = q(1);
const T& theta = q(2);
// Construct the problem data.
const int ngc = 3; // Number of generalized coords / velocities.
multibody::constraint::ConstraintVelProblemData<T> problem_data(ngc);
// Two contact points, corresponding to the two rod endpoints, are always
// used, regardless of whether any part of the rod is in contact with the
// halfspace. This practice is standard in discretization approaches with
// constraint stabilization. See:
// M. Anitescu and G. Hart. A Constraint-Stabilized Time-Stepping Approach
// for Rigid Multibody Dynamics with Joints, Contact, and Friction. Intl.
// J. for Numerical Methods in Engr., 60(14), 2004.
const int nc = 2;
// Find left and right end point locations.
const T stheta = sin(theta), ctheta = cos(theta);
const Vector2<T> left =
CalcRodEndpoint(x, y, -1, ctheta, stheta, half_length_);
const Vector2<T> right =
CalcRodEndpoint(x, y, 1, ctheta, stheta, half_length_);
// Set up the contact normal and tangent (friction) direction Jacobian
// matrices. These take the form:
// | 0 1 n1 | | 1 0 f1 |
// N = | 0 1 n2 | F = | 1 0 f2 |
// where n1, n2/f1, f2 are the moment arm induced by applying the
// force at the given contact point along the normal/tangent direction.
Eigen::Matrix<T, nc, ngc> N, F;
N(0, 0) = N(1, 0) = 0;
N(0, 1) = N(1, 1) = 1;
N(0, 2) = (left[0] - x);
N(1, 2) = (right[0] - x);
F(0, 0) = F(1, 0) = 1;
F(0, 1) = F(1, 1) = 0;
F(0, 2) = -(left[1] - y);
F(1, 2) = -(right[1] - y);
// Set the number of tangent directions.
problem_data.r = { 1, 1 };
// Get the total number of tangent directions.
const int nr = std::accumulate(
problem_data.r.begin(), problem_data.r.end(), 0);
// Set the coefficients of friction.
problem_data.mu.setOnes(nc) *= get_mu_coulomb();
// Set up the operators.
problem_data.N_mult = [&N](const VectorX<T>& vv) -> VectorX<T> {
return N * vv;
};
problem_data.N_transpose_mult = [&N](const VectorX<T>& vv) -> VectorX<T> {
return N.transpose() * vv;
};
problem_data.F_transpose_mult = [&F](const VectorX<T>& vv) -> VectorX<T> {
return F.transpose() * vv;
};
problem_data.F_mult = [&F](const VectorX<T>& vv) -> VectorX<T> {
return F * vv;
};
problem_data.solve_inertia = [this](const MatrixX<T>& mm) -> MatrixX<T> {
return GetInverseInertiaMatrix() * mm;
};
// Update the generalized velocity vector with discretized external forces
// (expressed in the world frame).
const Vector3<T> fext = ComputeExternalForces(context);
v += dt_ * problem_data.solve_inertia(fext);
problem_data.Mv = GetInertiaMatrix() * v;
// Set stabilization parameters.
problem_data.kN.resize(nc);
problem_data.kN[0] = erp * left[1] / dt_;
problem_data.kN[1] = erp * right[1] / dt_;
problem_data.kF.setZero(nr);
// Set regularization parameters.
problem_data.gammaN.setOnes(nc) *= cfm;
problem_data.gammaF.setOnes(nr) *= cfm;
problem_data.gammaE.setOnes(nc) *= cfm;
// Solve the constraint problem.
VectorX<T> cf;
solver_.SolveImpactProblem(problem_data, &cf);
// Compute the updated velocity.
VectorX<T> delta_v;
solver_.ComputeGeneralizedVelocityChange(problem_data, cf, &delta_v);
// Compute the new velocity. Note that external forces have already been
// incorporated into v.
VectorX<T> vplus = v + delta_v;
// Compute the new position using an "explicit" update.
VectorX<T> qplus = q + vplus * dt_;
// Set the new discrete state.
Eigen::VectorBlock<VectorX<T>> new_state =
discrete_state->get_mutable_value(0);
new_state.segment(0, 3) = qplus;
new_state.segment(3, 3) = vplus;
}
// Computes the impulses such that the vertical velocity at the contact point
// is zero and the frictional impulse lies exactly on the friction cone.
// These equations were determined by issuing the
// following commands in Mathematica:
//
// cx[t_] := x[t] + k*Cos[theta[t]]*(r/2)
// cy[t_] := y[t] + k*Sin[theta[t]]*(r/2)
// Solve[{mass*delta_xdot == fF,
// mass*delta_ydot == fN,
// J*delta_thetadot == (cx[t] - x)*fN - (cy - y)*fF,
// 0 == (D[y[t], t] + delta_ydot) +
// k*(r/2) *Cos[theta[t]]*(D[theta[t], t] + delta_thetadot),
// fF == mu*fN *-sgn_cxdot},
// {delta_xdot, delta_ydot, delta_thetadot, fN, fF}]
// where theta is the counter-clockwise angle the rod makes with the x-axis,
// fN and fF are contact normal and frictional forces; delta_xdot,
// delta_ydot, and delta_thetadot represent the changes in velocity,
// r is the length of the rod, sgn_xdot is the sign of the tangent
// velocity (pre-impact), and (hopefully) all other variables are
// self-explanatory.
//
// The first two equations above are the formula
// for the location of the point of contact. The next two equations
// describe the relationship between the horizontal/vertical change in
// momenta at the center of mass of the rod and the frictional/normal
// contact impulses. The fifth equation yields the moment from
// the contact impulses. The sixth equation specifies that the post-impact
// velocity in the vertical direction be zero. The last equation corresponds
// to the relationship between normal and frictional impulses (dictated by the
// Coulomb friction model).
// @returns a Vector2, with the first element corresponding to the impulse in
// the normal direction (positive y-axis) and the second element
// corresponding to the impulse in the tangential direction (positive
// x-axis).
// TODO(@edrumwri) This return vector is backwards -- should be x,y not y,x!
template <class T>
Vector2<T> Rod2D<T>::CalcFConeImpactImpulse(
const systems::Context<T>& context) const {
using std::abs;
// Get the necessary parts of the state.
const systems::VectorBase<T> &state = context.get_continuous_state_vector();
const T& x = state.GetAtIndex(0);
const T& y = state.GetAtIndex(1);
const T& theta = state.GetAtIndex(2);
const T& xdot = state.GetAtIndex(3);
const T& ydot = state.GetAtIndex(4);
const T& thetadot = state.GetAtIndex(5);
// The two points of the rod are located at (x,y) + R(theta)*[0,r/2] and
// (x,y) + R(theta)*[0,-r/2], where
// R(theta) = | cos(theta) -sin(theta) |
// | sin(theta) cos(theta) |
// and r is designated as the rod length. Thus, the heights of
// the rod endpoints are y + sin(theta)*r/2 and y - sin(theta)*r/2,
// or, y + k*sin(theta)*r/2, where k = +/-1.
const T ctheta = cos(theta), stheta = sin(theta);
const int k = (stheta > 0) ? -1 : 1;
const T cy = y + k * stheta * half_length_;
const double mu = mu_;
const double J = J_;
const double mass = mass_;
const double r = 2 * half_length_;
// Compute the impulses.
const T cxdot = xdot - k * stheta * half_length_ * thetadot;
const int sgn_cxdot = (cxdot > 0) ? 1 : -1;
const T fN = (J * mass * (-(r * k * ctheta * thetadot) / 2 - ydot)) /
(J + (r * k * mass * mu * (-y + cy) * ctheta * sgn_cxdot) / 2 -
(r * k * mass * ctheta * (x - (r * k * ctheta) / 2 - x)) / 2);
const T fF = -sgn_cxdot * mu * fN;
// Verify normal force is non-negative.
DRAKE_DEMAND(fN >= 0);
return Vector2<T>(fN, fF);
}
// Computes the impulses such that the velocity at the contact point is zero.
// These equations were determined by issuing the following commands in
// Mathematica:
//
// cx[t_] := x[t] + k*Cos[theta[t]]*(r/2)
// cy[t_] := y[t] + k*Sin[theta[t]]*(r/2)
// Solve[{mass*delta_xdot == fF, mass*delta_ydot == fN,
// J*delta_thetadot == (cx[t] - x)*fN - (cy - y)*fF,
// 0 == (D[y[t], t] + delta_ydot) +
// k*(r/2) *Cos[theta[t]]*(D[theta[t], t] + delta_thetadot),
// 0 == (D[x[t], t] + delta_xdot) +
// k*(r/2)*-Cos[theta[t]]*(D[theta[t], t] + delta_thetadot)},
// {delta_xdot, delta_ydot, delta_thetadot, fN, fF}]
// which computes the change in velocity and frictional (fF) and normal (fN)
// impulses necessary to bring the system to rest at the point of contact,
// 'r' is the rod length, theta is the counter-clockwise angle measured
// with respect to the x-axis; delta_xdot, delta_ydot, and delta_thetadot
// are the changes in velocity.
//
// The first two equations above are the formula
// for the location of the point of contact. The next two equations
// describe the relationship between the horizontal/vertical change in
// momenta at the center of mass of the rod and the frictional/normal
// contact impulses. The fifth equation yields the moment from
// the contact impulses. The sixth and seventh equations specify that the
// post-impact velocity in the horizontal and vertical directions at the
// point of contact be zero.
// @returns a Vector2, with the first element corresponding to the impulse in
// the normal direction (positive y-axis) and the second element
// corresponding to the impulse in the tangential direction (positive
// x-axis).
// TODO(@edrumwri) This return vector is backwards -- should be x,y not y,x!
template <class T>
Vector2<T> Rod2D<T>::CalcStickingImpactImpulse(
const systems::Context<T>& context) const {
// Get the necessary parts of the state.
const systems::VectorBase<T>& state = context.get_continuous_state_vector();
const T& x = state.GetAtIndex(0);
const T& y = state.GetAtIndex(1);
const T& theta = state.GetAtIndex(2);
const T& xdot = state.GetAtIndex(3);
const T& ydot = state.GetAtIndex(4);
const T& thetadot = state.GetAtIndex(5);
// The two points of the rod are located at (x,y) + R(theta)*[0,r/2] and
// (x,y) + R(theta)*[0,-r/2], where
// R(theta) = | cos(theta) -sin(theta) |
// | sin(theta) cos(theta) |
// and r is designated as the rod length. Thus, the heights of
// the rod endpoints are y + sin(theta)*l/2 and y - sin(theta)*l/2,
// or, y + k*sin(theta)*l/2, where k = +/-1.
const T ctheta = cos(theta), stheta = sin(theta);
const int k = (stheta > 0) ? -1 : 1;
const T cy = y + k * stheta * half_length_;
// Compute the impulses.
const double r = 2 * half_length_;
const double mass = mass_;
const double J = J_;
const T fN = (2 * (-(r * J * k * mass * ctheta * thetadot) +
r * k * mass * mass * y * ctheta * xdot -
r * k * mass * mass * cy * ctheta * xdot -
2 * J * mass * ydot + r * k * mass * mass * y * ctheta * ydot -
r * k * mass * mass * cy * ctheta * ydot)) /
(4 * J - 2 * r * k * mass * x * ctheta -
2 * r * k * mass * y * ctheta + 2 * r * k * mass * cy * ctheta +
r * r * k * k * mass * ctheta * ctheta +
2 * r * k * mass * ctheta * x);
const T fF = -((mass * (-2 * r * J * k * ctheta * thetadot + 4 * J * xdot -
2 * r * k * mass * x * ctheta * xdot +
r * r * k * k * mass * ctheta * ctheta * xdot +
2 * r * k * mass * ctheta * x * xdot -
2 * r * k * mass * x * ctheta * ydot +
r * r * k * k * mass * ctheta * ctheta * ydot +
2 * r * k * mass * ctheta * x * ydot)) /
(4 * J - 2 * r * k * mass * x * ctheta -
2 * r * k * mass * y * ctheta + 2 * r * k * mass * cy * ctheta +
r * r * k * k * mass * ctheta * ctheta +
2 * r * k * mass * ctheta * x));
// Verify that normal impulse is non-negative.
DRAKE_DEMAND(fN > 0.0);
return Vector2<T>(fN, fF);
}
// Returns the generalized inertia matrix computed about the
// center of mass of the rod and expressed in the world frame.
template <class T>
Matrix3<T> Rod2D<T>::GetInertiaMatrix() const {
Matrix3<T> M;
M << mass_, 0, 0,
0, mass_, 0,
0, 0, J_;
return M;
}
// Returns the inverse of the generalized inertia matrix computed about the
// center of mass of the rod and expressed in the world frame.
template <class T>
Matrix3<T> Rod2D<T>::GetInverseInertiaMatrix() const {
Matrix3<T> M_inv;
M_inv << 1.0 / mass_, 0, 0,
0, 1.0 / mass_, 0,
0, 0, 1.0 / J_;
return M_inv;
}
// This is a smooth approximation to a step function. Input x goes from 0 to 1;
// output goes 0 to 1 but smoothed with an S-shaped quintic with first and
// second derivatives zero at both ends.
template <class T>
T Rod2D<T>::step5(const T& x) {
DRAKE_ASSERT(0 <= x && x <= 1);
const T x3 = x * x * x;
return x3 * (10 + x * (6 * x - 15)); // 10x^3-15x^4+6x^5
}
// This function models Stribeck dry friction as a C² continuous quintic spline
// with 3 segments that is used to calculate an effective coefficient of
// friction mu_stribeck. That is the composite coefficient of friction that we
// use to scale the normal force to produce the friction force.
//
// We are given the friction coefficients mu_s (static) and mu_d (dynamic), and
// a dimensionless slip speed s>=0, then calculate:
// mu_stribeck =
// (a) s=0..1: smooth interpolation from 0 to mu_s
// (b) s=1..3: smooth interpolation from mu_s down to mu_d (Stribeck)
// (c) s=3..Inf mu_d
//
// s must be non-dimensionalized by taking the actual slip speed and dividing by
// the stiction slip velocity tolerance.
//
// mu_stribeck is zero at s=0 with 1st deriv (slope) zero and 2nd deriv
// (curvature) 0. At large speeds s>>0 the value is mu_d, again with zero slope
// and curvature. That makes mu_stribeck(s) C² continuous for all s>=0.
//
// The resulting curve looks like this:
//
// mu_s ***
// * *
// * *
// * *
// mu_d * * * * * * slope, curvature = 0 at Inf
// *
// *
// *
// 0 * * slope, curvature = 0 at 0
//
// | | |
// s=0 1 3
//
template <class T>
T Rod2D<T>::CalcMuStribeck(const T& mu_s, const T& mu_d, const T& s) {
DRAKE_ASSERT(mu_s >= 0 && mu_d >= 0 && s >= 0);
T mu_stribeck;
if (s >= 3)
mu_stribeck = mu_d; // sliding
else if (s >= 1)
mu_stribeck = mu_s - (mu_s - mu_d) * step5((s - 1) / 2); // Stribeck
else
mu_stribeck = mu_s * step5(s); // 0 <= s < 1 (stiction)
return mu_stribeck;
}
// Finds the locations of the two rod endpoints and generates contact forces at
// either or both end points (depending on contact condition) using a linear
// stiffness model, Hunt and Crossley normal dissipation, and a Stribeck
// friction model. No forces are applied anywhere else along the rod. The force
// is returned as the spatial force F_Ro_W (described in Rod2D class comments),
// represented as (fx,fy,τ).
//
// Note that we are attributing all of the compliance to the *halfspace*, not
// the *rod*, so that forces will be applied to the same points of the rod as is
// done in the rigid contact models. That makes comparison of the models easier.
//
// For each end point, let h be the penetration depth (in the -y direction) and
// v the slip speed along x. Then
// fK = k*h normal force due to stiffness
// fD = fK*d*hdot normal force due to dissipation
// fN = max(fK + fD, 0) total normal force (>= 0)
// fF = -mu(|v|)*fN*sign(v) friction force
// f = fN + fF total force
//
// Here mu(v) is a Stribeck function that is zero at zero slip speed, and
// reaches a maximum mu_s at the stiction speed tolerance. mu_s is the
// static coefficient of friction.
//
// Note: we return the spatial force in a Vector3 but it is not a vector
// quantity.
template <class T>
Vector3<T> Rod2D<T>::CalcCompliantContactForces(
const systems::Context<T>& context) const {
// Depends on continuous state being available.
DRAKE_DEMAND(system_type_ == SystemType::kContinuous);
using std::abs;
using std::max;
// Get the necessary parts of the state.
const systems::VectorBase<T>& state = context.get_continuous_state_vector();
const Vector2<T> p_WRo(state.GetAtIndex(0), state.GetAtIndex(1));
const T theta = state.GetAtIndex(2);
const Vector2<T> v_WRo(state.GetAtIndex(3), state.GetAtIndex(4));
const T w_WR(state.GetAtIndex(5));
const T ctheta = cos(theta), stheta = sin(theta);
// Find left and right end point locations.
Vector2<T> p_WP[2] = {
CalcRodEndpoint(p_WRo[0], p_WRo[1], -1, ctheta, stheta, half_length_),
CalcRodEndpoint(p_WRo[0], p_WRo[1], 1, ctheta, stheta, half_length_)};
// Calculate the net spatial force to apply at rod origin from the individual
// contact forces at the endpoints.
Vector3<T> F_Ro_W(0, 0, 0); // Accumulate contact forces here.
for (int i = 0; i < 2; ++i) {
const Vector2<T>& p_WC = p_WP[i]; // Get contact point C location in World.
// Calculate penetration depth h along -y; negative means separated.
const T h = -p_WC[1];
if (h > 0) { // This point is in contact.
const Vector2<T> v_WRc = // Velocity of rod point coincident with C.
CalcCoincidentRodPointVelocity(p_WRo, v_WRo, w_WR, p_WC);
const T hdot = -v_WRc[1]; // Penetration rate in -y.
const T v = v_WRc[0]; // Slip velocity in +x.
const int sign_v = v < 0 ? -1 : 1;
const T fK = get_stiffness() * h; // See method comment above.
const T fD = fK * get_dissipation() * hdot;
const T fN = max(fK + fD, T(0));
const T mu = CalcMuStribeck(get_mu_static(), get_mu_coulomb(),
abs(v) / get_stiction_speed_tolerance());
const T fF = -mu * fN * T(sign_v);
// Find the point Rc of the rod that is coincident with the contact point
// C, measured from Ro but expressed in W.
const Vector2<T> p_RRc_W = p_WC - p_WRo;
const Vector2<T> f_Rc(fF, fN); // The force to apply at Rc.
const T t_R = cross2(p_RRc_W, f_Rc); // τ=r X f.
F_Ro_W += Vector3<T>(fF, fN, t_R);
}
}
return F_Ro_W; // A 2-vector & scalar, not really a 3-vector.
}
template <class T>
bool Rod2D<T>::IsImpacting(const systems::Context<T>& context) const {
using std::sin;
using std::cos;
// Note: we do not consider modes here.
// TODO(edrumwri): Handle two-point contacts.
// Get state data necessary to compute the point of contact.
const systems::VectorBase<T>& state = context.get_continuous_state_vector();
const T& y = state.GetAtIndex(1);
const T& theta = state.GetAtIndex(2);
const T& ydot = state.GetAtIndex(4);
const T& thetadot = state.GetAtIndex(5);
// Get the height of the lower rod endpoint.
const T ctheta = cos(theta), stheta = sin(theta);
const int k = (stheta > 0) ? -1 : (stheta < 0) ? 1 : 0;
const T cy = y + k * stheta * half_length_;
// If rod endpoint is not touching, there is no impact.
if (cy >= 10 * std::numeric_limits<double>::epsilon()) return false;
// Compute the velocity at the point of contact.
const T cydot = ydot + k * ctheta * half_length_ * thetadot;
// Verify that the rod is not impacting.
return (cydot < -10 * std::numeric_limits<double>::epsilon());
}
// Computes the accelerations of the rod center of mass for the rod, both
// in compliant contact and contact-free.
template <typename T>
void Rod2D<T>::CalcAccelerationsCompliantContactAndBallistic(
const systems::Context<T>& context,
systems::ContinuousState<T>* derivatives) const {
// Obtain the structure we need to write into (ds=d/dt state).
systems::VectorBase<T>& ds = derivatives->get_mutable_vector();
// Get external applied force (a spatial force at Ro, in W).
const auto Fext_Ro_W = ComputeExternalForces(context);
// Calculate contact forces (also spatial force at Ro, in W).
const Vector3<T> Fc_Ro_W = CalcCompliantContactForces(context);
const Vector3<T> F_Ro_W = Fc_Ro_W + Fext_Ro_W; // Total force.
// Second three derivative components are acceleration due to gravity,
// contact forces, and non-gravitational, non-contact external forces.
ds.SetAtIndex(3, F_Ro_W[0]/mass_);
ds.SetAtIndex(4, F_Ro_W[1]/mass_);
ds.SetAtIndex(5, F_Ro_W[2]/J_);
}
template <typename T>
void Rod2D<T>::DoCalcTimeDerivatives(
const systems::Context<T>& context,
systems::ContinuousState<T>* derivatives) const {
using std::sin;
using std::cos;
using std::abs;
// Don't compute any derivatives if this is the discretized system.
if (system_type_ == SystemType::kDiscretized) {
DRAKE_ASSERT(derivatives->size() == 0);
return;
}
// Get the necessary parts of the state.
const systems::VectorBase<T>& state = context.get_continuous_state_vector();
const T& xdot = state.GetAtIndex(3);
const T& ydot = state.GetAtIndex(4);
const T& thetadot = state.GetAtIndex(5);
// Obtain the structure we need to write into.
systems::VectorBase<T>& f = derivatives->get_mutable_vector();
// First three derivative components are xdot, ydot, thetadot.
f.SetAtIndex(0, xdot);
f.SetAtIndex(1, ydot);
f.SetAtIndex(2, thetadot);
// Compute the velocity derivatives (accelerations).
if (system_type_ == SystemType::kContinuous) {
return CalcAccelerationsCompliantContactAndBallistic(context, derivatives);
} else {
// TODO(edrumwri): Implement the piecewise DAE approach.
throw std::domain_error(
"Rod2D<T>::DoCalcTimeDerivatives: piecewise DAE isn't implemented yet");
}
}
/// Sets the rod to a 45 degree angle with the halfspace and positions the rod
/// such that it and the halfspace are touching at exactly one point of contact.
template <typename T>
void Rod2D<T>::SetDefaultState(const systems::Context<T>&,
systems::State<T>* state) const {
using std::sqrt;
using std::sin;
// Initial state corresponds to an inconsistent configuration for piecewise
// DAE.
const double half_len = get_rod_half_length();
VectorX<T> x0(6);
const double r22 = sqrt(2) / 2;
x0 << half_len * r22, half_len * r22, M_PI / 4.0, -1, 0, 0; // Initial state.
if (system_type_ == SystemType::kDiscretized) {
state->get_mutable_discrete_state().get_mutable_vector(0)
.SetFromVector(x0);
} else {
// Continuous variables.
state->get_mutable_continuous_state().SetFromVector(x0);
// Set abstract variables for piecewise DAE approach.
if (system_type_ == SystemType::kPiecewiseDAE) {
// TODO(edrumwri): Indicate that the rod is in the single contact
// sliding mode.
// TODO(edrumwri): Determine and set the point of contact.
}
}
}
template class Rod2D<double>;
} // namespace rod2d
} // namespace examples
} // namespace drake