//============================================================================== /* * RBDL - Rigid Body Dynamics Library: Addon : muscle * Copyright (c) 2016 Matthew Millard * * Licensed under the zlib license. See LICENSE for more details. */ #include "Millard2016TorqueMuscle.h" #include "TorqueMuscleFunctionFactory.h" #include "csvtools.h" #include #include #include #include #include #include #include #include #include static double EPSILON = std::numeric_limits::epsilon(); static double SQRTEPSILON = sqrt(EPSILON); using namespace RigidBodyDynamics::Math; using namespace RigidBodyDynamics::Addons::Muscle; using namespace RigidBodyDynamics::Addons::Geometry; using namespace std; /************************************************************* Table Access Structure Names *************************************************************/ const double gravity = 9.81; //Needed for the strength scaling used //by Anderson et al. const char* DataSet::names[] = { "Anderson2007", "Gymnast"}; const char* GenderSet::names[] = {"Male", "Female"}; const char* AgeGroupSet::names[] = {"Young18To25", "Middle55To65", "SeniorOver65"}; const char* JointTorqueSet::names[] = { "HipExtension" , "HipFlexion" , "KneeExtension" , "KneeFlexion" , "AnkleExtension" , "AnkleFlexion" , "ElbowExtension" , "ElbowFlexion" , "ShoulderExtension" , "ShoulderFlexion" , "WristExtension" , "WristFlexion" , "ShoulderHorizontalAdduction", "ShoulderHorizontalAbduction", "ShoulderInternalRotation" , "ShoulderExternalRotation" , "WristUlnarDeviation" , "WristRadialDeviation" , "WristPronation" , "WristSupination" , "LumbarExtension" , "LumbarFlexion" }; const char* Anderson2007::GenderNames[] = {"Male","Female"}; const char* Anderson2007::AgeGroupNames[] = { "Young18To25", "Middle55To65", "SeniorOver65"}; const char* Anderson2007::JointTorqueNames[] = {"HipExtension" , "HipFlexion" , "KneeExtension" , "KneeFlexion" , "AnkleExtension", "AnkleFlexion" }; const char* Gymnast::GenderNames[] = {"Male"}; const char* Gymnast::AgeGroupNames[] = {"Young18To25"}; const char* Gymnast::JointTorqueNames[] = { "HipExtension" , "HipFlexion" , "KneeExtension" , "KneeFlexion" , "AnkleExtension" , "AnkleFlexion" , "ElbowExtension" , "ElbowFlexion" , "ShoulderExtension" , "ShoulderFlexion" , "WristExtension" , "WristFlexion" , "ShoulderHorizontalAdduction", "ShoulderHorizontalAbduction", "ShoulderInternalRotation" , "ShoulderExternalRotation" , "WristUlnarDeviation" , "WristRadialDeviation" , "WristPronation" , "WristSupination" , "LumbarExtension" , "LumbarFlexion"}; /************************************************************* Coefficient Tables *************************************************************/ /* This data is taken from Table 3 of Anderson, D. E., Madigan, M. L., & Nussbaum, M. A. (2007). Maximum voluntary joint torque as a function of joint angle and angular velocity: model development and application to the lower limb. Journal of biomechanics, 40(14), 3105-3113. Each row contains the coefficients for the active and passive torque characteristics for a specific joint, direction, gender and age group. Each row corresponds to a single block taken from Table 3, as read from left to right top to bottom. The first 4 columns have been added to describe the joint, direction, gender and age group. Column labels: Parameter Set Meta Data 0: joint: hip0_knee1_ankle2, 1: direction: ext0_flex1, 2: gender: male0_female1, 3: age: age18to25_0_55to65_1_g65_2, Active Torque-Angle and Torque-Velocity Curves 4: c1, 5: c2, 6: c3, 7: c4, 8: c5, 9: c6, Passive Torque-Angle Curves 10: b1, 11: k1, 12: b2, 13: k2, */ double const Millard2016TorqueMuscle::Anderson2007Table3Mean[36][14] = { {0,0,0,0,0.161,0.958,0.932,1.578,3.19,0.242,-1.21,-6.351,0.476,5.91 }, {0,0,1,0,0.181,0.697,1.242,1.567,3.164,0.164,-1.753,-6.358,0.239,3.872 }, {0,0,0,1,0.171,0.922,1.176,1.601,3.236,0.32,-2.16,-8.073,0.108,4.593 }, {0,0,1,1,0.14,0.83,1.241,1.444,2.919,0.317,-1.361,-7.128,0.013,6.479 }, {0,0,0,2,0.144,0.896,1.125,1.561,3.152,0.477,-2.671,-7.85,0.092,5.192 }, {0,0,1,2,0.138,0.707,1.542,1.613,3.256,0.36,-0.758,-7.545,0.018,6.061 }, {0,1,0,0,0.113,0.738,-0.214,2.095,4.267,0.218,1.21,-6.351,-0.476,5.91 }, {0,1,1,0,0.127,0.65,-0.35,2.136,4.349,0.156,1.753,-6.358,-0.239,3.872 }, {0,1,0,1,0.107,0.712,-0.192,2.038,4.145,0.206,2.16,-8.073,-0.108,4.593 }, {0,1,1,1,0.091,0.812,-0.196,2.145,4.366,0.186,1.361,-7.128,-0.013,6.479 }, {0,1,0,2,0.101,0.762,-0.269,1.875,3.819,0.296,2.671,-7.85,-0.092,5.192 }, {0,1,1,2,0.081,0.625,-0.422,2.084,4.245,0.196,0.758,-7.545,-0.018,6.061 }, {1,0,0,0,0.163,1.258,1.133,1.517,3.952,0.095,0,0,-6.25,-4.521 }, {1,0,1,0,0.159,1.187,1.274,1.393,3.623,0.173,0,0,-8.033,-5.25 }, {1,0,0,1,0.156,1.225,1.173,1.518,3.954,0.266,0,0,-12.83,-5.127 }, {1,0,1,1,0.128,1.286,1.141,1.332,3.469,0.233,0,0,-6.576,-4.466 }, {1,0,0,2,0.137,1.31,1.067,1.141,3.152,0.386,0,0,-10.519,-5.662 }, {1,0,1,2,0.124,1.347,1.14,1.066,2.855,0.464,0,0,-8.8,-6.763 }, {1,1,0,0,0.087,0.869,0.522,2.008,5.233,0.304,0,0,6.25,-4.521 }, {1,1,1,0,0.08,0.873,0.635,1.698,4.412,0.175,0,0,8.033,-5.25 }, {1,1,0,1,0.081,0.986,0.523,1.83,4.777,0.23,0,0,12.83,-5.127 }, {1,1,1,1,0.06,0.967,0.402,1.693,4.41,0.349,0,0,6.576,-4.466 }, {1,1,0,2,0.069,0.838,0.437,1.718,4.476,0.414,0,0,10.519,-5.662 }, {1,1,1,2,0.06,0.897,0.445,1.121,2.922,0.389,0,0,8.8,-6.763 }, {2,0,0,0,0.095,1.391,0.408,0.987,3.558,0.295,-0.0005781,-5.819,0.967,6.09 }, {2,0,1,0,0.104,1.399,0.424,0.862,3.109,0.189,-0.005218,-4.875,0.47,6.425 }, {2,0,0,1,0.114,1.444,0.551,0.593,2.128,0.35,-0.001311,-10.943,0.377,8.916 }, {2,0,1,1,0.093,1.504,0.381,0.86,3.126,0.349,-2.888e-05,-17.189,0.523,7.888 }, {2,0,0,2,0.106,1.465,0.498,0.49,1.767,0.571,-5.693e-05,-21.088,0.488,7.309 }, {2,0,1,2,0.125,1.299,0.58,0.587,1.819,0.348,-2.35e-05,-12.567,0.331,6.629 }, {2,1,0,0,0.033,1.51,-0.187,0.699,1.94,0.828,0.0005781,-5.819,-0.967,6.09 }, {2,1,1,0,0.027,1.079,-0.302,0.864,2.399,0.771,0.005218,-4.875,-0.47,6.425 }, {2,1,0,1,0.028,1.293,-0.284,0.634,1.759,0.999,0.001311,-10.943,-0.377,8.916 }, {2,1,1,1,0.024,1.308,-0.254,0.596,1.654,1.006,2.888e-05,-17.189,-0.523,7.888}, {2,1,0,2,0.029,1.419,-0.174,0.561,1.558,1.198,5.693e-05,-21.088,-0.488,7.309}, {2,1,1,2,0.022,1.096,-0.369,0.458,1.242,1.401,2.35e-05,-12.567,-0.331,6.629 }}; //See the description for the mean data. This table constains the //parameter standard deviations double const Millard2016TorqueMuscle::Anderson2007Table3Std[36][14] = { {0,0,0,0,0.049,0.201,0.358,0.286,0.586,0.272,0.66,0.97,0.547,4.955 }, {0,0,1,0,0.047,0.13,0.418,0.268,0.542,0.175,1.93,2.828,0.292,1.895 }, {0,0,0,1,0.043,0.155,0.195,0.306,0.622,0.189,1.297,2.701,0.091,0.854 }, {0,0,1,1,0.032,0.246,0.365,0.223,0.45,0.14,1.294,2.541,0.02,2.924 }, {0,0,0,2,0.039,0.124,0.077,0.184,0.372,0.368,0.271,3.402,0.111,1.691 }, {0,0,1,2,0.003,0.173,0.279,0.135,0.273,0.237,0.613,0.741,0.031,2.265 }, {0,1,0,0,0.025,0.217,0.245,0.489,0.995,0.225,0.66,0.97,0.547,4.955 }, {0,1,1,0,0.033,0.178,0.232,0.345,0.702,0.179,1.93,2.828,0.292,1.895 }, {0,1,0,1,0.02,0.248,0.274,0.318,0.652,0.088,1.297,2.701,0.091,0.854 }, {0,1,1,1,0.016,0.244,0.209,0.375,0.765,0.262,1.294,2.541,0.02,2.924 }, {0,1,0,2,0.025,0.151,0.234,0.164,0.335,0.102,0.271,3.402,0.111,1.691 }, {0,1,1,2,0.008,0.062,0.214,0.321,0.654,0.28,0.613,0.741,0.031,2.265 }, {1,0,0,0,0.04,0.073,0.073,0.593,1.546,0.171,0,0,2.617,0.553 }, {1,0,1,0,0.028,0.084,0.181,0.38,0.989,0.27,0,0,3.696,1.512 }, {1,0,0,1,0.031,0.063,0.048,0.363,0.947,0.06,0,0,2.541,2.148 }, {1,0,1,1,0.016,0.094,0.077,0.319,0.832,0.133,0,0,1.958,1.63 }, {1,0,0,2,0.017,0.127,0.024,0.046,0.04,0.124,0,0,1.896,1.517 }, {1,0,1,2,0.018,0.044,0.124,0.128,0.221,0.129,0,0,6.141,0.742 }, {1,1,0,0,0.015,0.163,0.317,1.364,3.554,0.598,0,0,2.617,0.553 }, {1,1,1,0,0.015,0.191,0.287,0.825,2.139,0.319,0,0,3.696,1.512 }, {1,1,0,1,0.017,0.138,0.212,0.795,2.067,0.094,0,0,2.541,2.148 }, {1,1,1,1,0.015,0.21,0.273,0.718,1.871,0.143,0,0,1.958,1.63 }, {1,1,0,2,0.022,0.084,0.357,0.716,1.866,0.201,0,0,1.896,1.517 }, {1,1,1,2,0.005,0.145,0.21,0.052,0.135,0.078,0,0,6.141,0.742 }, {2,0,0,0,0.022,0.089,0.083,0.595,2.144,0.214,0.001193,7.384,0.323,1.196 }, {2,0,1,0,0.034,0.19,0.186,0.487,1.76,0.213,0.01135,6.77,0.328,1.177 }, {2,0,0,1,0.029,0.136,0.103,0.165,0.578,0.133,0.003331,10.291,0.403,3.119 }, {2,0,1,1,0.026,0.235,0.143,0.448,1.613,0.27,3.562e-05,7.848,0.394,1.141 }, {2,0,0,2,0.035,0.136,0.132,0.262,0.944,0.313,3.164e-05,1.786,0.258,0.902 }, {2,0,1,2,0.006,0.095,0.115,0.258,0.423,0.158,2.535e-05,10.885,0.247,2.186}, {2,1,0,0,0.005,0.19,0.067,0.108,0.301,0.134,0.001193,7.384,0.323,1.196 }, {2,1,1,0,0.006,0.271,0.171,0.446,1.236,0.206,0.01135,6.77,0.328,1.177 }, {2,1,0,1,0.005,0.479,0.178,0.216,0.601,0.214,0.003331,10.291,0.403,3.119 }, {2,1,1,1,0.002,0.339,0.133,0.148,0.41,0.284,3.562e-05,7.848,0.394,1.141 }, {2,1,0,2,0.002,0.195,0.056,0.188,0.521,0.29,3.164e-05,1.786,0.258,0.902 }, {2,1,1,2,0.003,0.297,0.109,0.089,0.213,0.427,2.535e-05,10.885,0.247,2.186}}; /* This table contains parameters for the newly made torque muscle curves: 1. maxIsometricTorque Nm 2. maxAngularVelocity rad/s 3. angleAtOneActiveNormTorque rad 4. angularWidthActiveTorque rad 5. tvAtMaxEccentricVelocity Nm/Nm 6. tvAtHalfMaxConcentricVelocity Nm/Nm 7. angleAtZeroPassiveTorque rad 8. angleAtOneNormPassiveTorque rad */ double const Millard2016TorqueMuscle::GymnastWholeBody[22][12] = { {0,0,0,0,175.746, 9.02335, 1.06465, 1.05941, 1.1, 0.163849, 0.79158, 1.5708 }, {0,1,0,0,157.293, 9.18043, 0.733038, 1.21999, 1.11905, 0.25, -0.0888019,-0.515207 }, {1,0,0,0,285.619, 19.2161, 0.942478, 0.509636, 1.13292, 0.115304, 2.00713, 2.70526 }, {1,1,0,0,98.7579, 16.633, 1.02974, 1.11003, 1.12, 0.19746, 0, -0.174533 }, {2,0,0,0,127.561, 11.7646, 0.408, 0.660752, 1.159, 0.410591, 0.0292126, 0.785398 }, {2,1,0,0,44.3106, 17.2746, -0.187, 0.60868, 1.2656, 0.112303, -0.523599, -1.0472 }, {3,0,0,0,127.401, 16.249, 2.14675, 1.83085, 1.1, 0.250134, 2.8291, 3.55835 }, {3,1,0,0,91.1388, 19.0764, 0.890118, 1.2898, 1.23011, 0.249656, -0.523599, -1.0472 }, {5,0,0,0,15.5653, 36.5472, 1.55334, 1.38928, 1.16875, 0.115356, 1.0472, 1.5708 }, {5,1,0,0,39.2252, 36.3901, 0.663225, 1.71042, 1.14, 0.115456, -0.793739, -1.49714 }, {3,2,0,0,128.496, 18.05, 0.839204, 1.28041, 1.25856, 0.214179, 1.5708, 2.26893 }, {3,3,0,0,94.6839, 18 , -0.277611, 2.37086, 1.23042, 0.224227, -0.523599, -1.0472 }, {3,5,0,0,50.522 , 19.47, -1.18761, 2.80524, 1.27634, 0.285399, 1.39626, 1.91986 }, {3,4,0,0,43.5837, 18, -0.670796, 1.98361, 1.35664, 0.229104, -1.0472, -1.5708 }, {4,1,0,0,101.384, 18.1, 0.33, 3.62155, 1.37223, 0.189909, 0, -0.174533 }, {4,0,0,0,69.8728, 18.45, 1.64319, 1.30795, 1.31709, 0.189676, 2.0944, 2.61799 }, {5,6,0,0,13.5361, 35.45, -0.209204, 1.33735, 1.23945, 0.250544, -0.785398, -1.5708 }, {5,7,0,0,12.976 , 27.88, -0.212389, 0.991803, 1.3877, 0.207506, 0.785398, 1.5708 }, {5,9,0,0,31.4217, 18.02, 0.43, 1.47849, 1.34817, 0.196913, 0, -0.523599}, {5,8,0,0,23.8345, 21.77, -1.14319, 2.56082, 1.31466, 0.2092, 0.349066, 0.872665 }, {6,0,0,0,687.864, 1.0472, 1.5506, 1.14543, 1.1, 0.45, 0.306223, 1.35342 }, {6,1,0,0,211.65 , 0.523599, 0, 6.28319, 1.1, 0.45, 0, -0.785398 }}; /************************************************************* Map that goes from a single joint-torque-direction index to the pair of joint and direction indicies used in the tables *************************************************************/ const static struct JointSet{ enum item { Hip = 0, Knee, Ankle, Shoulder, Elbow, Wrist, Lumbar, Last}; JointSet(){} } JointSet; struct DirectionSet{ enum item { Extension = 0, Flexion, HorizontalAdduction, HorizontalAbduction, ExternalRotation, InternalRotation, Supination, Pronation, RadialDeviation, UlnarDeviation, Last }; DirectionSet(){} } DirectionSet; const static int JointTorqueMap[22][3] = { {(int)JointTorqueSet::HipExtension , (int)JointSet::Hip , (int)DirectionSet::Extension }, {(int)JointTorqueSet::HipFlexion , (int)JointSet::Hip , (int)DirectionSet::Flexion }, {(int)JointTorqueSet::KneeExtension , (int)JointSet::Knee , (int)DirectionSet::Extension }, {(int)JointTorqueSet::KneeFlexion , (int)JointSet::Knee , (int)DirectionSet::Flexion }, {(int)JointTorqueSet::AnkleExtension , (int)JointSet::Ankle , (int)DirectionSet::Extension }, {(int)JointTorqueSet::AnkleFlexion , (int)JointSet::Ankle , (int)DirectionSet::Flexion }, {(int)JointTorqueSet::ElbowExtension , (int)JointSet::Elbow , (int)DirectionSet::Extension }, {(int)JointTorqueSet::ElbowFlexion , (int)JointSet::Elbow , (int)DirectionSet::Flexion }, {(int)JointTorqueSet::ShoulderExtension , (int)JointSet::Shoulder , (int)DirectionSet::Extension }, {(int)JointTorqueSet::ShoulderFlexion , (int)JointSet::Shoulder , (int)DirectionSet::Flexion }, {(int)JointTorqueSet::WristExtension , (int)JointSet::Wrist , (int)DirectionSet::Extension }, {(int)JointTorqueSet::WristFlexion , (int)JointSet::Wrist , (int)DirectionSet::Flexion }, {(int)JointTorqueSet::ShoulderHorizontalAdduction , (int)JointSet::Shoulder , (int)DirectionSet::HorizontalAdduction}, {(int)JointTorqueSet::ShoulderHorizontalAbduction , (int)JointSet::Shoulder , (int)DirectionSet::HorizontalAbduction}, {(int)JointTorqueSet::ShoulderInternalRotation , (int)JointSet::Shoulder , (int)DirectionSet::InternalRotation }, {(int)JointTorqueSet::ShoulderExternalRotation , (int)JointSet::Shoulder , (int)DirectionSet::ExternalRotation }, {(int)JointTorqueSet::WristUlnarDeviation , (int)JointSet::Wrist , (int)DirectionSet::UlnarDeviation }, {(int)JointTorqueSet::WristRadialDeviation , (int)JointSet::Wrist , (int)DirectionSet::RadialDeviation }, {(int)JointTorqueSet::WristPronation , (int)JointSet::Wrist , (int)DirectionSet::Pronation }, {(int)JointTorqueSet::WristSupination , (int)JointSet::Wrist , (int)DirectionSet::Supination }, {(int)JointTorqueSet::LumbarExtension , (int)JointSet::Lumbar , (int)DirectionSet::Extension }, {(int)JointTorqueSet::LumbarFlexion , (int)JointSet::Lumbar , (int)DirectionSet::Flexion }}; /************************************************************* Constructors *************************************************************/ Millard2016TorqueMuscle:: Millard2016TorqueMuscle( ) :angleOffset(1.0), signOfJointAngle(1.0), signOfJointTorque(1.0), signOfConcentricAnglularVelocity(1.0), muscleName("empty") { muscleCurvesAreDirty = true; } Millard2016TorqueMuscle::Millard2016TorqueMuscle( DataSet::item dataSet, const SubjectInformation &subjectInfo, int jointTorque, double jointAngleOffsetRelativeToDoxygenFigures, double signOfJointAngleRelativeToDoxygenFigures, double signOfJointTorque, const std::string& name ):angleOffset(jointAngleOffsetRelativeToDoxygenFigures), signOfJointAngle(signOfJointAngleRelativeToDoxygenFigures), signOfJointTorque(signOfJointTorque), signOfConcentricAnglularVelocity(signOfJointTorque), muscleName(name), dataSet(dataSet) { subjectHeightInMeters = subjectInfo.heightInMeters; subjectMassInKg = subjectInfo.massInKg; passiveCurveAngleOffset = 0.; beta = 0.1; int gender = (int) subjectInfo.gender; int ageGroup = (int) subjectInfo.ageGroup; int joint = -1; int jointDirection = -1; for(int i=0; i < JointTorqueSet::Last; ++i){ if(JointTorqueMap[i][0] == jointTorque){ joint = JointTorqueMap[i][1]; jointDirection = JointTorqueMap[i][2]; } } if(joint == -1 || jointDirection == -1){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << ": A jointTorqueDirection of " << jointTorque << " does not exist."; assert(0); abort(); } if( abs(abs(signOfJointAngle)-1) > EPSILON){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << ": signOfJointAngleRelativeToAnderson2007 must be [-1,1] not " << signOfJointAngle; assert(0); abort(); } if( abs(abs(signOfConcentricAnglularVelocity)-1) > EPSILON){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << ": signOfJointAngularVelocityDuringConcentricContraction " << "must be [-1,1] not " << signOfConcentricAnglularVelocity; assert(0); abort(); } if( abs(abs(signOfJointTorque)-1) > EPSILON){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << ": signOfJointTorque must be [-1,1] not " << signOfJointTorque; assert(0); abort(); } if(subjectHeightInMeters <= 0){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << ": subjectHeightInMeters > 0, but it's " << subjectHeightInMeters; assert(0); abort(); } if(subjectMassInKg <= 0){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << ": subjectMassInKg > 0, but it's " << subjectMassInKg; assert(0); abort(); } int idx = -1; int jointIdx = 0; int dirIdx = 1; int genderIdx = 2; int ageIdx = 3; switch(dataSet){ case DataSet::Anderson2007: { c1c2c3c4c5c6Anderson2007.resize(6); b1k1b2k2Anderson2007.resize(4); for(int i=0; i<36;++i){ if( abs(Anderson2007Table3Mean[i][jointIdx] -(double)joint) < EPSILON && abs(Anderson2007Table3Mean[i][dirIdx] -(double)jointDirection) < EPSILON && abs(Anderson2007Table3Mean[i][genderIdx] -(double)gender) < EPSILON && abs(Anderson2007Table3Mean[i][ageIdx] -(double)ageGroup) < EPSILON){ idx = i; } } if(idx != -1){ for(int i=0; i<6; ++i){ c1c2c3c4c5c6Anderson2007[i] = Anderson2007Table3Mean[idx][i+4]; } for(int i=0; i<4; ++i){ b1k1b2k2Anderson2007[i] = Anderson2007Table3Mean[idx][i+10]; } } } break; case DataSet::Gymnast: { gymnastParams.resize(8); for(int i=0; i(this); mutableThis->updateTorqueMuscleCurves(); } double fiberAngle = calcFiberAngle(jointAngle); double fiberVelocity = calcFiberAngularVelocity(jointAngularVelocity); double ta = taCurve.calcValue(fiberAngle); double tp = tpCurve.calcValue(fiberAngle); double fiberVelocityNorm = fiberVelocity/omegaMax; double tv = tvCurve.calcValue(fiberVelocityNorm); double jointTorque = maxActiveIsometricTorque*( activation*ta*tv + passiveTorqueScale*tp - beta*fiberVelocityNorm); if(jointTorque < 0){ jointTorque = 0; } return jointTorque*signOfJointTorque; } void Millard2016TorqueMuscle:: calcTorqueMuscleInfo(double jointAngle, double jointAngularVelocity, double activation, TorqueMuscleInfo& tmi) const { if(muscleCurvesAreDirty){ Millard2016TorqueMuscle* mutableThis = const_cast(this); mutableThis->updateTorqueMuscleCurves(); } double fiberAngle = calcFiberAngle(jointAngle); double fiberAngularVelocity = calcFiberAngularVelocity(jointAngularVelocity); double ta = taCurve.calcValue(fiberAngle); double tp = passiveTorqueScale*tpCurve.calcValue(fiberAngle); double omegaNorm = fiberAngularVelocity/omegaMax; double D_wn_w = 1.0/omegaMax; double tv = tvCurve.calcValue(omegaNorm); double betaUpd = beta; double tb = -betaUpd*omegaNorm; //The jointTorque is not allowed to go below 0, as this corresponds //to the muscle pushing. If the joint torque is going negative, we update //beta so that it will perfectly zero the joint torque. if(activation*ta*tv + passiveTorqueScale*tp + tb < 0){ betaUpd = (activation*ta*tv + passiveTorqueScale*tp)/omegaNorm; tb = -betaUpd*omegaNorm; } double D_ta_DfiberAngle = taCurve.calcDerivative(fiberAngle,1); double D_tp_DfiberAngle = passiveTorqueScale*tpCurve.calcDerivative(fiberAngle,1); double D_tv_DfiberAngularVelocity = tvCurve.calcDerivative(omegaNorm,1)*D_wn_w; double D_tb_DfiberAngularVelocity = -betaUpd*D_wn_w; double D_fiberAngle_D_jointAngle = signOfJointAngle; double D_tv_DfiberAngularVelocity_D_jointAngularVelocity = signOfConcentricAnglularVelocity; tmi.jointAngle = jointAngle; tmi.jointAngularVelocity = jointAngularVelocity; tmi.fiberAngle = fiberAngle; //tmi.tendonAngle = 0.; tmi.fiberAngularVelocity = fiberAngularVelocity; //tmi.tendonAngularVelocity = 0.; tmi.fiberPassiveTorqueAngleMultiplier = tp; tmi.fiberActiveTorqueAngleMultiplier = ta; tmi.fiberActiveTorqueAngularVelocityMultiplier = tv; //tmi.tendonTorqueAngleMultiplier = activation*ta*tv + tp; tmi.activation = activation; tmi.fiberActiveTorque = maxActiveIsometricTorque*(activation*ta*tv); tmi.fiberPassiveTorque = maxActiveIsometricTorque*(tb+tp); tmi.fiberDampingTorque = maxActiveIsometricTorque*(tb); tmi.fiberNormDampingTorque = tb; tmi.fiberTorque = tmi.fiberActiveTorque + tmi.fiberPassiveTorque; //tmi.tendonTorque = tmi.fiberActiveTorque // + tmi.fiberPassiveTorque; tmi.jointTorque = signOfJointTorque*tmi.fiberTorque; tmi.fiberStiffness = maxActiveIsometricTorque*( activation*D_ta_DfiberAngle*tv + D_tp_DfiberAngle); //tmi.tendonStiffness = std::numeric_limits::infinity(); tmi.jointStiffness = signOfJointTorque *tmi.fiberStiffness *D_fiberAngle_D_jointAngle; tmi.fiberActivePower = tmi.fiberActiveTorque * tmi.fiberAngularVelocity; tmi.fiberPassivePower = tmi.fiberPassiveTorque * tmi.fiberAngularVelocity; tmi.fiberPower = tmi.fiberActivePower + tmi.fiberPassivePower; //tmi.tendonPower = 0.; tmi.jointPower = tmi.jointTorque * jointAngularVelocity; //tau = signTq*tauIso*(a * ta(theta) * tv(thetaDot) + tp(theta) ) // Dtau_Da = tauMaxIso*(ta(theta) * tv(thetaDot) ) tmi.DjointTorqueDactivation = signOfJointTorque *maxActiveIsometricTorque *(ta * tv); //Dtau_Domega = signTq*tauIso*(a * ta(theta) * Dtv_DthetaDot(thetaDot)* ) tmi.DjointTorqueDjointAngularVelocity = signOfJointTorque * maxActiveIsometricTorque * ( activation * ta * D_tv_DfiberAngularVelocity * D_tv_DfiberAngularVelocity_D_jointAngularVelocity + D_tb_DfiberAngularVelocity); tmi.DjointTorqueDjointAngle = signOfJointTorque * maxActiveIsometricTorque * ( activation *D_ta_DfiberAngle * tv + D_tp_DfiberAngle )* D_fiberAngle_D_jointAngle; } /************************************************************* Get / Set Functions *************************************************************/ double Millard2016TorqueMuscle:: getJointTorqueSign() const { return signOfJointTorque; } double Millard2016TorqueMuscle:: getJointAngleSign() const { return signOfJointAngle; } double Millard2016TorqueMuscle:: getJointAngleOffset() const { return angleOffset; } double Millard2016TorqueMuscle:: getNormalizedDampingCoefficient() const { return beta; } void Millard2016TorqueMuscle:: setNormalizedDampingCoefficient(double betaUpd) { if(betaUpd < 0){ cerr << "Millard2016TorqueMuscle::" << "setNormalizedDampingCoefficient:" << muscleName << "beta is " << betaUpd << " but beta must be > 0 " << endl; assert(0); abort(); } beta = betaUpd; } double Millard2016TorqueMuscle:: getMaximumActiveIsometricTorque() const { return maxActiveIsometricTorque; } double Millard2016TorqueMuscle:: getMaximumJointAngularVelocity() const { return calcFiberAngularVelocity(omegaMax); } void Millard2016TorqueMuscle:: setMaximumActiveIsometricTorque(double maxIsoTorque) { muscleCurvesAreDirty = true; maxActiveIsometricTorque = maxIsoTorque; } double Millard2016TorqueMuscle:: getJointAngleAtMaximumActiveIsometricTorque() const { return calcJointAngle(angleAtOneNormActiveTorque); } double Millard2016TorqueMuscle:: getJointAngleAtOneNormalizedPassiveIsometricTorque() const { if(muscleCurvesAreDirty){ Millard2016TorqueMuscle* mutableThis = const_cast(this); mutableThis->updateTorqueMuscleCurves(); } return calcJointAngle(angleAtOneNormPassiveTorque); } double Millard2016TorqueMuscle:: getPassiveTorqueScale() const { return passiveTorqueScale; } void Millard2016TorqueMuscle:: setPassiveTorqueScale(double passiveTorqueScaling) { muscleCurvesAreDirty = true; passiveTorqueScale = passiveTorqueScaling; } double Millard2016TorqueMuscle:: getPassiveCurveAngleOffset() const { return passiveCurveAngleOffset; } void Millard2016TorqueMuscle:: setPassiveCurveAngleOffset(double passiveCurveAngleOffsetVal) { muscleCurvesAreDirty = true; passiveCurveAngleOffset = passiveCurveAngleOffsetVal; } void Millard2016TorqueMuscle:: fitPassiveCurveAngleOffset(double jointAngleTarget, double passiveTorqueTarget) { muscleCurvesAreDirty = true; setPassiveCurveAngleOffset(0.0); if(passiveTorqueTarget < SQRTEPSILON){ cerr << "Millard2016TorqueMuscle::" << "fitPassiveTorqueScale:" << muscleName << ": passiveTorque " << passiveTorqueTarget << " but it should be greater than sqrt(eps)" << endl; assert(0); abort(); } //Solve for the fiber angle at which the curve develops //the desired passiveTorque double normPassiveTorque = passiveTorqueTarget /maxActiveIsometricTorque; //Ge a good initial guess VectorNd curveDomain = tpCurve.getCurveDomain(); double angleRange = abs( curveDomain[1]-curveDomain[0]); double fiberAngle = 0.5*(curveDomain[0]+curveDomain[1]); double jointAngleCurr = calcJointAngle(fiberAngle); TorqueMuscleInfo tqInfo = TorqueMuscleInfo(); calcTorqueMuscleInfo(jointAngleCurr,0.,0.,tqInfo); double err = tqInfo.fiberPassiveTorqueAngleMultiplier-normPassiveTorque; double jointAngleLeft = 0; double jointAngleRight = 0; double errLeft = 0; double errRight = 0; double h = 0.25*angleRange; //Get a good initial guess - necessary because these curves //can be *very* nonlinear. int iter = 0; int iterMax = 10; int tol = sqrt(SQRTEPSILON); while(iter < iterMax && abs(err) > tol){ jointAngleLeft = jointAngleCurr-h; jointAngleRight = jointAngleCurr+h; calcTorqueMuscleInfo(jointAngleLeft,0.,0.,tqInfo); errLeft = tqInfo.fiberPassiveTorqueAngleMultiplier - normPassiveTorque; calcTorqueMuscleInfo(jointAngleRight,0.,0.,tqInfo); errRight = tqInfo.fiberPassiveTorqueAngleMultiplier - normPassiveTorque; if(abs(errLeft) SQRTEPSILON && iter < iterMax){ calcTorqueMuscleInfo(jointAngleCurr,0.,0.,tqInfo); err = tqInfo.fiberPassiveTorqueAngleMultiplier -normPassiveTorque; derr = signOfJointTorque*tqInfo.DjointTorqueDjointAngle / maxActiveIsometricTorque ; delta = -err/derr; jointAngleCurr += delta; ++iter; } if(abs(err)>SQRTEPSILON){ cerr << "Millard2016TorqueMuscle::" << "fitPassiveCurveAngleOffset:" << muscleName << ": failed to fit the passive curve offset " << " such that the curve develops the desired " << " passive torque at the given joint angle. This" << " should not be possible - contact the maintainer " << " of this addon." << endl; assert(0); abort(); } double fiberAngleOffset = calcFiberAngle(jointAngleTarget) - calcFiberAngle(jointAngleCurr); setPassiveCurveAngleOffset(fiberAngleOffset); } void Millard2016TorqueMuscle:: fitPassiveTorqueScale(double jointAngleTarget, double passiveTorqueTarget) { muscleCurvesAreDirty = true; setPassiveTorqueScale(1.0); VectorNd curveDomain = tpCurve.getCurveDomain(); VectorNd curveRange = VectorNd(2); curveRange[0] = tpCurve.calcValue(curveDomain[0]); curveRange[1] = tpCurve.calcValue(curveDomain[1]); double fiberAngle = calcFiberAngle(jointAngleTarget); if( (fiberAngle < curveDomain[0] && (curveRange[0] < curveRange[1])) || (fiberAngle > curveDomain[1] && (curveRange[1] < curveRange[0])) ){ cerr << "Millard2016TorqueMuscle::" << "fitPassiveTorqueScale:" << muscleName << ": joint angle is " << jointAngleTarget << " but it should be between " << calcJointAngle(curveDomain[0]) << " and " << calcJointAngle(curveDomain[1]) << endl; assert(0); abort(); } if(passiveTorqueTarget < SQRTEPSILON){ cerr << "Millard2016TorqueMuscle::" << "fitPassiveTorqueScale:" << muscleName << ": passiveTorque " << passiveTorqueTarget << " but it should be greater than sqrt(eps)" << endl; assert(0); abort(); } double normPassiveTorque = passiveTorqueTarget/maxActiveIsometricTorque; int iter = 0; int iterMax = 10; double err = SQRTEPSILON*2; double derr = 0; double delta= 0; double scale = 1.0; setPassiveTorqueScale(scale); TorqueMuscleInfo tqInfo = TorqueMuscleInfo(); while(abs(err) > SQRTEPSILON && iter < iterMax){ setPassiveTorqueScale(scale); calcTorqueMuscleInfo(jointAngleTarget,0,0,tqInfo); err = tqInfo.fiberPassiveTorqueAngleMultiplier - normPassiveTorque; derr= tqInfo.fiberPassiveTorqueAngleMultiplier/scale; delta = -err/derr; scale += delta; if(scale < SQRTEPSILON){ scale = SQRTEPSILON; } iter++; } if(abs(err) > SQRTEPSILON){ cerr << "Millard2016TorqueMuscle::" << "fitPassiveTorqueScale:" << muscleName << ": passiveTorqueScale could not be fit to" << " the data given. See the maintainer of this" << " addon for help." << endl; assert(0); abort(); } } /* const RigidBodyDynamics::Math::VectorNd& Millard2016TorqueMuscle::getParametersc1c2c3c4c5c6() { return c1c2c3c4c5c6Anderson2007; } const RigidBodyDynamics::Math::VectorNd& Millard2016TorqueMuscle::getParametersb1k1b2k2() { return b1k1b2k2Anderson2007; } */ const SmoothSegmentedFunction& Millard2016TorqueMuscle:: getActiveTorqueAngleCurve() const { //This must be updated if the parameters have changed if(muscleCurvesAreDirty){ Millard2016TorqueMuscle* mutableThis = const_cast(this); mutableThis->updateTorqueMuscleCurves(); } return taCurve; } const SmoothSegmentedFunction& Millard2016TorqueMuscle:: getPassiveTorqueAngleCurve() const { //This must be updated if the parameters have changed if(muscleCurvesAreDirty){ Millard2016TorqueMuscle* mutableThis = const_cast(this); mutableThis->updateTorqueMuscleCurves(); } return tpCurve; } const SmoothSegmentedFunction& Millard2016TorqueMuscle:: getTorqueAngularVelocityCurve() const { //This must be updated if the parameters have changed if(muscleCurvesAreDirty){ Millard2016TorqueMuscle* mutableThis = const_cast(this); mutableThis->updateTorqueMuscleCurves(); } return tvCurve; } string Millard2016TorqueMuscle::getName(){ return muscleName; } void Millard2016TorqueMuscle::setName(string &name){ muscleCurvesAreDirty = true; muscleName = name; } /************************************************************* Utilities *************************************************************/ //fa = signJointAngle*(ja - jaO) // ja = signJointAngle*fa + ja0 //dja = signJointAngle*dfa double Millard2016TorqueMuscle:: calcFiberAngle(double jointAngle) const { return signOfJointAngle*(jointAngle-angleOffset); } double Millard2016TorqueMuscle:: calcJointAngle(double fiberAngle) const { return fiberAngle*signOfJointAngle + angleOffset; } double Millard2016TorqueMuscle:: calcFiberAngularVelocity(double jointAngularVelocity) const { return signOfConcentricAnglularVelocity*jointAngularVelocity; } void Millard2016TorqueMuscle::updateTorqueMuscleCurves() { std::string tempName = muscleName; switch(dataSet){ case DataSet::Anderson2007: { double c4 = c1c2c3c4c5c6Anderson2007[3]; double c5 = c1c2c3c4c5c6Anderson2007[4]; omegaMax = abs( 2.0*c4*c5/(c5-3.0*c4) ); scaleFactorAnderson2007 = subjectHeightInMeters *subjectMassInKg *gravity; maxActiveIsometricTorque = scaleFactorAnderson2007 *c1c2c3c4c5c6Anderson2007[0]; angleAtOneNormActiveTorque = c1c2c3c4c5c6Anderson2007[2]; TorqueMuscleFunctionFactory:: createAnderson2007ActiveTorqueAngleCurve( c1c2c3c4c5c6Anderson2007[1], c1c2c3c4c5c6Anderson2007[2], tempName.append("_taCurve"), taCurve); tempName = muscleName; TorqueMuscleFunctionFactory:: createAnderson2007ActiveTorqueVelocityCurve( c1c2c3c4c5c6Anderson2007[3], c1c2c3c4c5c6Anderson2007[4], c1c2c3c4c5c6Anderson2007[5], 1.1, 1.4, tempName.append("_tvCurve"), tvCurve); tempName = muscleName; double normMaxActiveIsometricTorque = maxActiveIsometricTorque /scaleFactorAnderson2007; TorqueMuscleFunctionFactory:: createAnderson2007PassiveTorqueAngleCurve( scaleFactorAnderson2007, normMaxActiveIsometricTorque, b1k1b2k2Anderson2007[0], b1k1b2k2Anderson2007[1], b1k1b2k2Anderson2007[2], b1k1b2k2Anderson2007[3], tempName.append("_tpCurve"), tpCurve); tpCurve.shift(passiveCurveAngleOffset,0); double k = 0; double b = 0; if(b1k1b2k2Anderson2007[0] > 0){ b = b1k1b2k2Anderson2007[0]; k = b1k1b2k2Anderson2007[1]; }else if(b1k1b2k2Anderson2007[2] > 0){ b = b1k1b2k2Anderson2007[2]; k = b1k1b2k2Anderson2007[3]; } if(abs(b) > 0 && passiveTorqueScale > SQRTEPSILON){ angleAtOneNormPassiveTorque = (1/k)*log(abs(maxActiveIsometricTorque/b)) + passiveCurveAngleOffset; }else{ angleAtOneNormPassiveTorque = std::numeric_limits::signaling_NaN(); } //angleAtOneNormPassiveTorque //gymnastParams[Gymnast::PassiveAngleAtOneNormTorque] } break; case DataSet::Gymnast: { omegaMax = gymnastParams[ Gymnast::OmegaMax]; maxActiveIsometricTorque = gymnastParams[ Gymnast::TauMax]; angleAtOneNormActiveTorque = gymnastParams[ Gymnast::ActiveAngleAtOneNormTorque]; TorqueMuscleFunctionFactory:: createGaussianShapedActiveTorqueAngleCurve( gymnastParams[Gymnast::ActiveAngleAtOneNormTorque], gymnastParams[Gymnast::ActiveAngularStandardDeviation], tempName.append("_taCurve"), taCurve); TorqueMuscleFunctionFactory::createPassiveTorqueAngleCurve( gymnastParams[Gymnast::PassiveAngleAtZeroTorque], gymnastParams[Gymnast::PassiveAngleAtOneNormTorque], tempName.append("_tpCurve"), tpCurve); tpCurve.shift(passiveCurveAngleOffset,0); TorqueMuscleFunctionFactory::createTorqueVelocityCurve( gymnastParams[Gymnast::TvAtMaxEccentricVelocity], gymnastParams[Gymnast::TvAtHalfMaxConcentricVelocity], tempName.append("_tvCurve"), tvCurve); if(passiveTorqueScale > 0){ angleAtOneNormPassiveTorque = gymnastParams[Gymnast::PassiveAngleAtOneNormTorque] + passiveCurveAngleOffset; }else{ angleAtOneNormPassiveTorque = std::numeric_limits::signaling_NaN(); } } break; default: { cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << "dataSet has a value of " << dataSet << " which is not a valid choice"; assert(0); abort(); } }; //If the passiveScale is < 1 and > 0, then we must iterate to //find the true value of angleAtOneNormPassiveTorque; if(isfinite(angleAtOneNormPassiveTorque) && (passiveTorqueScale > 0 && passiveTorqueScale != 1.0) ){ int iter = 1; int iterMax =100; double err = 10*SQRTEPSILON; double derr = 0; double delta = 0; while(abs(err) > SQRTEPSILON && iter < iterMax){ err = passiveTorqueScale*tpCurve.calcValue(angleAtOneNormPassiveTorque) - 1.0; derr = passiveTorqueScale *tpCurve.calcDerivative(angleAtOneNormPassiveTorque,1); delta = -err/derr; angleAtOneNormPassiveTorque += delta; ++iterMax; } if(abs(err) > SQRTEPSILON){ cerr << "Millard2016TorqueMuscle::" << "Millard2016TorqueMuscle:" << muscleName << "Internal Error: failed to solve for " <<"angleAtOneNormPassiveTorque. Consult the maintainer of this " <<"addon"; assert(0); abort(); } } muscleCurvesAreDirty = false; } void Millard2016TorqueMuscle::printJointTorqueProfileToFile( const std::string& path, const std::string& fileNameWithoutExtension, int numberOfSamplePoints) { if(muscleCurvesAreDirty){ updateTorqueMuscleCurves(); } VectorNd activeDomain = taCurve.getCurveDomain(); VectorNd passiveDomain = tpCurve.getCurveDomain(); VectorNd velocityDomain= tvCurve.getCurveDomain(); double angleMin = activeDomain[0]; double angleMax = activeDomain[1]; if(tpCurve.calcValue(passiveDomain[0]) >= 0.99){ angleMin = passiveDomain[0]; } if(tpCurve.calcValue(passiveDomain[1]) >= 0.99){ angleMax = passiveDomain[1]; } double jointMin = signOfJointAngle*angleMin + angleOffset; double jointMax = signOfJointAngle*angleMax + angleOffset; if(jointMin > jointMax){ double tmp = jointMin; jointMin=jointMax; jointMax=tmp; } double range = jointMax-jointMin; jointMin = jointMin -range*0.1; jointMax = jointMax +range*0.1; double jointDelta = (jointMax-jointMin) /((double)numberOfSamplePoints-1.); double velMin = omegaMax*signOfConcentricAnglularVelocity*velocityDomain[0]; double velMax = omegaMax*signOfConcentricAnglularVelocity*velocityDomain[1]; if(velMin > velMax){ double tmp = velMin; velMin = velMax; velMax = tmp; } double velRange = velMax-velMin; velMin = velMin-0.1*velRange; velMax = velMax+0.1*velRange; double velDelta = (velMax-velMin)/((double)numberOfSamplePoints-1.0); double angleAtMaxIsoTorque = angleAtOneNormActiveTorque; std::vector< std::vector < double > > matrix; std::vector < double > row(21); std::string header("jointAngle," "jointVelocity," "activation," "fiberAngle," "fiberAngularVelocity," "passiveTorqueAngleMultiplier," "activeTorqueAngleMultiplier," "torqueVelocityMultiplier," "activeTorque," "passiveTorque," "fiberTorque," "jointTorque," "fiberStiffness," "jointStiffness," "fiberActivePower," "fiberPassivePower," "fiberPower," "jointPower," "DjointTorqueDactivation," "DjointTorqueDjointAngularVelocity," "DjointTorqueDjointAngle"); double activation =1.0; double jointAngle = 0.; double jointVelocity = 0.; for(int i=0; i