-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
rod2d.cc
1055 lines (927 loc) · 40.2 KB
/
rod2d.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
#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);