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) 2009-12 Stanford University and the Authors.        *
10  * Authors: Michael Sherman                                                   *
11  * Contributors: Christopher Bruns                                            *
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 "SimTKcommon.h"
25 
26 #include "simbody/internal/common.h"
27 #include "simbody/internal/MobilizedBody.h"
28 #include "simbody/internal/SimbodyMatterSubsystem.h"
29 #include "simbody/internal/Force_Thermostat.h"
30 
31 #include "ForceImpl.h"
32 
33 namespace SimTK {
34 
35 // Implementation class for Force::Thermostat.
36 class Force::ThermostatImpl : public ForceImpl {
37 public:
ThermostatImpl(const SimbodyMatterSubsystem & matter,Real boltzmannsConstant,Real defBathTemp,Real defRelaxationTime,int defNumExcludedDofs)38     ThermostatImpl(const SimbodyMatterSubsystem& matter,
39                    Real boltzmannsConstant,
40                    Real defBathTemp,
41                    Real defRelaxationTime,
42                    int  defNumExcludedDofs)
43     :   matter(matter), kB(boltzmannsConstant),
44         defaultNumChains(DefaultDefaultNumChains),
45         defaultBathTemp(defBathTemp),
46         defaultRelaxationTime(defRelaxationTime),
47         defaultNumExcludedDofs(defNumExcludedDofs) {}
48 
clone() const49     ThermostatImpl* clone() const override {return new ThermostatImpl(*this);}
dependsOnlyOnPositions() const50     bool dependsOnlyOnPositions() const override {return false;}
51 
52     void calcForce(const State& state, Vector_<SpatialVec>& bodyForces,
53                    Vector_<Vec3>& particleForces, Vector& mobilityForces) const override;
54 
55     // Temperature does not contribute to potential energy.
calcPotentialEnergy(const State & state) const56     Real calcPotentialEnergy(const State& state) const override {return 0;}
57 
58     void realizeTopology(State& state) const override;
59     void realizeModel(State& state) const override;
60     void realizeVelocity(const State& state) const override;
61     void realizeDynamics(const State& state) const override;
62 
63     // Get/update the current number of chains.
getNumChains(const State & s) const64     int getNumChains(const State& s) const {
65         assert(dvNumChains.isValid());
66         return Value<int>::downcast(getForceSubsystem().getDiscreteVariable(s, dvNumChains));
67     }
updNumChains(State & s) const68     int& updNumChains(State& s) const {
69         assert(dvNumChains.isValid());
70         return Value<int>::updDowncast(getForceSubsystem().updDiscreteVariable(s, dvNumChains));
71     }
72 
73     // Get/update the current number of excluded dofs.
getNumExcludedDofs(const State & s) const74     int getNumExcludedDofs(const State& s) const {
75         assert(dvNumExcludedDofs.isValid());
76         return Value<int>::downcast
77             (getForceSubsystem().getDiscreteVariable(s, dvNumExcludedDofs));
78     }
updNumExcludedDofs(State & s) const79     int& updNumExcludedDofs(State& s) const {
80         assert(dvNumExcludedDofs.isValid());
81         return Value<int>::updDowncast
82             (getForceSubsystem().updDiscreteVariable(s, dvNumExcludedDofs));
83     }
84 
85     int getNumThermalDOFs(const State& s) const;
86 
87     // Get the auxiliary continuous state index of the 0'th thermostat state variable z.
getZ0Index(const State & s) const88     ZIndex getZ0Index(const State& s) const {
89         assert(cacheZ0Index.isValid());
90         return Value<ZIndex>::downcast(getForceSubsystem().getCacheEntry(s, cacheZ0Index));
91     }
updZ0Index(State & s) const92     ZIndex& updZ0Index(State& s) const {
93         assert(cacheZ0Index.isValid());
94         return Value<ZIndex>::updDowncast(getForceSubsystem().updCacheEntry(s, cacheZ0Index));
95     }
96 
97     // Get the current bath temperature (i.e., the desired temperature).
getBathTemp(const State & s) const98     Real getBathTemp(const State& s) const {
99         assert(dvBathTemp.isValid());
100         return Value<Real>::downcast(getForceSubsystem().getDiscreteVariable(s, dvBathTemp));
101     }
updBathTemp(State & s) const102     Real& updBathTemp(State& s) const {
103         assert(dvBathTemp.isValid());
104         return Value<Real>::updDowncast(getForceSubsystem().updDiscreteVariable(s, dvBathTemp));
105     }
106 
107     // Get the current bath temperature (i.e., the desired temperature).
getRelaxationTime(const State & s) const108     Real getRelaxationTime(const State& s) const {
109         assert(dvRelaxationTime.isValid());
110         return Value<Real>::downcast(getForceSubsystem().getDiscreteVariable(s, dvRelaxationTime));
111     }
updRelaxationTime(State & s) const112     Real& updRelaxationTime(State& s) const {
113         assert(dvRelaxationTime.isValid());
114         return Value<Real>::updDowncast(getForceSubsystem().updDiscreteVariable(s, dvRelaxationTime));
115     }
116 
117     // Get the calculated momentum M*u (after Stage::Velocity).
getMomentum(const State & s) const118     const Vector& getMomentum(const State& s) const {
119         assert(cacheMomentumIndex.isValid());
120         return Value<Vector>::downcast(getForceSubsystem().getCacheEntry(s, cacheMomentumIndex));
121     }
updMomentum(const State & s) const122     Vector& updMomentum(const State& s) const {
123         assert(cacheMomentumIndex.isValid());
124         return Value<Vector>::updDowncast(getForceSubsystem().updCacheEntry(s, cacheMomentumIndex));
125     }
126 
127     // Get the calculated system kinetic energy ~u*M*u/2 (after Stage::Velocity).
getKE(const State & s) const128     const Real& getKE(const State& s) const {
129         assert(cacheKEIndex.isValid());
130         return Value<Real>::downcast(getForceSubsystem().getCacheEntry(s, cacheKEIndex));
131     }
updKE(const State & s) const132     Real& updKE(const State& s) const {
133         assert(cacheKEIndex.isValid());
134         return Value<Real>::updDowncast(getForceSubsystem().updCacheEntry(s, cacheKEIndex));
135     }
136 
getExternalWork(const State & s) const137     Real getExternalWork(const State& s) const
138     {   return getForceSubsystem().getZ(s)[workZIndex]; }
updExternalWork(State & s) const139     Real& updExternalWork(State& s) const
140     {   return getForceSubsystem().updZ(s)[workZIndex]; }
141 
updWorkZDot(const State & s) const142     Real& updWorkZDot(const State& s) const
143     {   return getForceSubsystem().updZDot(s)[workZIndex];  }
144 
145     Real calcExternalPower(const State& s) const;
146 
147     // Get the current value of one of the thermostat state variables.
getZ(const State & s,int i) const148     Real getZ(const State& s, int i) const {
149         assert(0 <= i && i < 2*getNumChains(s));
150         const ZIndex z0 = getZ0Index(s);
151         return getForceSubsystem().getZ(s)[z0+i];
152     }
updZ(State & s,int i) const153     Real& updZ(State& s, int i) const {
154         assert(0 <= i && i < 2*getNumChains(s));
155         const ZIndex z0 = getZ0Index(s);
156         return getForceSubsystem().updZ(s)[z0+i];
157     }
getZDot(const State & s,int i) const158     Real getZDot(const State& s, int i) const {
159         assert(0 <= i && i < 2*getNumChains(s));
160         const ZIndex z0 = getZ0Index(s);
161         return getForceSubsystem().getZDot(s)[z0+i];
162     }
163     // State is const here because ZDot is a cache entry.
updZDot(const State & s,int i) const164     Real& updZDot(const State& s, int i) const {
165         assert(0 <= i && i < 2*getNumChains(s));
166         const ZIndex z0 = getZ0Index(s);
167         return getForceSubsystem().updZDot(s)[z0+i];
168     }
169 
170     static const int DefaultDefaultNumChains = 3;
171 private:
172     const SimbodyMatterSubsystem& matter;
173     const Real  kB;     // Boltzmann's constant in compatible units
174 
175     // Topology-stage "state" variables.
176     int         defaultNumChains;       // # chains in a new State
177     int         defaultNumExcludedDofs; // # non-thermal rigid body dofs
178     Real        defaultBathTemp;        // bath temperature
179     Real        defaultRelaxationTime;  // relaxation time
180 
181     // These indices are Topology-stage "cache" variables.
182     DiscreteVariableIndex dvNumChains;          // integer
183     DiscreteVariableIndex dvNumExcludedDofs;    // integer
184     DiscreteVariableIndex dvBathTemp;           // Real
185     DiscreteVariableIndex dvRelaxationTime;     // Real
186     CacheEntryIndex       cacheZ0Index;         // ZIndex
187     CacheEntryIndex       cacheMomentumIndex;   // M*u
188     CacheEntryIndex       cacheKEIndex;         // ~u*M*u/2
189     ZIndex                workZIndex;           // power integral
190 
191 friend class Force::Thermostat;
192 };
193 
194 //-------------------------------- Thermostat ----------------------------------
195 //------------------------------------------------------------------------------
196 
197 SimTK_INSERT_DERIVED_HANDLE_DEFINITIONS(Force::Thermostat, Force::ThermostatImpl, Force);
198 
Thermostat(GeneralForceSubsystem & forces,const SimbodyMatterSubsystem & matter,Real boltzmannsConstant,Real bathTemperature,Real relaxationTime,int numExcludedDofs)199 Force::Thermostat::Thermostat
200    (GeneralForceSubsystem& forces, const SimbodyMatterSubsystem& matter,
201     Real boltzmannsConstant, Real bathTemperature, Real relaxationTime,
202     int numExcludedDofs)
203 :   Force(new ThermostatImpl(matter, boltzmannsConstant, bathTemperature,
204                              relaxationTime, numExcludedDofs))
205 {
206     SimTK_APIARGCHECK1_ALWAYS(boltzmannsConstant > 0,
207         "Force::Thermostat","ctor", "Illegal Boltzmann's constant %g.", boltzmannsConstant);
208     SimTK_APIARGCHECK1_ALWAYS(bathTemperature > 0,
209         "Force::Thermostat","ctor", "Illegal bath temperature %g.", bathTemperature);
210     SimTK_APIARGCHECK1_ALWAYS(relaxationTime > 0,
211         "Force::Thermostat","ctor", "Illegal relaxation time %g.", relaxationTime);
212     SimTK_APIARGCHECK1_ALWAYS(0 <= numExcludedDofs && numExcludedDofs <= 6,
213         "Force::Thermostat","ctor",
214         "Illegal number of excluded rigid body dofs %d (must be 0-6).",
215         numExcludedDofs);
216 
217     updImpl().setForceSubsystem(forces, forces.adoptForce(*this));
218 }
219 
220 Force::Thermostat& Force::Thermostat::
setDefaultNumChains(int numChains)221 setDefaultNumChains(int numChains) {
222     SimTK_APIARGCHECK1_ALWAYS(numChains > 0,
223         "Force::Thermostat","setDefaultNumChains",
224         "Illegal number of chains %d.", numChains);
225 
226     getImpl().invalidateTopologyCache();
227     updImpl().defaultNumChains = numChains;
228     return *this;
229 }
230 
231 Force::Thermostat& Force::Thermostat::
setDefaultBathTemperature(Real bathTemperature)232 setDefaultBathTemperature(Real bathTemperature) {
233     SimTK_APIARGCHECK1_ALWAYS(bathTemperature > 0,
234         "Force::Thermostat","setDefaultBathTemperature",
235         "Illegal bath temperature %g.", bathTemperature);
236 
237     getImpl().invalidateTopologyCache();
238     updImpl().defaultBathTemp = bathTemperature;
239     return *this;
240 }
241 
242 Force::Thermostat& Force::Thermostat::
setDefaultRelaxationTime(Real relaxationTime)243 setDefaultRelaxationTime(Real relaxationTime) {
244     SimTK_APIARGCHECK1_ALWAYS(relaxationTime > 0,
245         "Force::Thermostat","setDefaultRelaxationTime",
246         "Illegal bath temperature %g.", relaxationTime);
247 
248     getImpl().invalidateTopologyCache();
249     updImpl().defaultRelaxationTime = relaxationTime;
250     return *this;
251 }
252 
253 Force::Thermostat& Force::Thermostat::
setDefaultNumExcludedDofs(int numExcludedDofs)254 setDefaultNumExcludedDofs(int numExcludedDofs) {
255     SimTK_APIARGCHECK1_ALWAYS(0 <= numExcludedDofs && numExcludedDofs <= 6,
256         "Force::Thermostat","setDefaultNumExcludedDofs",
257         "Illegal number of excluded rigid body dofs %d (must be 0-6).",
258         numExcludedDofs);
259 
260     getImpl().invalidateTopologyCache();
261     updImpl().defaultNumExcludedDofs = numExcludedDofs;
262     return *this;
263 }
264 
getDefaultNumChains() const265 int Force::Thermostat::getDefaultNumChains() const {return getImpl().defaultNumChains;}
getDefaultBathTemperature() const266 Real Force::Thermostat::getDefaultBathTemperature() const {return getImpl().defaultBathTemp;}
getDefaultRelaxationTime() const267 Real Force::Thermostat::getDefaultRelaxationTime() const {return getImpl().defaultRelaxationTime;}
getDefaultNumExcludedDofs() const268 int Force::Thermostat::getDefaultNumExcludedDofs() const {return getImpl().defaultNumExcludedDofs;}
getBoltzmannsConstant() const269 Real Force::Thermostat::getBoltzmannsConstant() const {return getImpl().kB;}
270 
271 const Force::Thermostat& Force::Thermostat::
setNumChains(State & s,int numChains) const272 setNumChains(State& s, int numChains) const {
273     SimTK_APIARGCHECK1_ALWAYS(numChains > 0,
274         "Force::Thermostat","setNumChains",
275         "Illegal number of chains %d.", numChains);
276 
277     getImpl().updNumChains(s) = numChains;
278     return *this;
279 }
280 
281 const Force::Thermostat& Force::Thermostat::
setBathTemperature(State & s,Real bathTemperature) const282 setBathTemperature(State& s, Real bathTemperature) const {
283     SimTK_APIARGCHECK1_ALWAYS(bathTemperature > 0,
284         "Force::Thermostat","setBathTemperature",
285         "Illegal bath temperature %g.", bathTemperature);
286 
287     getImpl().updBathTemp(s) = bathTemperature;
288     return *this;
289 }
290 
291 const Force::Thermostat& Force::Thermostat::
setRelaxationTime(State & s,Real relaxationTime) const292 setRelaxationTime(State& s, Real relaxationTime) const {
293     SimTK_APIARGCHECK1_ALWAYS(relaxationTime > 0,
294         "Force::Thermostat","setRelaxationTime",
295         "Illegal bath temperature %g.", relaxationTime);
296 
297     getImpl().updRelaxationTime(s) = relaxationTime;
298     return *this;
299 }
300 
301 const Force::Thermostat& Force::Thermostat::
setNumExcludedDofs(State & s,int numExcludedDofs) const302 setNumExcludedDofs(State& s, int numExcludedDofs) const {
303     SimTK_APIARGCHECK1_ALWAYS(0 <= numExcludedDofs && numExcludedDofs <= 6,
304         "Force::Thermostat","setNumExcludedDofs",
305         "Illegal number of excluded rigid body dofs %d (must be 0-6).",
306         numExcludedDofs);
307 
308     getImpl().updNumExcludedDofs(s) = numExcludedDofs;
309     return *this;
310 }
311 
getNumChains(const State & s) const312 int Force::Thermostat::getNumChains(const State& s) const {return getImpl().getNumChains(s);}
getBathTemperature(const State & s) const313 Real Force::Thermostat::getBathTemperature(const State& s) const {return getImpl().getBathTemp(s);}
getRelaxationTime(const State & s) const314 Real Force::Thermostat::getRelaxationTime(const State& s) const {return getImpl().getRelaxationTime(s);}
getNumExcludedDofs(const State & s) const315 int Force::Thermostat::getNumExcludedDofs(const State& s) const {return getImpl().getNumExcludedDofs(s);}
316 
initializeChainState(State & s) const317 void Force::Thermostat::initializeChainState(State& s) const {
318     const ThermostatImpl& impl = getImpl();
319     const int nChains = impl.getNumChains(s);
320     for (int i=0; i < 2*nChains; ++i)
321         impl.updZ(s, i) = 0;
322 }
323 
setChainState(State & s,const Vector & z) const324 void Force::Thermostat::setChainState(State& s, const Vector& z) const {
325     const ThermostatImpl& impl = getImpl();
326     const int nChains = impl.getNumChains(s);
327     SimTK_APIARGCHECK2_ALWAYS(z.size() == 2*nChains,
328         "Force::Thermostat", "setChainState",
329         "Number of values supplied (%d) didn't match twice the number of chains %d.",
330         z.size(), nChains);
331     for (int i=0; i < 2*nChains; ++i)
332         impl.updZ(s, i) = z[i];
333 }
334 
getChainState(const State & s) const335 Vector Force::Thermostat::getChainState(const State& s) const {
336     const ThermostatImpl& impl = getImpl();
337     const int nChains = impl.getNumChains(s);
338     Vector out(2*nChains);
339     for (int i=0; i < 2*nChains; ++i)
340         out[i] = impl.getZ(s, i);
341     return out;
342 }
343 
344 // Cost is a divide and two flops, say about 20 flops.
getCurrentTemperature(const State & s) const345 Real Force::Thermostat::getCurrentTemperature(const State& s) const {
346     const Real ke = getImpl().getKE(s); // Cached value for kinetic energy
347     const Real kB = getImpl().kB;       // Boltzmann's constant
348     const int  N  = getImpl().getNumThermalDOFs(s);
349     return (2*ke) / (N*kB);
350 }
351 
getNumThermalDofs(const State & s) const352 int Force::Thermostat::getNumThermalDofs(const State& s) const {
353     return getImpl().getNumThermalDOFs(s);
354 }
355 
356 // Bath energy is KEb + PEb where
357 //    KEb = 1/2 kT t^2 (N z0^2 + sum(zi^2))
358 //    PEb = kT (N s0 + sum(si))
359 // Cost is about 7 flops + 4 flops/chain, say about 25 flops.
calcBathEnergy(const State & state) const360 Real Force::Thermostat::calcBathEnergy(const State& state) const {
361     const ThermostatImpl& impl = getImpl();
362     const int nChains = impl.getNumChains(state);
363     const int  N = impl.getNumThermalDOFs(state);
364     const Real kT = impl.kB * impl.getBathTemp(state);
365     const Real t = impl.getRelaxationTime(state);
366 
367     Real zsqsum = N * square(impl.getZ(state,0));
368     for (int i=1; i < nChains; ++i)
369         zsqsum += square(impl.getZ(state,i));
370 
371     Real ssum = N * impl.getZ(state, nChains);
372     for (int i=1; i < nChains; ++i)
373         ssum += impl.getZ(state, nChains+i);
374 
375     const Real KEb = (kT/2) * t*t * zsqsum;
376     const Real PEb = kT * ssum;
377 
378     return KEb + PEb;
379 }
380 
getExternalPower(const State & state) const381 Real Force::Thermostat::getExternalPower(const State& state) const {
382     return getImpl().calcExternalPower(state);
383 }
384 
getExternalWork(const State & state) const385 Real Force::Thermostat::getExternalWork(const State& state) const {
386     return getImpl().getExternalWork(state);
387 }
388 
setExternalWork(State & state,Real w) const389 void Force::Thermostat::setExternalWork(State& state, Real w) const {
390     getImpl().updExternalWork(state) = w;
391 }
392 
393 // This is the number of thermal dofs. TODO: we're ignoring constraint
394 // redundancy but we shouldn't be! That could result in negative dofs, so we'll
395 // make sure that doesn't happen. But don't expect meaningful results
396 // in that case. Note that it is the acceleration-level constraints that
397 // matter; they remove dofs regardless of whether there is a corresponding
398 // velocity constraint.
getNumThermalDOFs(const State & state) const399 int Force::ThermostatImpl::getNumThermalDOFs(const State& state) const {
400     const int ndofs = state.getNU() - state.getNUDotErr()
401                         - getNumExcludedDofs(state);
402     const int N = std::max(1, ndofs);
403     return N;
404 }
405 
406 // This force produces only mobility forces, with
407 //      f = -z0 * M * u
408 // Conveniently we already calculated the momentum M*u and cached it
409 // in realizeVelocity(). Additional cost here is 2*N flops.
410 void Force::ThermostatImpl::
calcForce(const State & state,Vector_<SpatialVec> &,Vector_<Vec3> &,Vector & mobilityForces) const411 calcForce(const State& state, Vector_<SpatialVec>&, Vector_<Vec3>&,
412           Vector& mobilityForces) const
413 {
414     const Vector& p = getMomentum(state);
415 
416     // Generate momentum-weighted forces and apply to mobilities.
417     // This is 2*N flops.
418     mobilityForces -= getZ(state, 0)*p;
419 }
420 
421 // All the power generated by this force is external (to or from the
422 // thermal bath). So the power is
423 //      p = ~u * f = -z0 * (~u * M * u) = -z0 * (2*KE).
424 // We already calculated and cached KE in realizeVelocity() so power
425 // is practically free here (2 flops).
426 Real Force::ThermostatImpl::
calcExternalPower(const State & state) const427 calcExternalPower(const State& state) const {
428     return -2 * getZ(state, 0) * getKE(state);
429 }
430 
431 // Allocate and initialize state variables.
realizeTopology(State & state) const432 void Force::ThermostatImpl::realizeTopology(State& state) const {
433     // Make these writable just here where we need to fill in the Topology "cache"
434     // variables; after this they are const.
435     Force::ThermostatImpl* mutableThis = const_cast<Force::ThermostatImpl*>(this);
436     mutableThis->dvNumChains =
437         getForceSubsystem().allocateDiscreteVariable(state, Stage::Model,
438                                                      new Value<int>(defaultNumChains));
439     mutableThis->dvBathTemp =
440         getForceSubsystem().allocateDiscreteVariable(state, Stage::Instance,
441                                                      new Value<Real>(defaultBathTemp));
442     mutableThis->dvRelaxationTime =
443         getForceSubsystem().allocateDiscreteVariable(state, Stage::Instance,
444                                                      new Value<Real>(defaultRelaxationTime));
445     mutableThis->dvNumExcludedDofs =
446         getForceSubsystem().allocateDiscreteVariable(state, Stage::Model,
447                                                      new Value<int>(defaultNumExcludedDofs));
448 
449     // This cache entry holds the auxiliary state index of our first
450     // thermostat state variable. It is valid after realizeModel().
451     mutableThis->cacheZ0Index =
452         getForceSubsystem().allocateCacheEntry(state, Stage::Model,
453                                                new Value<ZIndex>());
454 
455     // This cache entry holds the generalized momentum M*u. The vector
456     // will be allocated to hold nu values.
457     mutableThis->cacheMomentumIndex =
458         getForceSubsystem().allocateCacheEntry(state, Stage::Velocity,
459                                                new Value<Vector>());
460 
461     // This cache entry holds the kinetic energy ~u*M*u/2.
462     mutableThis->cacheKEIndex =
463         getForceSubsystem().allocateCacheEntry(state, Stage::Velocity,
464                                                new Value<Real>(NaN));
465 
466     const Vector workZInit(1, Zero);
467     mutableThis->workZIndex =
468         getForceSubsystem().allocateZ(state, workZInit);
469 }
470 
471 // Allocate the chain state variables and bath energy variables.
472 // TODO: this should be done at Instance stage.
realizeModel(State & state) const473 void Force::ThermostatImpl::realizeModel(State& state) const {
474     const int nChains = getNumChains(state);
475     const Vector zInit(2*nChains, Zero);
476     updZ0Index(state) = getForceSubsystem().allocateZ(state, zInit);
477 }
478 
479 // Calculate velocity-dependent terms, the internal coordinate
480 // momentum and the kinetic energy. This is the expensive part
481 // at about 125*N flops if all joints are 1 dof.
realizeVelocity(const State & state) const482 void Force::ThermostatImpl::realizeVelocity(const State& state) const {
483     Vector& Mu = updMomentum(state);
484     matter.multiplyByM(state, state.getU(), Mu); // <-- expensive ~123*N flops
485     updKE(state) = (~state.getU() * Mu) / 2;     // 2*N flops
486 }
487 
488 // Calculate time derivatives of the various state variables.
489 // This is just a fixed cost for the whole system, independent of
490 // size: 3 divides + a few flops per chain, maybe 100 flops total.
realizeDynamics(const State & state) const491 void Force::ThermostatImpl::realizeDynamics(const State& state) const {
492     const Real t    = getRelaxationTime(state);
493     const Real oot2 = 1 / square(t);
494     const int  m    = getNumChains(state);
495 
496     // This is the desired kinetic energy per dof.
497     const Real Eb = kB * getBathTemp(state) / 2;
498 
499     const int  N = getNumThermalDOFs(state);
500 
501     // This is the current average kinetic energy per dof.
502     const Real E = getKE(state) / N;
503 
504     updZDot(state, 0) = (E/Eb - 1) * oot2;
505 
506     int Ndofs = N;  // only for z0
507     for (int k=1; k < m; ++k) {
508         const Real zk1 = getZ(state, k-1);
509         const Real zk  = getZ(state, k);
510         updZDot(state, k-1) -= zk1 * zk;
511         updZDot(state, k) = Ndofs * square(zk1) - oot2;
512         Ndofs = 1; // z1..m-1 control only 1 dof each
513     }
514 
515     // Calculate sdot's for energy calculation.
516     for (int k=0; k < m; ++k)
517         updZDot(state, m+k) = getZ(state, k);
518 
519     updWorkZDot(state) = calcExternalPower(state);
520 }
521 
522 
523 } // namespace SimTK
524 
525