-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
constraint_solver.h
2556 lines (2279 loc) · 113 KB
/
constraint_solver.h
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
#pragma once
#include <algorithm>
#include <limits>
#include <memory>
#include <numeric>
#include <utility>
#include <vector>
#include "drake/common/text_logging.h"
#include "drake/multibody/constraint/constraint_problem_data.h"
#include "drake/solvers/moby_lcp_solver.h"
namespace drake {
namespace multibody {
namespace constraint {
/// Solves constraint problems for constraint forces. Specifically, given
/// problem data corresponding to a rigid or multi-body system constrained
/// bilaterally and/or unilaterally and acted upon by friction, this class
/// computes the constraint forces.
///
/// This problem can be formulated as a mixed linear complementarity problem
/// (MLCP)- for 2D problems with Coulomb friction or 3D problems without Coulomb
/// friction- or a mixed complementarity problem (for 3D problems with
/// Coulomb friction). We use a polygonal approximation (of selectable accuracy)
/// to the friction cone, which yields a MLCP in all cases.
///
/// Existing algorithms for solving MLCPs, which are based upon algorithms for
/// solving "pure" linear complementarity problems (LCPs), solve smaller classes
/// of problems than the corresponding LCP versions. For example, Lemke's
/// Algorithm, which is provably able to solve the impacting problems covered by
/// this class, can solve LCPs with copositive matrices [Cottle 1992] but MLCPs
/// with only positive semi-definite matrices (the latter is a strict subset of
/// the former) [Sargent 1978].
///
/// Rather than using one of these MLCP algorithms, we instead transform the
/// problem into a pure LCP by first solving for the bilateral constraint
/// forces. This method yields an implication of which the user should be aware.
/// Bilateral constraint forces are computed before unilateral constraint
/// forces: the constraint forces will not be evenly distributed between
/// bilateral and unilateral constraints (assuming such a distribution were even
/// possible).
///
/// For the normal case of unilateral constraints admitting degrees of
/// freedom, the solution methods in this class support "softening" of the
/// constraints, as described in [Lacoursiere 2007] via the constraint force
/// mixing (CFM) and error reduction parameter (ERP) parameters that are now
/// ubiquitous in game multi-body dynamics simulation libraries.
///
/// - [Cottle 1992] R. W. Cottle, J.-S. Pang, and R. E. Stone. The Linear
/// Complementarity Problem. SIAM Classics in Applied
/// Mathematics, 1992.
/// - [Judice 1992] J. J. Judice, J. Machado, and A. Faustino. An extension of
/// the Lemke's method for the solution of a generalized
/// linear complementarity problem. In System Modeling and
/// Optimization (Lecture Notes in Control and Information
/// Sciences), Springer-Verlag, 1992.
/// - [Lacoursiere 2007] C. Lacoursiere. Ghosts and Machines: Regularized
/// Variational Methods for Interactive Simulations of
/// Multibodies with Dry Frictional Contacts.
/// Ph. D. thesis (Umea University), 2007.
/// - [Sargent 1978] R. W. H. Sargent. An efficient implementation of the Lemke
/// Algorithm and its extension to deal with upper and lower
/// bounds. Mathematical Programming Study, 7, 1978.
///
/// @tparam_double_only
template <typename T>
class ConstraintSolver {
public:
ConstraintSolver() = default;
DRAKE_NO_COPY_NO_MOVE_NO_ASSIGN(ConstraintSolver)
/// Structure used to convert a mixed linear complementarity problem to a
/// pure linear complementarity problem (by solving for free variables).
struct MlcpToLcpData {
/// Decomposition of the Delassus matrix GM⁻¹Gᵀ, where G is the bilateral
/// constraint matrix and M is the system generalized inertia matrix.
Eigen::CompleteOrthogonalDecomposition<MatrixX<T>> delassus_QTZ;
/// A function pointer for solving linear systems using MLCP "A" matrix
/// (see @ref Velocity-level-MLCPs).
std::function<MatrixX<T>(const MatrixX<T>&)> A_solve;
/// A function pointer for solving linear systems using only the upper left
/// block of A⁺ in the MLCP (see @ref Velocity-level-MLCPs), where A⁺ is
/// a singularity-robust pseudo-inverse of A, toward exploiting operations
/// with zero blocks. For example:<pre>
/// A⁺ | b |
/// | 0 |</pre> and<pre>
/// A⁺ | B |
/// | 0 |
/// </pre>
/// where `b ∈ ℝⁿᵛ` is an arbitrary vector of dimension equal to the
/// generalized velocities and `B ∈ ℝⁿᵛˣᵐ` is an arbitrary matrix with
/// row dimension equal to the dimension of the generalized velocities and
/// arbitrary number of columns (denoted `m` here).
std::function<MatrixX<T>(const MatrixX<T>&)> fast_A_solve;
};
// TODO(edrumwri): Describe conditions under which it is safe to replace
// A⁻¹ by a pseudo-inverse.
// TODO(edrumwri): Constraint solver needs to use monogram notation throughout
// to make it consistent with the rest of Drake's multibody dynamics
// documentation (see Issue #9080).
/// @name Velocity-level constraint problems formulated as MLCPs.
/// @anchor Velocity-level-MLCPs
/// Constraint problems can be posed as mixed linear complementarity problems
/// (MLCP), which are problems that take the form:<pre>
/// (a) Au + X₁y + a = 0
/// (b) X₂u + X₃y + x₄ ≥ 0
/// (c) y ≥ 0
/// (d) vᵀ(x₄ + X₂u + X₃y) = 0
/// </pre>
/// where `u` are "free" variables, `y` are constrained variables, `A`, `X₁`,
/// `X₂`, and `X₃` are given matrices (we label only the most important
/// variables without subscripts to make them stand out) and `a` and `x₄` are
/// given vectors. If `A` is nonsingular, `u` can be solved for:<pre>
/// (e) u = -A⁻¹ (a + X₁y)
/// </pre>
/// allowing the mixed LCP to be converted to a "pure" LCP `(qq, MM)` by:<pre>
/// (f) qq = x₄ - X₂A⁻¹a
/// (g) MM = X₃ - X₂A⁻¹X₁
/// </pre>
/// This pure LCP can then be solved for `y` such that:<pre>
/// (h) MMv + qq ≥ 0
/// (i) y ≥ 0
/// (j) yᵀ(MMv + qq) = 0
/// </pre>
/// and this value for `y` can be substituted into (e) to obtain the value
/// for `u`.
///
/// <h3>An MLCP-based impact model:</h3>
/// Consider the following problem formulation of a multibody dynamics
/// impact model (taken from [Anitescu 1997]). In a simulator, one could use
/// this model when a collision is detected in order to compute an
/// instantaneous change in velocity.
/// <pre>
/// (1) | M -Gᵀ -Nᵀ -Dᵀ 0 -Lᵀ | | v⁺ | + |-Mv⁻ | = | 0 |
/// | G 0 0 0 0 0 | | fG | + | kᴳ | = | 0 |
/// | N 0 0 0 0 0 | | fN | + | kᴺ | = | x₅ |
/// | D 0 0 0 E 0 | | fD | + | kᴰ | = | x₆ |
/// | 0 0 μ -Eᵀ 0 0 | | λ | + | 0 | = | x₇ |
/// | L 0 0 0 0 0 | | fL | + | kᴸ | = | x₈ |
/// (2) 0 ≤ fN ⊥ x₅ ≥ 0
/// (3) 0 ≤ fD ⊥ x₆ ≥ 0
/// (4) 0 ≤ λ ⊥ x₇ ≥ 0
/// (5) 0 ≤ fL ⊥ x₈ ≥ 0
/// </pre>
/// Here, the velocity variables
/// v⁻ ∈ ℝⁿᵛ, v ∈ ℝⁿᵛ⁺ correspond to the velocity of the system before and
/// after impulses are applied, respectively. More details will be forthcoming
/// but key variables are `M ∈ ℝⁿᵛˣⁿᵛ`, the generalized inertia matrix;
/// `G ∈ ℝⁿᵇˣⁿᵛ`, `N ∈ ℝⁿᶜˣⁿᵛ`, `D ∈ ℝⁿᶜᵏˣⁿᵛ`, and `L ∈ ℝⁿᵘˣⁿᵛ` correspond to
/// Jacobian matrices for various constraints (joints, contact, friction,
/// generic unilateral constraints, respectively); `μ ∈ ℝⁿᶜˣⁿᶜ` is a diagonal
/// matrix comprised of Coulomb friction coefficients; `E ∈ ℝⁿᶜᵏˣⁿᶜ` is a
/// binary matrix used to linearize the friction cone (necessary to make
/// this into a *linear* complementarity problem); `fG ∈ ℝⁿᵇ`, `fN ∈ ℝⁿᶜ`,
/// `fD ∈ ℝⁿᶜᵏ`, and `fL ∈ ℝⁿᵘ` are constraint impulses; `λ ∈ ℝⁿᶜ`, `x₅`,
/// `x₆`, `x₇`, and `x₈` can be viewed as mathematical programming "slack"
/// variables; and `kᴳ ∈ ℝⁿᵇ`, `kᴺ ∈ ℝⁿᶜ`, `kᴰ ∈ ℝⁿᶜᵏ`, `kᴸ ∈ ℝⁿᵘ`
/// allow customizing the problem to, e.g., correct constraint violations and
/// simulate restitution. See @ref constraint_variable_defs for complete
/// definitions of `nv`, `nc`, `nb`, etc.
///
/// From the notation above in Equations (a)-(d), we can convert the MLCP
/// to a "pure" linear complementarity problem (LCP), which is easier to
/// solve, at least for active-set-type mathematical programming
/// approaches:<pre>
/// A ≡ | M -Ĝᵀ| a ≡ |-Mv⁻ | X₁ ≡ |-Nᵀ -Dᵀ 0 -Lᵀ |
/// | Ĝ 0 | | kᴳ | | 0 0 0 0 |
///
/// X₂ ≡ | N 0 | b ≡ | kᴺ | X₃ ≡ | 0 0 0 0 |
/// | D 0 | | kᴰ | | 0 0 E 0 |
/// | 0 0 | | 0 | | μ -Eᵀ 0 0 |
/// | L 0 | | kᴸ | | 0 0 0 0 |
///
/// u ≡ | v⁺ | y ≡ | fN |
/// | fG | | fD |
/// | λ |
/// | fL |
/// </pre>
/// Where applicable, ConstraintSolver computes solutions to linear equations
/// with rank-deficient `A` (e.g., `AX₅ = x₆`) using a least squares approach
/// (the complete orthogonal factorization). Now, using Equations (f) and (g)
/// and defining `C` as the nv × nv-dimensional upper left block of
/// `A⁻¹` (`nv` is the dimension of the generalized velocities) the pure
/// LCP `(qq,MM)` is defined as:<pre>
/// MM ≡ | NCNᵀ NCDᵀ 0 NCLᵀ |
/// | DCNᵀ DCDᵀ E DCLᵀ |
/// | μ -Eᵀ 0 0 |
/// | LCNᵀ LCDᵀ 0 LCLᵀ |
///
/// qq ≡ | kᴺ - |N 0ⁿᵛ⁺ⁿᵇ|A⁻¹a |
/// | kᴰ - |D 0ⁿᵛ⁺ⁿᵇ|A⁻¹a |
/// | 0 |
/// | kᴸ - |L 0ⁿᵛ⁺ⁿᵇ|A⁻¹a |
/// </pre>
/// where `nb` is the number of bilateral constraint equations. The solution
/// `y` will then take the form:<pre>
/// y ≡ | fN |
/// | fD |
/// | λ |
/// | fL |
/// </pre>
/// The key variables for using the MLCP-based formulations are the matrix `A`
/// and vector `a`, as seen in documentation of MlcpToLcpData and the
/// following methods. During its operation, ConstructBaseDiscretizedTimeLcp()
/// constructs (and returns) functions for solving `AX=B`, where `B` is a
/// given matrix and `X` is an unknown matrix. UpdateDiscretizedTimeLcp()
/// computes and returns `a` during its operation.
///
/// <h3>Another use of the MLCP formulation (discretized multi-body dynamics
/// with contact and friction):</h3>
///
/// Without reconstructing the entire MLCP, we now show a very similar
/// formulation to solve the problem of discretizing
/// the multi-body dynamics equations with contact and friction. This
/// particular formulation provides several nice features: 1) the formulation
/// is semi-implicit and models compliant contact efficiently, including both
/// sticking contact and contact between very stiff surfaces; 2) all
/// constraint forces are computed in Newtons (typical "time stepping
/// methods" require considerable care to correctly compare constraint forces,
/// which are impulsive, and non-constraint forces, which are non-impulsive);
/// and 3) can be made almost symplectic by choosing a
/// representation and computational coordinate frame that minimize
/// velocity-dependent forces (thereby explaining the extreme stability of
/// software like ODE and Bullet that computes dynamics in body frames
/// (minimizing the magnitudes of velocity-dependent forces) and provides
/// the ability to disable gyroscopic forces.
///
/// The discretization problem replaces the meaning of v⁻ and v⁺ in the MLCP
/// to mean the generalized velocity at time t and the generalized velocity
/// at time t+h, respectively, for discretization quantum h (or, equivalently,
/// integration step size h). The LCP is adjusted to the form:<pre>
/// MM ≡ | hNCNᵀ+γᴺ hNCDᵀ 0 hNCLᵀ |
/// | hDCNᵀ hDCDᵀ E hDCLᵀ |
/// | μ -Eᵀ 0 0 |
/// | hLCNᵀ hLCDᵀ 0 hLCLᵀ+γᴸ |
///
/// qq ≡ | kᴺ - |N 0ⁿᵛ⁺ⁿᵇ|A⁻¹a |
/// | kᴰ - |D 0ⁿᵛ⁺ⁿᵇ|A⁻¹a |
/// | 0 |
/// | kᴸ - |L 0ⁿᵛ⁺ⁿᵇ|A⁻¹a |</pre>
/// where `γᴺ`, `γᴸ`, `kᴺ`, `kᴸ`, and `a` are all functions of `h`;
/// documentation that describes how to update these (and dependent) problem
/// data to attain desired constraint stiffness and dissipation is
/// forthcoming.
///
/// The procedure one uses to formulate and solve this discretization problem
/// is:
/// -# Call ConstructBaseDiscretizedTimeLcp()
/// -# Select an integration step size, dt
/// -# Compute `kᴺ' and `kᴸ` in the problem data, accounting for dt as
/// necessary
/// -# Call UpdateDiscretizedTimeLcp(), obtaining MM and qq that encode the
/// linear complementarity problem
/// -# Solve the linear complementarity problem
/// -# If LCP solved, quit.
/// -# Reduce dt and repeat the process from 3. until success.
///
/// The solution to the LCP can be used to obtain the constraint forces via
/// PopulatePackedConstraintForcesFromLcpSolution().
///
/// <h3>Obtaining the generalized constraint forces:</h3>
///
/// Given the constraint forces, which have been obtained either through
/// SolveImpactProblem() (in which case the forces are impulsive) or through
/// direct solution of the LCP corresponding to the discretized multibody
/// dynamics problem, followed by
/// PopulatePackedConstraintForcesFromLcpSolution() (in which cases the forces
/// are non-impulsive), the generalized forces/impulses due to the constraints
/// can then be acquired via ComputeGeneralizedForceFromConstraintForces().
// @{
/// Computes the base time-discretization of the system using the problem
/// data, resulting in the `MM` and `qq` described in
/// @ref velocity-level-MLCPs; if `MM` and `qq` are modified no further, the
/// LCP corresponds to an impact problem (i.e., the multibody dynamics problem
/// would not be discretized). The data output (`mlcp_to_lcp_data`, `MM`, and
/// `qq`) can be updated using a particular time step in
/// UpdateDiscretizedTimeLcp(), resulting in a non-impulsive problem
/// formulation. In that case, the multibody dynamics equations *are*
/// discretized, as described in UpdateDiscretizedTimeLcp().
/// @note If you really do wish to solve an impact problem, you should use
/// SolveImpactProblem() instead.
/// @param problem_data the constraint problem data.
/// @param[out] mlcp_to_lcp_data a pointer to a valid MlcpToLcpData object;
/// the caller must ensure that this pointer remains valid through
/// the constraint solution process.
/// @param[out] MM a pointer to a matrix that will contain the parts of the
/// LCP matrix not dependent upon the time step on return.
/// @param[out] qq a pointer to a vector that will contain the parts of the
/// LCP vector not dependent upon the time step on return.
/// @pre `mlcp_to_lcp_data`, `MM`, and `qq` are non-null on entry.
/// @see UpdateDiscretizedTimeLcp()
static void ConstructBaseDiscretizedTimeLcp(
const ConstraintVelProblemData<T>& problem_data,
MlcpToLcpData* mlcp_to_lcp_data,
MatrixX<T>* MM,
VectorX<T>* qq);
/// Updates the time-discretization of the LCP initially computed in
/// ConstructBaseDiscretizedTimeLcp() using the problem data and time step
/// `h`. Solving the resulting pure LCP yields non-impulsive constraint forces
/// that can be obtained from PopulatePackedConstraintForcesFromLcpSolution().
/// @param problem_data the constraint problem data.
/// @param[out] mlcp_to_lcp_data a pointer to a valid MlcpToLcpData object;
/// the caller must ensure that this pointer remains valid through
/// the constraint solution process.
/// @param[out] a the vector corresponding to the MLCP vector `a`, on return.
/// @param[out] MM a pointer to the updated LCP matrix on return.
/// @param[out] qq a pointer to the updated LCP vector on return.
/// @pre `mlcp_to_lcp_data`, `a`, `MM`, and `qq` are non-null on entry.
/// @see ConstructBaseDiscretizedTimeLcp()
static void UpdateDiscretizedTimeLcp(
const ConstraintVelProblemData<T>& problem_data,
double h,
MlcpToLcpData* mlcp_to_lcp_data,
VectorX<T>* a,
MatrixX<T>* MM,
VectorX<T>* qq);
/// Solves the impact problem described above.
/// @param problem_data The data used to compute the impulsive constraint
/// forces.
/// @param cf The computed impulsive forces, on return, in a packed storage
/// format. The first `nc` elements of `cf` correspond to the
/// magnitudes of the contact impulses applied along the normals of
/// the `nc` contact points. The next elements of `cf`
/// correspond to the frictional impulses along the `r` spanning
/// directions at each point of contact. The first `r`
/// values (after the initial `nc` elements) correspond to the first
/// contact, the next `r` values correspond to the second contact,
/// etc. The next `ℓ` values of `cf` correspond to the impulsive
/// forces applied to enforce unilateral constraint functions. The
/// final `b` values of `cf` correspond to the forces applied to
/// enforce generic bilateral constraints. This packed storage
/// format can be turned into more useful representations through
/// ComputeGeneralizedForceFromConstraintForces() and
/// CalcContactForcesInContactFrames(). `cf` will be resized as
/// necessary.
/// @pre Constraint data has been computed.
/// @throws std::runtime_error if the constraint forces cannot be computed
/// (due to, e.g., the effects of roundoff error in attempting to
/// solve a complementarity problem); in such cases, it is
/// recommended to increase regularization and attempt again.
/// @throws std::logic_error if `cf` is null.
void SolveImpactProblem(const ConstraintVelProblemData<T>& problem_data,
VectorX<T>* cf) const;
/// Populates the packed constraint force vector from the solution to the
/// linear complementarity problem (LCP) constructed using
/// ConstructBaseDiscretizedTimeLcp() and UpdateDiscretizedTimeLcp().
/// @param problem_data the constraint problem data.
/// @param mlcp_to_lcp_data a reference to a MlcpToLcpData object.
/// @param zz the solution to the LCP resulting from
/// UpdateDiscretizedTimeLcp().
/// @param a the vector `a` output from UpdateDiscretizedTimeLcp().
/// @param dt the time step used to discretize the problem.
/// @param[out] cf the constraint forces, on return. The first `nc` elements
/// of `cf` correspond to the magnitudes of the contact forces applied
/// along the normals of the `nc` contact points. The next elements of
/// `cf` correspond to the frictional forces along the `r` spanning
/// directions at each point of contact. The first `r` values (after
/// the initial `nc` elements) correspond to the first contact, the
/// next `r` values correspond to the second contact, etc. The next
/// `ℓ` values of `cf` correspond to the impulsive forces applied to
/// enforce unilateral constraint functions. The final `b` values of
/// `cf` correspond to the forces applied to enforce generic bilateral
/// constraints. This packed storage format can be turned into more
/// useful representations through
/// ComputeGeneralizedForceFromConstraintForces() and
/// CalcContactForcesInContactFrames().
/// @pre cf is non-null.
static void PopulatePackedConstraintForcesFromLcpSolution(
const ConstraintVelProblemData<T>& problem_data,
const MlcpToLcpData& mlcp_to_lcp_data,
const VectorX<T>& zz,
const VectorX<T>& a,
double dt,
VectorX<T>* cf);
//@}
/// Solves the appropriate constraint problem at the acceleration level.
/// @param problem_data The data used to compute the constraint forces.
/// @param cf The computed constraint forces, on return, in a packed storage
/// format. The first `nc` elements of `cf` correspond to the
/// magnitudes of the contact forces applied along the normals of
/// the `nc` contact points. The next elements of `cf`
/// correspond to the frictional forces along the `r` spanning
/// directions at each non-sliding point of contact. The first `r`
/// values (after the initial `nc` elements) correspond to the first
/// non-sliding contact, the next `r` values correspond to the
/// second non-sliding contact, etc. The next `ℓ` values of `cf`
/// correspond to the forces applied to enforce generic unilateral
/// constraints. The final `b` values of `cf` correspond to the
/// forces applied to enforce generic bilateral constraints. This
/// packed storage format can be turned into more useful
/// representations through
/// ComputeGeneralizedForceFromConstraintForces() and
/// CalcContactForcesInContactFrames(). `cf` will be resized as
/// necessary.
/// @pre Constraint data has been computed.
/// @throws std::runtime_error if the constraint forces cannot be computed
/// (due to, e.g., an "inconsistent" rigid contact configuration).
/// @throws std::logic_error if `cf` is null.
void SolveConstraintProblem(const ConstraintAccelProblemData<T>& problem_data,
VectorX<T>* cf) const;
/// Computes the generalized force on the system from the constraint forces
/// given in packed storage.
/// @param problem_data The data used to compute the contact forces.
/// @param cf The computed constraint forces, in the packed storage
/// format described in documentation for SolveConstraintProblem.
/// @param[out] generalized_force The generalized force acting on the system
/// from the total constraint wrench is stored here, on return.
/// This method will resize `generalized_force` as necessary. The
/// indices of `generalized_force` will exactly match the indices
/// of `problem_data.f`.
/// @throws std::logic_error if `generalized_force` is null or `cf`
/// vector is incorrectly sized.
static void ComputeGeneralizedForceFromConstraintForces(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& cf,
VectorX<T>* generalized_force);
/// Computes the generalized force on the system from the constraint forces
/// given in packed storage.
/// @param problem_data The data used to compute the contact forces.
/// @param cf The computed constraint forces, in the packed storage
/// format described in documentation for
/// PopulatePackedConstraintForcesFromLcpSolution().
/// @param[out] generalized_force The generalized force acting on the system
/// from the total constraint wrench is stored here, on return.
/// This method will resize `generalized_force` as necessary. The
/// indices of `generalized_force` will exactly match the indices
/// of `problem_data.f`.
/// @throws std::logic_error if `generalized_force` is null or `cf`
/// vector is incorrectly sized.
static void ComputeGeneralizedForceFromConstraintForces(
const ConstraintVelProblemData<T>& problem_data,
const VectorX<T>& cf,
VectorX<T>* generalized_force);
/// Computes the system generalized acceleration due to both external forces
/// and constraint forces.
/// @param problem_data The acceleration-level constraint data.
/// @param cf The computed constraint forces, in the packed storage
/// format described in documentation for SolveConstraintProblem.
/// @param[out] generalized_acceleration The generalized acceleration, on
/// return.
/// @throws std::logic_error if @p generalized_acceleration is null or
/// @p cf vector is incorrectly sized.
static void ComputeGeneralizedAcceleration(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& cf,
VectorX<T>* generalized_acceleration) {
ComputeGeneralizedAccelerationFromConstraintForces(
problem_data, cf, generalized_acceleration);
(*generalized_acceleration) += problem_data.solve_inertia(
problem_data.tau);
}
/// Computes a first-order approximation of generalized acceleration due
/// *only* to constraint forces.
/// @param problem_data The velocity-level constraint data.
/// @param cf The computed constraint forces, in the packed storage
/// format described in documentation for SolveConstraintProblem.
/// @param v The system generalized velocity at time t.
/// @param dt The discretization time constant (i.e., the "time step" for
/// simulations) used to take the system's generalized velocities
/// from time t to time t + `dt`.
/// @param[out] generalized_acceleration The generalized acceleration, on
/// return. The original will be resized (if necessary) and
/// overwritten.
/// @warning This method uses the method `problem_data.solve_inertia()` in
/// order to compute `v(t+dt)`, so the computational demands may
/// be significant.
/// @throws std::logic_error if `generalized_acceleration` is null or
/// `cf` vector is incorrectly sized.
/// @pre `dt` is positive.
static void ComputeGeneralizedAcceleration(
const ConstraintVelProblemData<T>& problem_data,
const VectorX<T>& v,
const VectorX<T>& cf,
double dt,
VectorX<T>* generalized_acceleration);
/// Computes the system generalized acceleration due *only* to constraint
/// forces.
/// @param cf The computed constraint forces, in the packed storage
/// format described in documentation for SolveConstraintProblem.
/// @throws std::logic_error if @p generalized_acceleration is null or
/// @p cf vector is incorrectly sized.
static void ComputeGeneralizedAccelerationFromConstraintForces(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& cf,
VectorX<T>* generalized_acceleration);
/// Computes the system generalized acceleration due *only* to constraint
/// forces.
/// @param cf The computed constraint forces, in the packed storage
/// format described in documentation for SolveConstraintProblem.
/// @throws std::logic_error if @p generalized_acceleration is null or
/// @p cf vector is incorrectly sized.
static void ComputeGeneralizedAccelerationFromConstraintForces(
const ConstraintVelProblemData<T>& problem_data,
const VectorX<T>& cf,
VectorX<T>* generalized_acceleration);
/// Computes the change to the system generalized velocity from constraint
/// impulses.
/// @param cf The computed constraint impulses, in the packed storage
/// format described in documentation for SolveImpactProblem.
/// @throws std::logic_error if `generalized_delta_v` is null or
/// `cf` vector is incorrectly sized.
static void ComputeGeneralizedVelocityChange(
const ConstraintVelProblemData<T>& problem_data,
const VectorX<T>& cf,
VectorX<T>* generalized_delta_v);
/// Gets the contact forces expressed in each contact frame *for 2D contact
/// problems* from the "packed" solution returned by SolveConstraintProblem().
/// @param cf the output from SolveConstraintProblem()
/// @param problem_data the problem data input to SolveConstraintProblem()
/// @param contact_frames the contact frames corresponding to the contacts.
/// The first column of each matrix should give the contact normal,
/// while the second column gives a contact tangent. For sliding
/// contacts, the contact tangent should point along the direction of
/// sliding. For non-sliding contacts, the tangent direction should be
/// that used to determine `problem_data.F`. All vectors should be
/// expressed in the global frame.
/// @param[out] contact_forces a non-null vector of a doublet of values, where
/// the iᵗʰ element represents the force along each basis
/// vector in the iᵗʰ contact frame.
/// @throws std::logic_error if `contact_forces` is null, if
/// `contact_forces` is not empty, if `cf` is not the
/// proper size, if the number of tangent directions is not one per
/// non-sliding contact (indicating that the contact problem might not
/// be 2D), if the number of contact frames is not equal to the number
/// of contacts, or if a contact frame does not appear to be
/// orthonormal.
/// @note On return, the contact force at the iᵗʰ contact point expressed
/// in the world frame is `contact_frames[i]` * `contact_forces[i]`.
static void CalcContactForcesInContactFrames(
const VectorX<T>& cf,
const ConstraintAccelProblemData<T>& problem_data,
const std::vector<Matrix2<T>>& contact_frames,
std::vector<Vector2<T>>* contact_forces);
/// Gets the contact forces expressed in each contact frame *for 2D contact
/// problems* from a "packed" solution returned by, e.g.,
/// SolveImpactProblem(). If the constraint forces are impulsive, the contact
/// forces are impulsive (with units of Ns); similarly, if the constraint
/// forces are non-impulsive, the contact forces will be non-impulsive (with
/// units of N).
/// @param cf the constraint forces in packed format.
/// @param problem_data the problem data input to SolveImpactProblem()
/// @param contact_frames the contact frames corresponding to the contacts.
/// The first column of each matrix should give the contact normal,
/// while the second column gives a contact tangent (specifically, the
/// tangent direction used to determine `problem_data.F`). All
/// vectors should be expressed in the global frame.
/// @param[out] contact_forces a non-null vector of a doublet of values,
/// where the iᵗʰ element represents the force along
/// each basis vector in the iᵗʰ contact frame.
/// @throws std::logic_error if `contact_forces` is null, if
/// `contact_forces` is not empty, if `cf` is not the
/// proper size, if the number of tangent directions is not one per
/// contact (indicating that the contact problem might not be 2D), if
/// the number of contact frames is not equal to the number
/// of contacts, or if a contact frame does not appear to be
/// orthonormal.
/// @note On return, the contact force at the iᵗʰ contact point expressed
/// in the world frame is `contact_frames[i]` * `contact_forces[i]`.
static void CalcContactForcesInContactFrames(
const VectorX<T>& cf,
const ConstraintVelProblemData<T>& problem_data,
const std::vector<Matrix2<T>>& contact_frames,
std::vector<Vector2<T>>* contact_forces);
private:
static void PopulatePackedConstraintForcesFromLcpSolution(
const ConstraintVelProblemData<T>& problem_data,
const MlcpToLcpData& mlcp_to_lcp_data,
const VectorX<T>& zz,
const VectorX<T>& a,
VectorX<T>* cf);
static void ConstructLinearEquationSolversForMlcp(
const ConstraintVelProblemData<T>& problem_data,
MlcpToLcpData* mlcp_to_lcp_data);
void FormAndSolveConstraintLcp(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& trunc_neg_invA_a,
VectorX<T>* cf) const;
void FormAndSolveConstraintLinearSystem(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& trunc_neg_invA_a,
VectorX<T>* cf) const;
static void CheckAccelConstraintMatrix(
const ConstraintAccelProblemData<T>& problem_data,
const MatrixX<T>& MM);
static void CheckVelConstraintMatrix(
const ConstraintVelProblemData<T>& problem_data,
const MatrixX<T>& MM);
// Computes a constraint space compliance matrix A⋅M⁻¹⋅Bᵀ, where A ∈ ℝᵃˣᵐ
// (realized here using an operator) and B ∈ ℝᵇˣᵐ are both Jacobian matrices
// and M⁻¹ ∈ ℝᵐˣᵐ is the inverse of the generalized inertia matrix. Note that
// mixing types of constraints is explicitly allowed. Aborts if A_iM_BT is
// not of size a × b.
static void ComputeConstraintSpaceComplianceMatrix(
std::function<VectorX<T>(const VectorX<T>&)> A_mult,
int a,
const MatrixX<T>& M_inv_BT,
Eigen::Ref<MatrixX<T>>);
// Computes the matrix M⁻¹⋅Gᵀ, G ∈ ℝᵐˣⁿ is a constraint Jacobian matrix
// (realized here using an operator) and M⁻¹ ∈ ℝⁿˣⁿ is the inverse of the
// generalized inertia matrix. Resizes iM_GT as necessary.
static void ComputeInverseInertiaTimesGT(
std::function<MatrixX<T>(const MatrixX<T>&)> M_inv_mult,
std::function<VectorX<T>(const VectorX<T>&)> G_transpose_mult,
int m,
MatrixX<T>* iM_GT);
static void FormImpactingConstraintLcp(
const ConstraintVelProblemData<T>& problem_data,
const VectorX<T>& invA_a,
MatrixX<T>* MM, VectorX<T>* qq);
static void FormSustainedConstraintLcp(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& invA_a,
MatrixX<T>* MM, VectorX<T>* qq);
static void FormSustainedConstraintLinearSystem(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& invA_a,
MatrixX<T>* MM, VectorX<T>* qq);
template <typename ProblemData>
static void DetermineNewPartialInertiaSolveOperator(
const ProblemData* problem_data,
int num_generalized_velocities,
const Eigen::CompleteOrthogonalDecomposition<MatrixX<T>>* delassus_QTZ,
std::function<MatrixX<T>(const MatrixX<T>&)>* A_solve);
template <typename ProblemData>
static void DetermineNewFullInertiaSolveOperator(
const ProblemData* problem_data,
int num_generalized_velocities,
const Eigen::CompleteOrthogonalDecomposition<MatrixX<T>>* delassus_QTZ,
std::function<MatrixX<T>(const MatrixX<T>&)>* A_solve);
template <typename ProblemData>
static ProblemData* UpdateProblemDataForUnilateralConstraints(
const ProblemData& problem_data,
std::function<const MatrixX<T>(const MatrixX<T>&)> modified_inertia_solve,
int gv_dim,
ProblemData* modified_problem_data);
drake::solvers::MobyLCPSolver<T> lcp_;
};
// Given a matrix A of blocks consisting of generalized inertia (M) and the
// Jacobian of bilaterals constraints (G):
// A ≡ | M -Gᵀ |
// | G 0 |
// this function sets a function pointer that computes X for X = A⁻¹ | B | and
// | 0 |
// given B for the case where X will be premultiplied by some matrix | R 0 |,
// where R is an arbitrary matrix. This odd operation is relatively common, and
// optimizing for this case allows us to skip some expensive matrix arithmetic.
// @param num_generalized_velocities The dimension of the system generalized
// velocities.
// @param problem_data The constraint problem data.
// @param delassus_QTZ The factorization of the Delassus Matrix GM⁻¹Gᵀ,
// where G is the constraint Jacobian corresponding to the
// independent constraints.
// @param[out] A_solve The operator for solving AX = B, on return.
template <typename T>
template <typename ProblemData>
void ConstraintSolver<T>::DetermineNewPartialInertiaSolveOperator(
const ProblemData* problem_data,
int num_generalized_velocities,
const Eigen::CompleteOrthogonalDecomposition<MatrixX<T>>* delassus_QTZ,
std::function<MatrixX<T>(const MatrixX<T>&)>* A_solve) {
const int num_eq_constraints = problem_data->kG.size();
*A_solve = [problem_data, delassus_QTZ, num_eq_constraints,
num_generalized_velocities](const MatrixX<T>& X) -> MatrixX<T> {
// ************************************************************************
// See DetermineNewFullInertiaSolveOperator() for block inversion formula.
// ************************************************************************
// Set the result matrix.
const int C_rows = num_generalized_velocities;
const int E_cols = num_eq_constraints;
MatrixX<T> result(C_rows + E_cols, X.cols());
// Begin computation of components of C (upper left hand block of inverse
// of A): compute M⁻¹ X
const MatrixX<T> iM_X = problem_data->solve_inertia(X);
// Compute G M⁻¹ X
MatrixX<T> G_iM_X(E_cols, X.cols());
for (int i = 0; i < X.cols(); ++i)
G_iM_X.col(i) = problem_data->G_mult(iM_X.col(i));
// Compute (GM⁻¹Gᵀ)⁻¹GM⁻¹X
const MatrixX<T> Del_G_iM_X = delassus_QTZ->solve(G_iM_X);
// Compute Gᵀ(GM⁻¹Gᵀ)⁻¹GM⁻¹X
const MatrixX<T> GT_Del_G_iM_X = problem_data->G_transpose_mult(Del_G_iM_X);
// Compute M⁻¹Gᵀ(GM⁻¹Gᵀ)⁻¹GM⁻¹X
const MatrixX<T> iM_GT_Del_G_iM_X = problem_data->solve_inertia(
GT_Del_G_iM_X);
// Compute the result
result = iM_X - iM_GT_Del_G_iM_X;
return result;
};
}
// Given a matrix A of blocks consisting of generalized inertia (M) and the
// Jacobian of bilaterals constraints (G):
// A ≡ | M -Gᵀ |
// | G 0 |
// this function sets a function pointer that computes X for AX = B, given B.
// @param num_generalized_velocities The dimension of the system generalized
// velocities.
// @param problem_data The constraint problem data.
// @param delassus_QTZ The factorization of the Delassus Matrix GM⁻¹Gᵀ,
// where G is the constraint Jacobian corresponding to the
// independent constraints.
// @param[out] A_solve The operator for solving AX = B, on return.
template <typename T>
template <typename ProblemData>
void ConstraintSolver<T>::DetermineNewFullInertiaSolveOperator(
const ProblemData* problem_data,
int num_generalized_velocities,
const Eigen::CompleteOrthogonalDecomposition<MatrixX<T>>* delassus_QTZ,
std::function<MatrixX<T>(const MatrixX<T>&)>* A_solve) {
// Get the number of equality constraints.
const int num_eq_constraints = problem_data->kG.size();
*A_solve = [problem_data, delassus_QTZ, num_eq_constraints,
num_generalized_velocities](const MatrixX<T>& B) -> MatrixX<T> {
// From a block matrix inversion,
// | M -Gᵀ |⁻¹ | Y | = | C E || Y | = | CY + EZ |
// | G 0 | | Z | | -Eᵀ F || Z | | -EᵀY + FZ |
// where E ≡ M⁻¹Gᵀ(GM⁻¹Gᵀ)⁻¹
// C ≡ M⁻¹ - M⁻¹Gᵀ(GM⁻¹Gᵀ)⁻¹GM⁻¹ = M⁻¹ - M⁻¹GᵀE
// -Eᵀ ≡ -(GM⁻¹Gᵀ)⁻¹GM⁻¹
// F ≡ (GM⁻¹Gᵀ)⁻¹
// B ≡ | Y |
// | Z |
// Set the result matrix (X).
const int C_rows = num_generalized_velocities;
const int E_cols = num_eq_constraints;
MatrixX<T> X(C_rows + E_cols, B.cols());
// Name the blocks of B and X.
const auto Y = B.topRows(C_rows);
const auto Z = B.bottomRows(B.rows() - C_rows);
auto X_top = X.topRows(C_rows);
auto X_bot = X.bottomRows(X.rows() - C_rows);
// 1. Begin computation of components of C.
// Compute M⁻¹ Y
const MatrixX<T> iM_Y = problem_data->solve_inertia(Y);
// Compute G M⁻¹ Y
MatrixX<T> G_iM_Y(E_cols, Y.cols());
for (int i = 0; i < Y.cols(); ++i)
G_iM_Y.col(i) = problem_data->G_mult(iM_Y.col(i));
// Compute (GM⁻¹Gᵀ)⁻¹GM⁻¹Y
const MatrixX<T> Del_G_iM_Y = delassus_QTZ->solve(G_iM_Y);
// Compute Gᵀ(GM⁻¹Gᵀ)⁻¹GM⁻¹Y
const MatrixX<T> GT_Del_G_iM_Y = problem_data->G_transpose_mult(Del_G_iM_Y);
// Compute M⁻¹Gᵀ(GM⁻¹Gᵀ)⁻¹GM⁻¹Y
const MatrixX<T> iM_GT_Del_G_iM_Y = problem_data->solve_inertia(
GT_Del_G_iM_Y);
// 2. Begin computation of components of E
// Compute (GM⁻¹Gᵀ)⁻¹Z
const MatrixX<T> Del_Z = delassus_QTZ->solve(Z);
// Compute Gᵀ(GM⁻¹Gᵀ)⁻¹Z
const MatrixX<T> GT_Del_Z = problem_data->G_transpose_mult(Del_Z);
// Compute M⁻¹Gᵀ(GM⁻¹Gᵀ)⁻¹Z = EZ
const MatrixX<T> iM_GT_Del_Z = problem_data->solve_inertia(GT_Del_Z);
// Set the top block of the result.
X_top = iM_Y - iM_GT_Del_G_iM_Y + iM_GT_Del_Z;
// Set the bottom block of the result.
X_bot = delassus_QTZ->solve(Z) - Del_G_iM_Y;
return X;
};
}
template <typename T>
template <typename ProblemData>
ProblemData* ConstraintSolver<T>::UpdateProblemDataForUnilateralConstraints(
const ProblemData& problem_data,
std::function<const MatrixX<T>(const MatrixX<T>&)> modified_inertia_solve,
int gv_dim,
ProblemData* modified_problem_data) {
// Verify that the modified problem data points to something.
DRAKE_DEMAND(modified_problem_data);
// Get the number of equality constraints.
const int num_eq_constraints = problem_data.kG.size();
// Construct a new problem data.
if (num_eq_constraints == 0) {
// Just point to the original problem data.
return const_cast<ProblemData*>(&problem_data);
} else {
// Alias the modified problem data so that we don't need to change its
// pointer.
ProblemData& new_data = *modified_problem_data;
// Copy most of the data unchanged.
new_data = problem_data;
// Construct zero functions.
auto zero_fn = [](const VectorX<T>&) -> VectorX<T> {
return VectorX<T>(0);
};
auto zero_gv_dim_fn = [gv_dim](const VectorX<T>&) -> VectorX<T> {
return VectorX<T>::Zero(gv_dim);
};
// Remove the bilateral constraints.
new_data.kG.resize(0);
new_data.G_mult = zero_fn;
new_data.G_transpose_mult = zero_gv_dim_fn;
// Update the inertia function pointer.
new_data.solve_inertia = modified_inertia_solve;
return &new_data;
}
}
// Forms and solves the system of linear equations used to compute the
// accelerations during ODE evaluations, as an alternative to the linear
// complementarity problem formulation. If all constraints are known to be
// active, this method will provide the correct solution rapidly.
// @param problem_data The constraint data formulated at the acceleration
// level.
// @param trunc_neg_invA_a The first ngc elements of -A⁻¹a, where
// A ≡ | M Gᵀ | (M is the generalized inertia matrix and G is the
// | G 0 | Jacobian matrix for the bilateral constraints)
// and a ≡ | -τ | (τ [tau] and kG are defined in `problem_data`).
// | kG |
// @param[out] cf The unilateral constraint forces, on return, in a packed
// storage format. The first `nc` elements of `cf` correspond to
// the magnitudes of the contact forces applied along the normals
// of the `nc` contact points. The next elements of `cf`
// correspond to the frictional forces along the `r` spanning
// directions at each non-sliding point of contact. The first `r`
// values (after the initial `nc` elements) correspond to the
// first non-sliding contact, the next `r` values correspond to
// the second non-sliding contact, etc. The next `ℓ` values of
// `cf` correspond to the forces applied to enforce generic
// unilateral constraints. `cf` will be resized as necessary.
template <class T>
void ConstraintSolver<T>::FormAndSolveConstraintLinearSystem(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& trunc_neg_invA_a,
VectorX<T>* cf) const {
using std::max;
using std::abs;
DRAKE_DEMAND(cf);
// Alias problem data.
const std::vector<int>& sliding_contacts = problem_data.sliding_contacts;
const std::vector<int>& non_sliding_contacts =
problem_data.non_sliding_contacts;
// Get numbers of friction directions and types of contacts.
const int num_sliding = sliding_contacts.size();
const int num_non_sliding = non_sliding_contacts.size();
const int num_contacts = num_sliding + num_non_sliding;
const int num_spanning_vectors = std::accumulate(problem_data.r.begin(),
problem_data.r.end(), 0);
const int num_limits = problem_data.kL.size();
const int num_eq_constraints = problem_data.kG.size();
// Initialize contact force vector.
cf->resize(num_contacts + num_spanning_vectors + num_limits +
num_eq_constraints);
// Using Equations (f) and (g) from the comments in
// FormAndSolveConstraintLcp() and defining C as the upper left block of A⁻¹,
// the linear system is defined as MM*z + qq = 0, where:
//
// MM ≡ | NC(Nᵀ-μQᵀ) NCDᵀ NCLᵀ |
// | DC(Nᵀ-μQᵀ) DCDᵀ DCLᵀ |
// | LC(Nᵀ-μQᵀ) LCDᵀ LCLᵀ |
//
// qq ≡ | kᴺ + |N 0|A⁻¹a |
// | kᴰ + |D 0|A⁻¹a |
// | kᴸ + |L 0|A⁻¹a |
// @TODO(edrumwri): Consider checking whether or not the constraints are
// satisfied to a user-specified tolerance; a set of constraint equations that
// are dependent upon time (e.g., prescribed motion constraints) might not be
// fully satisfiable.
// Set up the linear system.
MatrixX<T> MM;
VectorX<T> qq;
FormSustainedConstraintLinearSystem(problem_data, trunc_neg_invA_a, &MM, &qq);
// Solve the linear system.
Eigen::CompleteOrthogonalDecomposition<MatrixX<T>> MM_QTZ(MM);
cf->head(qq.size()) = MM_QTZ.solve(-qq);
}
// Forms and solves the linear complementarity problem used to compute the
// accelerations during ODE evaluations, as an alternative to the linear
// system problem formulation. In contrast to
// FormAndSolveConstraintLinearSystem(), this approach does not require the
// active constraints to be known a priori. Significantly more computation is
// required, however.
// @sa FormAndSolveConstraintLinearSystem for descriptions of parameters.
template <typename T>
void ConstraintSolver<T>::FormAndSolveConstraintLcp(
const ConstraintAccelProblemData<T>& problem_data,
const VectorX<T>& trunc_neg_invA_a,
VectorX<T>* cf) const {
using std::max;
using std::abs;
DRAKE_DEMAND(cf);
// Alias problem data.
const std::vector<int>& sliding_contacts = problem_data.sliding_contacts;
const std::vector<int>& non_sliding_contacts =
problem_data.non_sliding_contacts;
// Get numbers of friction directions and types of contacts.
const int num_sliding = sliding_contacts.size();
const int num_non_sliding = non_sliding_contacts.size();
const int num_contacts = num_sliding + num_non_sliding;
const int num_spanning_vectors = std::accumulate(problem_data.r.begin(),
problem_data.r.end(), 0);
const int nk = num_spanning_vectors * 2;
const int num_limits = problem_data.kL.size();
const int num_eq_constraints = problem_data.kG.size();
// Initialize contact force vector.
cf->resize(num_contacts + num_spanning_vectors + num_limits +
num_eq_constraints);
// The constraint problem is a mixed linear complementarity problem of the
// form:
// (a) Au + Xv + a = 0
// (b) Yu + Bv + b ≥ 0
// (c) v ≥ 0
// (d) vᵀ(b + Yu + Bv) = 0
// where u are "free" variables. If the matrix A is nonsingular, u can be
// solved for:
// (e) u = -A⁻¹ (a + Xv)
// allowing the mixed LCP to be converted to a "pure" LCP (qq, MM) by:
// (f) qq = b - YA⁻¹a
// (g) MM = B - YA⁻¹X
// Our mixed linear complementarity problem takes the specific form:
// (1) | M -Gᵀ -(Nᵀ-μQᵀ) -Dᵀ 0 -Lᵀ | | v̇ | + | -f | = | 0 |
// | G 0 0 0 0 0 | | fG | + | kᴳ | = | 0 |
// | N 0 0 0 0 0 | | fN | + | kᴺ | = | α |
// | D 0 0 0 E 0 | | fD | + | kᴰ | = | β |
// | 0 0 μ -Eᵀ 0 0 | | λ | + | 0 | = | γ |
// | L 0 0 0 0 0 | | fL | + | kᴸ | = | δ |
// (2) 0 ≤ fN ⊥ α ≥ 0
// (3) 0 ≤ fD ⊥ β ≥ 0
// (4) 0 ≤ λ ⊥ γ ≥ 0
// (5) 0 ≤ fL ⊥ δ ≥ 0
// --------------------------------------------------------------------------
// Converting the MLCP to a pure LCP:
// --------------------------------------------------------------------------
// From the notation above in Equations (a)-(d):
// A ≡ | M -Gᵀ| a ≡ | -f | X ≡ |-(Nᵀ-μQᵀ) -Dᵀ 0 -Lᵀ |
// | G 0 | | kᴳ | | 0 0 0 0 |
//