Skip to content

Commit 468779b

Browse files
author
mmillard
committed
Thelen2003Muscle::initMuscleState updated. Algorithm changes: bisection used for good i.c., followed by a text book Newton method (not the previous version with the line search). In addition the search routine has been constrained to look for solutions in which the tendon is strained by a small numerical amount and fv has a small numerical value: this prevents gradients from dissappearing and screwing up the initialization.
1 parent 542269d commit 468779b

File tree

1 file changed

+156
-40
lines changed

1 file changed

+156
-40
lines changed

OpenSim/Actuators/Thelen2003Muscle.cpp

Lines changed: 156 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,9 @@ Thelen2003Muscle::initMuscleState(const SimTK::State& s,
749749

750750
double phi = 0.0;
751751
double cosphi = 1.0;
752+
double sinphi = 0.0;
753+
double tanphi = 0.;
754+
752755

753756
//Normalized quantities
754757
double tlN = tl/tsl;
@@ -782,6 +785,15 @@ Thelen2003Muscle::initMuscleState(const SimTK::State& s,
782785

783786
double Ke = 0; // Linearized local stiffness of the muscle
784787

788+
//MM 4 March 2020: additional numerical bounds to prevent
789+
// cover a few additional nasty cases
790+
// eTMin > 0: if the tendon goes slack the tendons force-length gradient
791+
// goes to zero. The Newton method will fail.
792+
// fvMin > 0: if the fv multiplier hits zero then the gradient of fiber
793+
// force w.r.t. length goes to zero and the Newton method will
794+
// fail.
795+
double eTMin = aSolTolerance*10;
796+
double fvMin = aSolTolerance*10;
785797
//*******************************
786798
// Helper functions
787799
//Update position level quantities, only if they won't go singular
@@ -858,8 +870,124 @@ Thelen2003Muscle::initMuscleState(const SimTK::State& s,
858870
dlceN = dlce / (vmax*ofl);
859871
// Update the force-velocity multiplier
860872
fv = calcfvInv(ma, fal, dlceN, aSolTolerance, 100);
873+
874+
//Ensure that fv is in the allowed range
875+
if(fv < fvMin){
876+
fv = fvMin;
877+
dlceN = calcdlceN(ma,fal, ma*fal*fv);
878+
dlce = dlceN*(vmax*ofl);
879+
sinphi = sin(phi);
880+
tanphi = sin(phi)/cos(phi);
881+
dphi= getPennationModel().calcPennationAngularVelocity(
882+
tanphi,lce,dlce);
883+
dtl = getPennationModel().calcTendonVelocity(cosphi,sinphi,dphi,
884+
lce,dlce,dml);
885+
}
861886
};
862887

888+
889+
890+
// MM 4 March 2020
891+
// Run the biesection method over tendon strain to get a good initial
892+
// condition. To get some appropriately large bounds here we evaluate
893+
// the fiber lengths that correspond to a big tendon strain (4!) and zero
894+
// tendon strain.
895+
double eTDelta = 1.0*get_FmaxTendonStrain();
896+
double eTStart = eTDelta*2;
897+
double eTShortest = eTStart - 2*eTDelta; //shortest eT in the limit
898+
double eTLongest = eTStart + 2*eTDelta; //longest eT in the limit
899+
900+
//Shorter than this is a problem: the tendon's force-length gradient
901+
//goes to zero which means the Newton iteration will do nothing useful.
902+
903+
904+
eTShortest = std::max(eTMin,eTShortest);
905+
906+
//Map the tendon lengths over to fiber lengths for the bisection method
907+
//Make sure the bounds we are testing within the allowable range. Adjust
908+
//the bounds if need be
909+
double lceLongest =
910+
getPennationModel().calcFiberLength(ml, tsl*(1.0+eTShortest));
911+
double lceShortest =
912+
getPennationModel().calcFiberLength(ml, tsl*(1.0+eTLongest));
913+
914+
if(lceShortest > getMinimumFiberLength() ){
915+
lceShortest = getMinimumFiberLength();
916+
}
917+
918+
double lceDelta = (lceLongest-lceShortest)/4.0;
919+
double lceBest = 0.5*(lceLongest+lceShortest);
920+
921+
double lceLeft = 0.;
922+
double lceRight = 0.;
923+
924+
double ferrLeft = 0;
925+
double ferrRight = 0;
926+
double ferrBest = 1e100;
927+
928+
//Evaluate the initial solution
929+
lce = lceBest;
930+
positionFunc();
931+
multipliersFunc();
932+
fv = 1.0;
933+
partialsFunc();
934+
velocityFunc();
935+
ferrFunc();
936+
ferrBest = ferr;
937+
//10 Bisection iterations will bring us to a tendon length that is within
938+
// 4 / 2^10 = 0.0039 of the true normalized strain. This translates directly
939+
// into 0.0039 of the true force at equilibrium
940+
unsigned int iterBisection =
941+
std::max((unsigned int)(std::ceil(0.5*aMaxIterations)) ,
942+
(unsigned int)5 );
943+
944+
for(unsigned int i = 0; i<iterBisection; ++i) {
945+
//Evaluate the left hand candidate
946+
lceLeft = lceBest-lceDelta;
947+
lce = lceLeft;
948+
positionFunc();
949+
multipliersFunc();
950+
fv = 1.0;
951+
partialsFunc();
952+
velocityFunc();
953+
ferrFunc();
954+
ferrLeft = ferr;
955+
956+
//Evalute the right hand candidate
957+
lceRight = lceBest+lceDelta;
958+
lce = lceRight;
959+
positionFunc();
960+
multipliersFunc();
961+
fv = 1.0;
962+
partialsFunc();
963+
velocityFunc();
964+
ferrFunc();
965+
ferrRight = ferr;
966+
967+
//Update the current best solution if one of the candidates is suitable
968+
if( std::abs(ferrLeft) < std::abs(ferrBest)
969+
&& std::abs(ferrLeft) <= std::abs(ferrRight)){
970+
lceBest = lceLeft;
971+
ferrBest = ferrLeft;
972+
}
973+
if( std::abs(ferrRight) < std::abs(ferrBest)
974+
&& std::abs(ferrRight) < std::abs(ferrLeft)){
975+
lceBest = lceRight;
976+
ferrBest = ferrRight;
977+
}
978+
979+
//Reduce the step size
980+
lceDelta = lceDelta*0.5;
981+
}
982+
983+
double delta_lce_max = lceDelta*2.0;
984+
985+
//Polish up the root to tolerance using a text-book Newton's method
986+
lce = lceBest;
987+
988+
double ferrPrev = ferrBest;
989+
double lcePrev = lceBest;
990+
863991
//*******************************
864992
//Initialize the loop
865993
int iter = 0;
@@ -886,57 +1014,45 @@ Thelen2003Muscle::initMuscleState(const SimTK::State& s,
8861014
// newly estimated fv
8871015
partialsFunc();
8881016

889-
double ferrPrev = ferr;
890-
double lcePrev = lce;
1017+
//MM 4 March 2020
1018+
//Removing the line search capability of the (original) Newton method
1019+
//implementation: this is less reliable then simply giving the Newton
1020+
//method a very good initial condition by using the bisection method.
1021+
//This will be a bit slower, but probably not noticeably so.
8911022

892-
double h = 1.0;
893-
while( (abs(ferr) > aSolTolerance) && (iter < aMaxIterations)) {
894-
// Compute the search direction
895-
dferr_d_lce = dFmAT_dlce - dFt_d_lce;
896-
h = 1.0;
897-
898-
while (abs(ferr) >= abs(ferrPrev)) {
899-
// Compute the Newton step
900-
delta_lce = -h*ferrPrev / dferr_d_lce;
901-
// Take a Newton Step if the step is nonzero
902-
if (abs(delta_lce) > SimTK::SignificantReal)
903-
lce = lcePrev + delta_lce;
904-
else {
905-
// We've stagnated or hit a limit; assume we are hitting local
906-
// minimum and attempt to approach from the other direction.
907-
lce = lcePrev - sign(delta_lce)*SimTK::SqrtEps;
908-
// Force a break, which will update the derivatives of
909-
// the muscle force and estimate of the fiber-velocity
910-
h = 0;
911-
}
1023+
//double h = 1.0;
9121024

913-
if (lce < getMinimumFiberLength()) {
914-
lce = getMinimumFiberLength();
915-
}
9161025

917-
// Update the muscles's position level quantities (lengths, angles)
918-
positionFunc();
1026+
while( (abs(ferr) > aSolTolerance) && (iter < aMaxIterations)) {
9191027

920-
// Update the muscle force multipliers
921-
multipliersFunc();
1028+
dferr_d_lce = dFmAT_dlce - dFt_d_lce;
1029+
delta_lce = - ferrPrev / dferr_d_lce;
9221030

923-
// Compute the force error assuming fiber-velocity is unchanged
924-
ferrFunc();
1031+
lce = lcePrev + delta_lce;
9251032

926-
if (h <= SimTK::SqrtEps ) {
927-
break;
928-
}
929-
else
930-
h = 0.5*h;
1033+
if (lce < getMinimumFiberLength()) {
1034+
lce = getMinimumFiberLength();
9311035
}
9321036

933-
ferrPrev = ferr;
934-
lcePrev = lce;
9351037

936-
// Update the partial derivative of the force error w.r.t. lce
1038+
//Evaluate the current solution assuming the fiber velocity
1039+
//does not change from the previous iteration.
1040+
positionFunc();
1041+
multipliersFunc();
9371042
partialsFunc();
938-
// Update velocity estimate and velocity multiplier
9391043
velocityFunc();
1044+
ferrFunc();
1045+
1046+
1047+
ferrPrev = ferr;
1048+
lcePrev = lce;
1049+
1050+
//If the tendon goes slack the tendon's gradient goes to zero
1051+
//and the Newton method has no hope of recovering.
1052+
if( tlN < 1.0 ){
1053+
lce = lceLongest;
1054+
}
1055+
9401056

9411057
iter++;
9421058
}

0 commit comments

Comments
 (0)