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