1 /* -------------------------------------------------------------------------- *
2 * Simbody(tm) *
3 * -------------------------------------------------------------------------- *
4 * This is part of the SimTK biosimulation toolkit originating from *
5 * Simbios, the NIH National Center for Physics-Based Simulation of *
6 * Biological Structures at Stanford, funded under the NIH Roadmap for *
7 * Medical Research, grant U54 GM072970. See https://simtk.org/home/simbody. *
8 * *
9 * Portions copyright (c) 2010-14 Stanford University and the Authors. *
10 * Authors: Michael Sherman *
11 * Contributors: *
12 * *
13 * Licensed under the Apache License, Version 2.0 (the "License"); you may *
14 * not use this file except in compliance with the License. You may obtain a *
15 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
16 * *
17 * Unless required by applicable law or agreed to in writing, software *
18 * distributed under the License is distributed on an "AS IS" BASIS, *
19 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
20 * See the License for the specific language governing permissions and *
21 * limitations under the License. *
22 * -------------------------------------------------------------------------- */
23
24 #include "SimTKmath.h"
25 #include "simbody/internal/MobilizedBody.h"
26 #include "simbody/internal/MultibodySystem.h"
27 #include "simbody/internal/SimbodyMatterSubsystem.h"
28 #include "simbody/internal/Assembler.h"
29 #include "simbody/internal/AssemblyCondition.h"
30 #include <map>
31 #include <iostream>
32 using std::cout; using std::endl;
33
34 using namespace SimTK;
35
36 //------------------------------------------------------------------------------
37 // EXCEPTIONS
38 //------------------------------------------------------------------------------
39 class Assembler::AssembleFailed : public Exception::Base {
40 public:
AssembleFailed(const char * fn,int ln,const char * why,Real tolAchieved,Real tolRequired)41 AssembleFailed
42 (const char* fn, int ln, const char* why,
43 Real tolAchieved, Real tolRequired)
44 : Base(fn,ln)
45 {
46 setMessage(
47 "Method Assembler::assemble() failed because:\n" + String(why)
48 + "\nAssembly error tolerance achieved: "
49 + String(tolAchieved) + " required: " + String(tolRequired)
50 + ".");
51 }
52 };
53 class Assembler::TrackFailed : public Exception::Base {
54 public:
TrackFailed(const char * fn,int ln,const char * why,Real tolAchieved,Real tolRequired)55 TrackFailed
56 (const char* fn, int ln, const char* why,
57 Real tolAchieved, Real tolRequired)
58 : Base(fn,ln)
59 {
60 setMessage(
61 "Method Assembler::track() failed because:\n" + String(why)
62 + "\nAssembly error tolerance achieved: "
63 + String(tolAchieved) + " required: " + String(tolRequired)
64 + ".");
65 }
66 };
67
68 //------------------------------------------------------------------------------
69 // BUILT IN CONSTRAINTS
70 //------------------------------------------------------------------------------
71 // This is the Assembly Condition representing the System's built-in
72 // Constraints. Only Constraints that are currently enabled are included.
73 // This class provides an efficient implementation for treating these
74 // Constraints either as an assembly requirement or an assembly goal.
75 namespace { // this class is local to this file
76 class BuiltInConstraints : public AssemblyCondition {
77 public:
BuiltInConstraints()78 BuiltInConstraints()
79 : AssemblyCondition("System Constraints") {}
80
81 // Note that we have turned off quaternions so the number of q error
82 // slots in the State includes only real holonomic constraint equations.
getNumErrors(const State & s) const83 int getNumErrors(const State& s) const override {return s.getNQErr();}
84
85 // Return the system holonomic constraint errors as found in the State.
calcErrors(const State & state,Vector & err) const86 int calcErrors(const State& state, Vector& err) const override {
87 err = state.getQErr();
88 return 0;
89 }
90
91 // The Jacobian of the holonomic constraint errors is Pq=PN^-1. We can get
92 // that analytically from Simbody but we might have to strip out some
93 // of the columns if we aren't using all the q's.
calcErrorJacobian(const State & state,Matrix & jacobian) const94 int calcErrorJacobian(const State& state, Matrix& jacobian) const override {
95 const SimbodyMatterSubsystem& matter = getMatterSubsystem();
96 const int np = getNumFreeQs();
97 const int nq = state.getNQ();
98
99 jacobian.resize(state.getNQErr(), np); // ok cuz no quaternions
100
101 if (np == nq) {
102 // Jacobian is already the right shape.
103 matter.calcPq(state, jacobian);
104 } else {
105 Matrix fullJac(state.getNQErr(), nq);
106 matter.calcPq(state, fullJac);
107 // Extract just the columns corresponding to free Qs
108 for (Assembler::FreeQIndex fx(0); fx < np; ++fx)
109 jacobian(fx) = fullJac(getQIndexOfFreeQ(fx));
110 }
111 return 0;
112 }
113
114 // Goal is qerr^2/2 (the /2 makes the gradient easier).
calcGoal(const State & state,Real & goal) const115 int calcGoal(const State& state, Real& goal) const override {
116 goal = (~state.getQErr() * state.getQErr()) / 2;
117 return 0;
118 }
119
120 // Gradient is ~(d goal/dq) = ~(~qerr * dqerr/dq) = ~(~qerr*Pq)
121 // = ~Pq qerr. This can be done in O(n+m) time since we can calculate
122 // the matrix-vector product ~Pq*v in O(n+m) time, where
123 // n=#q's and m=# constraint equations.
calcGoalGradient(const State & state,Vector & grad) const124 int calcGoalGradient(const State& state, Vector& grad) const override {
125 const SimbodyMatterSubsystem& matter = getMatterSubsystem();
126 const int np = getNumFreeQs();
127 const int nq = state.getNQ();
128
129 grad.resize(np);
130
131 if (np == nq) {
132 // Nothing locked; analytic gradient is the right size
133 matter.multiplyByPqTranspose(state, state.getQErr(), grad);
134 } else {
135 Vector fullGrad(nq);
136 matter.multiplyByPqTranspose(state, state.getQErr(), fullGrad);
137 // Extract just the entries corresponding to free Qs
138 for (Assembler::FreeQIndex fx(0); fx < np; ++fx)
139 grad[fx] = fullGrad[getQIndexOfFreeQ(fx)];
140 }
141
142 //cout << "built in grad=" << grad << endl;
143 return 0;
144 }
145
146 private:
147 };
148 } // end anonymous namespace
149
150
151 //------------------------------------------------------------------------------
152 // ASSEMBLER SYSTEM
153 //------------------------------------------------------------------------------
154
155 // This class defines the objective function which is passed to the Optimizer.
156 class Assembler::AssemblerSystem : public OptimizerSystem {
157 public:
AssemblerSystem(Assembler & assembler)158 AssemblerSystem(Assembler& assembler)
159 : OptimizerSystem(assembler.getNumFreeQs()), assembler(assembler)
160 { getSystem().realize(getInternalState(), Stage::Time);
161 resetStats(); }
162
163 // Convenient interface to objective function.
calcCurrentGoal() const164 Real calcCurrentGoal() const {
165 Real val;
166 const int status = objectiveFunc(getFreeQsFromInternalState(),true,val);
167 SimTK_ERRCHK1_ALWAYS(status==0,
168 "AssemblerSystem::calcCurrentGoal()",
169 "objectiveFunc() returned status %d.", status);
170 return val;
171 }
172
173 // Convenient interface to gradient of objective function.
calcCurrentGradient() const174 Vector calcCurrentGradient() const {
175 Vector grad(getNumFreeQs());
176 const int status =
177 gradientFunc(getFreeQsFromInternalState(),true,grad);
178 SimTK_ERRCHK1_ALWAYS(status==0,
179 "AssemblerSystem::calcCurrentGradient()",
180 "gradientFunc() returned status %d.", status);
181 return grad;
182 }
183
184 // Convenient interface to assembly constraint error function.
calcCurrentErrors() const185 Vector calcCurrentErrors() const {
186 Vector errs(getNumEqualityConstraints());
187 const int status =
188 constraintFunc(getFreeQsFromInternalState(), true, errs);
189 SimTK_ERRCHK1_ALWAYS(status==0,
190 "AssemblerSystem::calcCurrentErrors()",
191 "constraintFunc() returned status %d.", status);
192 return errs;
193 }
194
195 // Convenient interface to Jacobian of assembly constraint error function.
calcCurrentJacobian() const196 Matrix calcCurrentJacobian() const {
197 Matrix jac(getNumEqualityConstraints(), getNumFreeQs());
198 const int status =
199 constraintJacobian(getFreeQsFromInternalState(), true, jac);
200 SimTK_ERRCHK1_ALWAYS(status==0,
201 "AssemblerSystem::calcCurrentJacobian()",
202 "constraintJacobian() returned status %d.", status);
203 return jac;
204 }
205
206 // Return the value of the objective to be minimized when the freeQs
207 // have the values given by the parameters.
objectiveFunc(const Vector & parameters,bool new_parameters,Real & objectiveValue) const208 int objectiveFunc(const Vector& parameters,
209 bool new_parameters,
210 Real& objectiveValue) const override
211 { ++nEvalObjective;
212
213 if (new_parameters)
214 setInternalStateFromFreeQs(parameters);
215
216 objectiveValue = 0;
217 for (unsigned i=0; i < assembler.goals.size(); ++i) {
218 AssemblyConditionIndex goalIx = assembler.goals[i];
219 const AssemblyCondition& cond = *assembler.conditions[goalIx];
220 Real goalValue;
221 const int stat = cond.calcGoal(getInternalState(), goalValue);
222 if (stat != 0)
223 return stat;
224 SimTK_ERRCHK2_ALWAYS(goalValue >= 0,
225 "AssemblerSystem::objectiveFunc()",
226 "calcGoal() method of assembly condition %s returned a"
227 " negative or non-finite value %g.", cond.getName(), goalValue);
228 objectiveValue += assembler.weights[goalIx] * goalValue;
229 }
230
231 //static int count = 0;
232 //cout << " " << count++ << " obj=" << objectiveValue << endl;
233 return 0;
234 }
235
236 class NumGradientFunc : public Differentiator::GradientFunction {
237 public:
NumGradientFunc(Assembler & assembler,const Array_<AssemblyConditionIndex> & numGoals)238 NumGradientFunc(Assembler& assembler,
239 const Array_<AssemblyConditionIndex>& numGoals)
240 : Differentiator::GradientFunction(assembler.getNumFreeQs()),
241 assembler(assembler), numGoals(numGoals) {}
242
243 // This is the function that gets differentiated. We want it to
244 // return fy = sum( w[i] * goal[i] ) for each of the goals that needs
245 // a numerical gradient. Then we can calculate all of them at once.
f(const Vector & y,Real & fy) const246 int f(const Vector& y, Real& fy) const override {
247 assembler.setInternalStateFromFreeQs(y);
248 fy = 0;
249 for (unsigned i=0; i < numGoals.size(); ++i) {
250 AssemblyConditionIndex goalIx = numGoals[i];
251 const AssemblyCondition& cond =
252 *assembler.conditions[goalIx];
253 Real goalValue;
254 const int stat = cond.calcGoal(assembler.getInternalState(),
255 goalValue);
256 if (stat != 0)
257 return stat;
258 fy += assembler.weights[goalIx] * goalValue;
259 }
260 return 0;
261 }
262 private:
263 Assembler& assembler;
264 const Array_<AssemblyConditionIndex>& numGoals;
265 };
266
gradientFunc(const Vector & parameters,bool new_parameters,Vector & gradient) const267 int gradientFunc(const Vector& parameters,
268 bool new_parameters,
269 Vector& gradient) const override
270 { SimTK_ASSERT2_ALWAYS(gradient.size() == getNumFreeQs(),
271 "AssemblySystem::gradientFunc(): expected gradient vector of"
272 " size %d but got %d; this is likely a problem with the optimizer"
273 " which is required to allocate the right amount of space.",
274 getNumFreeQs(), gradient.size());
275
276 ++nEvalGradient;
277
278 if (new_parameters)
279 setInternalStateFromFreeQs(parameters);
280
281 for (unsigned i=0; i < assembler.reporters.size(); ++i)
282 assembler.reporters[i]->handleEvent(getInternalState());
283
284 // This will record the indices of any goals we encounter that can't
285 // provide their own gradients; we'll handle them all together at
286 // the end.
287 Array_<AssemblyConditionIndex> needNumericalGradient;
288
289 gradient = 0;
290 Vector tmpGradient(gradient.size());
291 for (unsigned i=0; i < assembler.goals.size(); ++i) {
292 AssemblyConditionIndex goalIx = assembler.goals[i];
293 const AssemblyCondition& cond = *assembler.conditions[goalIx];
294 const int stat = (assembler.forceNumericalGradient
295 ? -1
296 : cond.calcGoalGradient(getInternalState(),
297 tmpGradient));
298 if (stat == -1) {
299 needNumericalGradient.push_back(goalIx);
300 continue;
301 }
302 if (stat != 0)
303 return stat;
304
305 gradient += assembler.weights[goalIx] * tmpGradient;
306 }
307
308 if (!needNumericalGradient.empty()) {
309 //cout << "Need numerical gradient for "
310 // << needNumericalGradient.size() << " goals." << endl;
311 NumGradientFunc numGoals(assembler, needNumericalGradient);
312 // Essential to use central difference here so that the
313 // approximate gradient is actually zero at the optimum
314 // solution, otherwise IpOpt won't converge.
315 Differentiator gradNumGoals
316 (numGoals,Differentiator::CentralDifference);
317 // weights are already included here
318 gradient += gradNumGoals.calcGradient(getFreeQsFromInternalState());
319
320 nEvalObjective += gradNumGoals.getNumCallsToUserFunction();
321 }
322
323 //cout << "Grad=" << gradient << endl;
324
325 return 0;
326 }
327
328
329 // Return the errors in the hard assembly error conditions.
constraintFunc(const Vector & parameters,bool new_parameters,Vector & qerrs) const330 int constraintFunc(const Vector& parameters,
331 bool new_parameters,
332 Vector& qerrs) const override
333 { ++nEvalConstraints;
334
335 if (new_parameters)
336 setInternalStateFromFreeQs(parameters);
337
338 int nxtEqn = 0;
339 for (unsigned i=0; i < assembler.errors.size(); ++i) {
340 AssemblyConditionIndex consIx = assembler.errors[i];
341 const AssemblyCondition& cond = *assembler.conditions[consIx];
342 const int m = cond.getNumErrors(getInternalState());
343 int stat = cond.calcErrors(getInternalState(), qerrs(nxtEqn,m));
344 if (stat != 0)
345 return stat;
346 nxtEqn += m;
347 }
348
349 //cout << " err=" << qerrs << endl;
350
351 return 0;
352 }
353
354 class NumJacobianFunc : public Differentiator::JacobianFunction {
355 public:
NumJacobianFunc(Assembler & assembler,const Array_<AssemblyConditionIndex> & numCons,const Array_<int> & nErrorEqns,int totalNEqns)356 NumJacobianFunc(Assembler& assembler,
357 const Array_<AssemblyConditionIndex>& numCons,
358 const Array_<int>& nErrorEqns,
359 int totalNEqns)
360 : Differentiator::JacobianFunction
361 (totalNEqns, assembler.getNumFreeQs()),
362 assembler(assembler), numCons(numCons), nEqns(nErrorEqns),
363 totalNEqns(totalNEqns)
364 { assert(numCons.size() == nEqns.size()); }
365
366 // This is the function that gets differentiated. We want it to
367 // return fy = [ err[i] ] for each of the assembly constraint
368 // conditions that needs a numerical gradient. Then we can calculate
369 // all their Jacobians at once.
f(const Vector & y,Vector & fy) const370 int f(const Vector& y, Vector& fy) const override {
371 assert(y.size() == assembler.getNumFreeQs());
372 assert(fy.size() == totalNEqns);
373
374 assembler.setInternalStateFromFreeQs(y);
375 int nxtSlot = 0;
376 for (unsigned i=0; i < numCons.size(); ++i) {
377 AssemblyConditionIndex consIx = numCons[i];
378 const AssemblyCondition& cond =
379 *assembler.conditions[consIx];
380 const int stat = cond.calcErrors
381 (assembler.getInternalState(), fy(nxtSlot, nEqns[i]));
382 if (stat != 0)
383 return stat;
384 nxtSlot += nEqns[i];
385 }
386
387 assert(nxtSlot == totalNEqns); // must use all slots
388 return 0;
389 }
390 private:
391 Assembler& assembler;
392 const Array_<AssemblyConditionIndex>& numCons;
393 const Array_<int>& nEqns;
394 const int totalNEqns;
395 };
396
constraintJacobian(const Vector & parameters,bool new_parameters,Matrix & J) const397 int constraintJacobian(const Vector& parameters,
398 bool new_parameters,
399 Matrix& J) const override
400 { ++nEvalJacobian;
401
402 if (new_parameters)
403 setInternalStateFromFreeQs(parameters);
404 for (unsigned i=0; i < assembler.reporters.size(); ++i)
405 assembler.reporters[i]->handleEvent(getInternalState());
406
407 assert(J.nrow() == getNumEqualityConstraints());
408 assert(J.ncol() == getNumFreeQs());
409
410 const int n = getNumFreeQs();
411
412 // This will record the indices of any constraints we encounter that
413 // can't provide their own gradients; we'll handle them all together
414 // at the end.
415 Array_<AssemblyConditionIndex> needNumericalJacobian;
416 Array_<int> firstEqn;
417 Array_<int> nEqns;
418 int needy = 0;
419
420 int nxtEqn = 0;
421 for (unsigned i=0; i < assembler.errors.size(); ++i) {
422 AssemblyConditionIndex consIx = assembler.errors[i];
423 const AssemblyCondition& cond = *assembler.conditions[consIx];
424 const int m = cond.getNumErrors(getInternalState());
425 const int stat = (assembler.forceNumericalJacobian
426 ? -1
427 : cond.calcErrorJacobian(getInternalState(),
428 J(nxtEqn,0,m,n)));
429 if (stat == -1) {
430 needNumericalJacobian.push_back(consIx);
431 firstEqn.push_back(nxtEqn);
432 nEqns.push_back(m);
433 needy += m;
434 } else if (stat != 0)
435 return stat;
436 nxtEqn += m;
437 }
438
439 if (!needNumericalJacobian.empty()) {
440 //cout << "Need numerical Jacobian for "
441 // << needNumericalJacobian.size() << " constraints." << endl;
442 NumJacobianFunc numCons(assembler, needNumericalJacobian,
443 nEqns, needy);
444 // Forward difference should be fine here, unlike for the
445 // gradient because we converge on the solution value
446 // rather than the derivative norm.
447 Differentiator jacNumCons(numCons);
448 Matrix numJ = jacNumCons.calcJacobian(getFreeQsFromInternalState());
449 nEvalConstraints += jacNumCons.getNumCallsToUserFunction();
450
451 // Fill in the missing rows.
452 int nxtInNumJ = 0;
453 for (unsigned i=0; i < needNumericalJacobian.size(); ++i) {
454 J(firstEqn[i],0,nEqns[i],n) = numJ(nxtInNumJ,0,nEqns[i],n);
455 nxtInNumJ += nEqns[i];
456 }
457
458 }
459
460 //cout << "J=" << J;
461 return 0;
462 }
463
getNumObjectiveEvals() const464 int getNumObjectiveEvals() const {return nEvalObjective;}
getNumConstraintEvals() const465 int getNumConstraintEvals() const {return nEvalConstraints;}
getNumGradientEvals() const466 int getNumGradientEvals() const {return nEvalGradient;}
getNumJacobianEvals() const467 int getNumJacobianEvals() const {return nEvalJacobian;}
468
resetStats() const469 void resetStats() const { // stats are mutable
470 nEvalObjective=nEvalConstraints=nEvalGradient=nEvalJacobian=0;
471 }
472 private:
getSystem() const473 const MultibodySystem& getSystem() const
474 { return assembler.getMultibodySystem(); }
getInternalState() const475 const State& getInternalState() const
476 { return assembler.getInternalState(); }
getNumFreeQs() const477 int getNumFreeQs() const {return assembler.getNumFreeQs();}
getQIndexOfFreeQ(FreeQIndex fx) const478 QIndex getQIndexOfFreeQ(FreeQIndex fx) const
479 { return assembler.getQIndexOfFreeQ(fx);}
getFreeQIndexOfQ(QIndex qx) const480 FreeQIndex getFreeQIndexOfQ(QIndex qx) const
481 { return assembler.getFreeQIndexOfQ(qx); }
getFreeQsFromInternalState() const482 Vector getFreeQsFromInternalState() const
483 { return assembler.getFreeQsFromInternalState(); }
setInternalStateFromFreeQs(const Vector & freeQs) const484 void setInternalStateFromFreeQs(const Vector& freeQs) const
485 { assembler.setInternalStateFromFreeQs(freeQs); }
486
487 Assembler& assembler;
488
489 mutable int nEvalObjective;
490 mutable int nEvalConstraints;
491 mutable int nEvalGradient;
492 mutable int nEvalJacobian;
493 };
494
495
496
497 //------------------------------------------------------------------------------
498 // ASSEMBLER
499 //------------------------------------------------------------------------------
Assembler(const MultibodySystem & system)500 Assembler::Assembler(const MultibodySystem& system)
501 : system(system), accuracy(0), tolerance(0), // i.e., 1e-3, 1e-4
502 forceNumericalGradient(false), forceNumericalJacobian(false),
503 useRMSErrorNorm(false), alreadyInitialized(false),
504 asmSys(0), optimizer(0), nAssemblySteps(0), nInitializations(0)
505 {
506 const SimbodyMatterSubsystem& matter = system.getMatterSubsystem();
507 matter.convertToEulerAngles(system.getDefaultState(),
508 internalState);
509 system.realizeModel(internalState);
510
511 // Make sure the System's Constraints are always present; this sets the
512 // weight to Infinity which makes us treat this as an assembly error
513 // rather than merely a goal; that can be changed by the user.
514 systemConstraints = adoptAssemblyError(new BuiltInConstraints());
515 }
516
517
~Assembler()518 Assembler::~Assembler() {
519 uninitialize();
520 // To be polite, and to show off, delete in reverse order of allocation
521 // (this is easier on the heap system).
522 Array_<AssemblyCondition*,AssemblyConditionIndex>::reverse_iterator p;
523 for (p = conditions.rbegin(); p != conditions.rend(); ++p)
524 delete *p;
525 }
526
527
528 AssemblyConditionIndex Assembler::
adoptAssemblyError(AssemblyCondition * p)529 adoptAssemblyError(AssemblyCondition* p) {
530 return adoptAssemblyGoal(p, Infinity);
531 }
532
533 AssemblyConditionIndex Assembler::
adoptAssemblyGoal(AssemblyCondition * p,Real weight)534 adoptAssemblyGoal(AssemblyCondition* p, Real weight) {
535 SimTK_ERRCHK_ALWAYS(p != 0, "Assembler::adoptAssemblyGoal()",
536 "Null assembly condition pointer.");
537 SimTK_ERRCHK1_ALWAYS(weight >= 0, "Assembler::adoptAssemblyGoal()",
538 "Illegal assembly goal weight %g.", weight);
539
540 uninitialize();
541
542 const AssemblyConditionIndex acx(conditions.size());
543 assert(conditions.size() == weights.size());
544 p->setAssembler(*this, acx);
545 conditions.push_back(p);
546 weights.push_back(weight);
547 return acx;
548 }
549
550
initialize() const551 void Assembler::initialize() const {
552 if (alreadyInitialized)
553 return;
554
555 ++nInitializations;
556
557 Array_<QIndex> toBeLocked;
558 reinitializeWithExtraQsLocked(toBeLocked);
559 alreadyInitialized = true;
560 return;
561
562 /*NOTREACHED*/
563 // TODO: This currently unused code would allow the Assembler to lock out
564 // variables that it thinks aren't worth bothering with. Needs real-world
565 // testing and probably some override options. And should there be a
566 // desperation mode where all variables are tried if we can't assemble
567 // with some of them removed?
568 Vector grad = abs(asmSys->calcCurrentGradient());
569 Real maxGrad = 0;
570 for (FreeQIndex fx(0); fx < grad.size(); ++fx)
571 maxGrad = std::max(maxGrad, grad[fx]);
572 if (maxGrad == 0) // no q does anything; probably no objective
573 maxGrad = Infinity; // no q will be kept for gradient purposes
574
575 Matrix jac = asmSys->calcCurrentJacobian();
576 Vector colNorm(getNumFreeQs());
577 Real maxJac = 0;
578 for (FreeQIndex fx(0); fx < grad.size(); ++fx) {
579 colNorm[fx] = jac(fx).norm();
580 maxJac = std::max(maxJac, colNorm[fx]);
581 }
582 if (maxJac == 0) // no q does anything; probably no constraints
583 maxJac = Infinity; // no q will be kept for Jacobian purposes
584
585 const Real QTol = SqrtEps;
586 const Real minGradAllowed = maxGrad*QTol;
587 const Real minJacAllowed = maxJac*QTol;
588 for (FreeQIndex fx(0); fx < grad.size(); ++fx)
589 if (grad[fx] < minGradAllowed && colNorm[fx] < minJacAllowed)
590 toBeLocked.push_back(getQIndexOfFreeQ(fx));
591
592 if (toBeLocked.size()) {
593 cout << "Reinitializing with these q's locked:\n";
594 cout << toBeLocked << endl;
595 reinitializeWithExtraQsLocked(toBeLocked);
596 alreadyInitialized = true;
597 }
598 }
599
reinitializeWithExtraQsLocked(const Array_<QIndex> & toBeLocked) const600 void Assembler::reinitializeWithExtraQsLocked
601 (const Array_<QIndex>& toBeLocked) const {
602 uninitialize();
603
604 const SimbodyMatterSubsystem& matter = getMatterSubsystem();
605
606 system.realize(internalState, Stage::Instance);
607 const int nq = internalState.getNQ();
608
609 // Initialized locked q's to all those that the user locked, plus
610 // the extras.
611 q2FreeQ.resize(nq); // no q has an associated freeQ at this point
612
613 extraQsLocked = toBeLocked;
614 lockedQs.insert(extraQsLocked.begin(), extraQsLocked.end());
615
616 // Find all the mobilizers that have prescribed positions and lock
617 // all their q's.
618 for (MobodIndex mbx(0); mbx < matter.getNumBodies(); ++mbx) {
619 const MobilizedBody& mobod = matter.getMobilizedBody(mbx);
620 if (mobod.getQMotionMethod(internalState) == Motion::Free)
621 continue;
622 const QIndex q0 = mobod.getFirstQIndex(internalState);
623 const int nq = mobod.getNumQ(internalState);
624 for (int i=0; i<nq; ++i)
625 lockedQs.insert(QIndex(q0+i));
626 }
627
628 // Lock all the q's for locked mobilizers.
629 for (LockedMobilizers::const_iterator p = userLockedMobilizers.begin();
630 p != userLockedMobilizers.end(); ++p)
631 {
632 const MobilizedBody& mobod = matter.getMobilizedBody(*p);
633 const QIndex q0 = mobod.getFirstQIndex(internalState);
634 const int nq = mobod.getNumQ(internalState);
635 for (int i=0; i<nq; ++i)
636 lockedQs.insert(QIndex(q0+i));
637 }
638
639 // Next add in all the q's that were individually locked.
640 for (LockedQs::const_iterator p = userLockedQs.begin();
641 p != userLockedQs.end(); ++p)
642 {
643 const MobilizedBodyIndex mbx = p->first;
644 const MobilizedBody& mobod = matter.getMobilizedBody(mbx);
645 const QIndex q0 = mobod.getFirstQIndex(internalState);
646 const int nq = mobod.getNumQ(internalState);
647
648 const QSet& qs = p->second;
649 for (QSet::const_iterator qp = qs.begin(); qp != qs.end(); ++qp) {
650 const MobilizerQIndex q = *qp;
651 SimTK_ERRCHK3_ALWAYS(q < nq, "Assembler::initialize()",
652 "An attempt was made to lock q[%d] (numbering from 0) of"
653 " mobilized body %d but that mobilizer has only %d q(s).",
654 (int)q, (int)mbx, nq);
655 lockedQs.insert(QIndex(q0 + q));
656 }
657 }
658
659 const int nlockedq = (int)lockedQs.size();
660 const int nfreeq = std::max(nq - nlockedq, 0);
661
662 // Find all the free q's and fill in the maps from FreeQIndex
663 // to full QIndex, and from QIndex to FreeQIndex.
664 freeQ2Q.resize(nfreeq);
665 if (nlockedq) {
666 FreeQIndex nxtFree(0);
667 for (QIndex qx(0); qx < nq; ++qx) {
668 if (lockedQs.find(qx) == lockedQs.end()) {
669 q2FreeQ[qx] = nxtFree;
670 freeQ2Q[nxtFree++] = qx;
671 }
672 }
673 } else // all q's are free
674 for (QIndex qx(0); qx < nq; ++qx) {
675 q2FreeQ[qx] = FreeQIndex(qx);
676 freeQ2Q[FreeQIndex(qx)] = qx;
677 }
678
679 // If *any* of the free q's have a restricted range, we'll provide a
680 // (lower,upper) range value for *all* of them, using (-Inf,Inf) by
681 // default. It might turn out that all the restricted q's are currently
682 // locked so we'll hold off allocating the bounds arrays until we
683 // actually see a freeQ that's restricted.
684 bool foundAnyRestricted = false;
685 for (RestrictedQs::const_iterator p = userRestrictedQs.begin();
686 p != userRestrictedQs.end(); ++p)
687 {
688 const MobilizedBodyIndex mbx = p->first;
689 const MobilizedBody& mobod = matter.getMobilizedBody(mbx);
690 const QIndex q0 = mobod.getFirstQIndex(internalState);
691 const int nq = mobod.getNumQ(internalState);
692
693 // Run through each of the q's that was restricted for this mobilizer.
694 const QRanges& qranges = p->second;
695 for (QRanges::const_iterator qr = qranges.begin();
696 qr != qranges.end(); ++qr)
697 {
698 const MobilizerQIndex q = qr->first;
699 const Vec2& r = qr->second;
700
701 SimTK_ERRCHK3_ALWAYS(q < nq, "Assembler::initialize()",
702 "An attempt was made to restrict q[%d] (numbering from 0) of"
703 " mobilized body %d but that mobilizer has only %d q(s).",
704 (int)q, (int)mbx, nq);
705
706 const QIndex qx = QIndex(q0 + q);
707 const FreeQIndex fx = q2FreeQ[qx];
708 if (!fx.isValid())
709 continue; // this q is locked; no need to restrict it
710
711 if (!foundAnyRestricted) { // this is first one
712 lower.resize(nfreeq); lower = -Infinity; // allocate and initialize
713 upper.resize(nfreeq); upper = Infinity;
714 foundAnyRestricted = true;
715 }
716 lower[fx] = r[0];
717 upper[fx] = r[1];
718 }
719 }
720
721 system.realize(internalState, Stage::Position);
722
723 // Set up the lists of errors and goals based on the weights
724 // currently assigned to assembly conditions, and initialize the
725 // conditions as they are added.
726 errors.clear(); nTermsPerError.clear(); goals.clear();
727 assert(conditions.size() == weights.size());
728
729 int nErrorTerms = 0;
730 for (AssemblyConditionIndex acx(0); acx < conditions.size(); ++acx) {
731 assert(conditions[acx] != 0 && weights[acx] >= 0);
732 if (weights[acx] == 0)
733 continue;
734 conditions[acx]->initializeCondition();
735 if (weights[acx] == Infinity) {
736 const int n = conditions[acx]->getNumErrors(internalState);
737 if (n == 0)
738 continue; // never mind; no constraint errors
739 nErrorTerms += n;
740 errors.push_back(acx);
741 nTermsPerError.push_back(n);
742 } else
743 goals.push_back(acx);
744 }
745
746 // Allocate an AssemblerSystem which is in the form of an objective
747 // function for the SimTK::Optimizer class.
748 asmSys = new AssemblerSystem(*const_cast<Assembler*>(this));
749 asmSys->setNumEqualityConstraints(nErrorTerms);
750 if (lower.size())
751 asmSys->setParameterLimits(lower, upper);
752
753
754 // Optimizer will choose LBFGS for unconstrained (or just bounds-constrained)
755 // problems, InteriorPoint for constrained problems.
756 optimizer = new Optimizer(*asmSys
757 //,InteriorPoint
758 //,LBFGS
759 //,LBFGSB
760 );
761 //optimizer->useNumericalGradient(true); // of goals
762 //optimizer->useNumericalJacobian(true); // of errors
763
764 // The size of the limited memory history affects the various optimizers
765 // differently; I found this to be a good compromise. Smaller or larger
766 // can both cause degraded performance.
767 optimizer->setLimitedMemoryHistory(50);
768 optimizer->setDiagnosticsLevel(0);
769 optimizer->setMaxIterations(3000);
770 }
771
772 // Clean up all the mutable stuff; don't touch any user-set members.
uninitialize() const773 void Assembler::uninitialize() const {
774 if (!alreadyInitialized)
775 return;
776
777 alreadyInitialized = false;
778 nAssemblySteps = 0;
779 delete optimizer; optimizer = 0;
780 delete asmSys; asmSys = 0;
781 // Run through conditions in reverse order when uninitializing them;
782 // watch out: negative index not allowed so it is easier to use a reverse
783 // iterators.
784 Array_<AssemblyCondition*,AssemblyConditionIndex>::const_reverse_iterator p;
785 for (p = conditions.crbegin(); p != conditions.crend(); ++p)
786 (*p)->uninitializeCondition();
787 goals.clear();
788 nTermsPerError.clear();
789 errors.clear();
790 lower.clear(); upper.clear();
791 freeQ2Q.clear();
792 q2FreeQ.clear();
793 lockedQs.clear();
794 extraQsLocked.clear();
795 }
796
calcCurrentGoal() const797 Real Assembler::calcCurrentGoal() const {
798 initialize();
799 return asmSys->calcCurrentGoal();
800 }
801
802 // Return norm of constraint errors, using the appropriate norm.
calcCurrentErrorNorm() const803 Real Assembler::calcCurrentErrorNorm() const {
804 initialize();
805 const int nc = asmSys->getNumEqualityConstraints();
806 if (nc == 0) return 0;
807 const Vector errs = asmSys->calcCurrentErrors();
808 return useRMSErrorNorm
809 ? std::sqrt(~errs*errs / errs.size()) // RMS
810 : max(abs(errs)); // infinity norm
811 }
812
assemble()813 Real Assembler::assemble() {
814 initialize();
815 ++nAssemblySteps;
816
817 const Real initialErrorNorm = calcCurrentErrorNorm();
818 const Real initialGoalValue = calcCurrentGoal(); // squared, >=0
819
820 // Short circuit if this is already good enough.
821 if (initialErrorNorm <= getErrorToleranceInUse()) {
822 // Already feasible. Is the goal good enough too? Note that we don't
823 // know much about the goal units, and "accuracy" is a unitless
824 // fraction. So we're going to use "tolerance" here. We do know that
825 // the goal is squared but error tolerance is not. So this is likely
826 // to be a very strict test if the error tolerance is tight!
827 if (initialGoalValue <= square(getErrorToleranceInUse())) {
828 for (unsigned i=0; i < reporters.size(); ++i)
829 reporters[i]->handleEvent(internalState);
830 return initialGoalValue;
831 }
832 // Not short circuiting.
833 }
834
835
836 const int nfreeq = getNumFreeQs();
837 const int nqerr = internalState.getNQErr();
838
839
840 // std::cout << "assemble(): initial tol/goal is "
841 // << calcCurrentError() << "/" << calcCurrentGoal() << std::endl;
842
843 for (unsigned i=0; i < reporters.size(); ++i)
844 reporters[i]->handleEvent(internalState);
845
846 // First step: satisfy prescribed motion (exactly).
847 system.realize(internalState, Stage::Time);
848 system.prescribeQ(internalState);
849
850 // Optimize
851
852 // Save the starting solution so we can restore it if the optimizer makes
853 // it worse, which IpOpt has been observed to do.
854 const Vector initialFreeQs = getFreeQsFromInternalState();
855 Vector freeQs = initialFreeQs;
856 // Use tolerance if there are any error conditions, else accuracy.
857 optimizer->setConvergenceTolerance(getAccuracyInUse());
858 optimizer->setConstraintTolerance(getErrorToleranceInUse());
859 try
860 { optimizer->optimize(freeQs); }
861 catch (const std::exception& e)
862 { setInternalStateFromFreeQs(freeQs); // realizes to Stage::Position
863
864 // Sometimes the optimizer will throw an exception after it has
865 // already achieved a winning solution. One message that comes up
866 // is "Ipopt: Restoration failed (status -2)". We'll ignore that as
867 // long as we have a good result. Otherwise we'll re-throw here.
868 if (calcCurrentErrorNorm() > getErrorToleranceInUse()) {
869 SimTK_THROW3(AssembleFailed,
870 (String("Optimizer failed with message: ") + e.what()).c_str(),
871 calcCurrentErrorNorm(), getErrorToleranceInUse());
872 }
873 }
874
875 // This will ensure that the internalState has its q's set to match the
876 // parameters.
877 setInternalStateFromFreeQs(freeQs);
878
879 for (unsigned i=0; i < reporters.size(); ++i)
880 reporters[i]->handleEvent(internalState);
881
882 Real tolAchieved = calcCurrentErrorNorm();
883 Real goalAchieved = calcCurrentGoal();
884
885 // See if we should just revert to the initial solution.
886 if ( initialErrorNorm <= getErrorToleranceInUse() // started feasible
887 && goalAchieved > initialGoalValue) // objective got worse
888 {
889 setInternalStateFromFreeQs(initialFreeQs);
890 tolAchieved = initialErrorNorm;
891 goalAchieved = initialGoalValue;
892 }
893
894 if (tolAchieved > getErrorToleranceInUse())
895 SimTK_THROW3(AssembleFailed,
896 "Unable to achieve required assembly error tolerance.",
897 tolAchieved, getErrorToleranceInUse());
898
899 //std::cout << "assemble(): final tol/goal is "
900 // << calcCurrentError() << "/" << calcCurrentGoal() << std::endl;
901
902 return goalAchieved;
903 }
904
905
track(Real frameTime)906 Real Assembler::track(Real frameTime) {
907 initialize();
908 ++nAssemblySteps;
909
910 if (frameTime >= 0 && internalState.getTime() != frameTime) {
911 internalState.setTime(frameTime);
912 system.realize(internalState, Stage::Time);
913 // Satisfy prescribed motion (exactly).
914 system.prescribeQ(internalState);
915 system.realize(internalState, Stage::Position);
916 }
917
918 const Real initialErrorNorm = calcCurrentErrorNorm();
919 const Real initialGoalValue = calcCurrentGoal(); // squared!
920
921 // Short circuit if this is already good enough.
922 if (initialErrorNorm <= getErrorToleranceInUse()) {
923 // See comments in assemble() for more info.
924 if (initialGoalValue <= square(getErrorToleranceInUse())) {
925 for (unsigned i=0; i < reporters.size(); ++i)
926 reporters[i]->handleEvent(internalState);
927 return initialGoalValue;
928 }
929 // Not short circuiting.
930 }
931
932
933 const int nfreeq = getNumFreeQs();
934 const int nqerr = internalState.getNQErr();
935
936
937 // std::cout << "track(): initial tol/goal is "
938 // << calcCurrentError() << "/" << calcCurrentGoal() << std::endl;
939
940 // Optimize
941 Vector freeQs = getFreeQsFromInternalState();
942 optimizer->setConvergenceTolerance(getAccuracyInUse());
943 optimizer->setConstraintTolerance(getErrorToleranceInUse());
944 try
945 { optimizer->optimize(freeQs); }
946 catch (const std::exception& e)
947 { setInternalStateFromFreeQs(freeQs); // realizes to Stage::Position
948
949 // Sometimes the optimizer will throw an exception after it has
950 // already achieved a winning solution. One message that comes up
951 // is "Ipopt: Restoration failed (status -2)". We'll ignore that as
952 // long as we have a good result. Otherwise we'll re-throw here.
953 if (calcCurrentErrorNorm() > getErrorToleranceInUse()) {
954 SimTK_THROW3(TrackFailed,
955 (String("Optimizer failed with message: ") + e.what()).c_str(),
956 calcCurrentErrorNorm(), getErrorToleranceInUse());
957 }
958 }
959
960 // This will ensure that the internalState has its q's set to match the
961 // parameters.
962 // This will ensure that the internalState has its q's set to match the
963 // parameters.
964 setInternalStateFromFreeQs(freeQs);
965
966 for (unsigned i=0; i < reporters.size(); ++i)
967 reporters[i]->handleEvent(internalState);
968
969 const Real tolAchieved = calcCurrentErrorNorm();
970 if (tolAchieved > getErrorToleranceInUse())
971 SimTK_THROW3(TrackFailed,
972 "Unable to achieve required assembly error tolerance.",
973 tolAchieved, getErrorToleranceInUse());
974
975 //std::cout << "track(): final tol/goal is "
976 // << calcCurrentError() << "/" << calcCurrentGoal() << std::endl;
977
978 return calcCurrentGoal();
979 }
980
getNumGoalEvals() const981 int Assembler::getNumGoalEvals() const
982 { return asmSys ? asmSys->getNumObjectiveEvals() : 0;}
getNumErrorEvals() const983 int Assembler::getNumErrorEvals() const
984 { return asmSys ? asmSys->getNumConstraintEvals() : 0;}
getNumGoalGradientEvals() const985 int Assembler::getNumGoalGradientEvals() const
986 { return asmSys ? asmSys->getNumGradientEvals() : 0;}
getNumErrorJacobianEvals() const987 int Assembler::getNumErrorJacobianEvals() const
988 { return asmSys ? asmSys->getNumJacobianEvals() : 0;}
getNumAssemblySteps() const989 int Assembler::getNumAssemblySteps() const
990 { return nAssemblySteps; }
getNumInitializations() const991 int Assembler::getNumInitializations() const
992 { return nInitializations; }
resetStats() const993 void Assembler::resetStats() const
994 { if (asmSys) asmSys->resetStats();
995 nAssemblySteps = nInitializations = 0; }
996
997