#include "TorqueMuscleFittingToolkit.h" #include "coin/IpIpoptApplication.hpp" using namespace Ipopt; 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; void TorqueMuscleFittingToolkit:: fitTorqueMuscleParameters( Millard2016TorqueMuscle const &tqMcl, VectorNd const &jointAngle, VectorNd const &jointAngularVelocity, VectorNd const &jointTorque, double activationUpperBound, double passiveTorqueAngleMultiplierUpperBound, TorqueMuscleParameterFittingData ¶metersOfBestFit, bool verbose) { if(tqMcl.mMuscleCurvesAreDirty){ Millard2016TorqueMuscle* mutableTqMcl = const_cast(&tqMcl); mutableTqMcl->updateTorqueMuscleCurves(); } assert(jointAngle.rows() > 1); assert(jointAngularVelocity.rows() > 1); assert(jointTorque.rows() > 1); unsigned int nrows = jointAngle.rows(); double activationLowerBound = 0.0; assert(jointAngularVelocity.rows() == nrows); assert(jointTorque.rows() == nrows); assert(activationUpperBound > 0 && activationUpperBound <= 1.0); assert(activationLowerBound >= 0 && activationLowerBound= 0){ jointTorqueSingleSided[i] = jointTorque[i]; }else{ jointTorqueSingleSided[i] = 0.0; } } double tiso = tqMcl.getMaximumActiveIsometricTorque(); double omegaMax = tqMcl.getMaximumConcentricJointAngularVelocity(); double maxConcentricAngularVelocityData = 0; for(unsigned int i=0; i fabs(maxConcentricAngularVelocityData)){ maxConcentricAngularVelocityData = fabs(jointAngularVelocity[i]); } } //Set ta and tv blending variables to a small finite value so that //it is possible to solve for an activation of finite value when //calcTorqueMuscleDataFeatures is called double taLambda = sqrt(SQRTEPSILON); double tvLambda = sqrt(SQRTEPSILON); if(tqMcl.getActiveTorqueAngleCurveBlendingVariable() > taLambda){ taLambda = tqMcl.getActiveTorqueAngleCurveBlendingVariable(); } if(tqMcl.getTorqueAngularVelocityCurveBlendingVariable() > tvLambda){ tvLambda = tqMcl.getTorqueAngularVelocityCurveBlendingVariable(); } double tpLambda = 0.0; double taAngleScaling = tqMcl.mTaAngleScaling; double taAngleAtOneNormTorque = tqMcl.mAngleAtOneNormActiveTorque; double tvOmegaMaxScale = 1.0; double tpOffset = 0.0; double objectiveValue = 0.0; double constraintError = 0.0; TorqueMuscleDataFeatures tmf; omegaMax = fabs(tqMcl.getMaximumConcentricJointAngularVelocity() *tvOmegaMaxScale); //If the optimization run fails, the values from tmf are passed out to //the user to help them debug the problem. tqMcl.calcTorqueMuscleDataFeatures( jointTorqueSingleSided,jointAngle,jointAngularVelocity, taLambda,tpLambda,tvLambda, taAngleScaling,taAngleAtOneNormTorque,tpOffset,omegaMax, tiso,tmf); Millard2016TorqueMuscle* mutableTqMcl = const_cast(&tqMcl); SmartPtr fittingProblem = new FitTorqueMuscleParameters( jointAngle, jointAngularVelocity, jointTorqueSingleSided, activationUpperBound, passiveTorqueAngleMultiplierUpperBound, taLambda, tvLambda, *mutableTqMcl); SmartPtr app = IpoptApplicationFactory(); app->RethrowNonIpoptException(true); double solnTol = SQRTEPSILON; app->Options()->SetNumericValue("tol",solnTol); app->Options()->SetStringValue("mu_strategy","adaptive"); if(!verbose){ app->Options()->SetIntegerValue("print_level",0); } ApplicationReturnStatus status; objectiveValue = std::numeric_limits::infinity(); constraintError= std::numeric_limits::infinity(); bool converged = false; double taAngleScaleStart = 1.0; double tvOmegaMaxScaleStart =fabs(1.1*maxConcentricAngularVelocityData /tqMcl.getMaximumConcentricJointAngularVelocity()); double tisoScale = 1.0; bool updateStart = false; status = app->Initialize(); if(status == Solve_Succeeded){ status = app->OptimizeTNLP(fittingProblem); if(status == Solve_Succeeded){ converged = true; updateStart = true; taAngleScaling = fittingProblem ->getSolutionActiveTorqueAngleAngleScaling(); tvOmegaMaxScale = fittingProblem ->getSolutionTorqueAngularVelocityOmegaMaxScale(); tpLambda = fittingProblem ->getSolutionPassiveTorqueAngleBlendingParameter(); tpOffset = fittingProblem ->getSolutionPassiveTorqueAngleCurveOffset(); tisoScale = fittingProblem ->getSolutionMaximumActiveIsometricTorqueScale(); tiso = tisoScale * tiso; omegaMax = fabs(tvOmegaMaxScale *tqMcl.getMaximumConcentricJointAngularVelocity()); objectiveValue = fittingProblem->getObjectiveValue(); constraintError= fittingProblem->getConstraintError().norm(); tqMcl.calcTorqueMuscleDataFeatures( jointTorqueSingleSided,jointAngle,jointAngularVelocity, taLambda,tpLambda,tvLambda, taAngleScaling,taAngleAtOneNormTorque,tpOffset,omegaMax, tiso,tmf); } } if(converged){ parametersOfBestFit.fittingConverged = true; parametersOfBestFit.isTorqueMuscleActive = !(tmf.isInactive); parametersOfBestFit.indexAtMaximumActivation=tmf.indexOfMaxActivation; parametersOfBestFit.indexAtMinimumActivation=tmf.indexOfMinActivation; parametersOfBestFit.indexAtMaxPassiveTorqueAngleMultiplier =tmf.indexOfMaxPassiveTorqueAngleMultiplier; parametersOfBestFit.activeTorqueAngleBlendingVariable = taLambda; parametersOfBestFit.torqueVelocityBlendingVariable = tvLambda; parametersOfBestFit.passiveTorqueAngleBlendingVariable = tpLambda; parametersOfBestFit.passiveTorqueAngleCurveOffset = tpOffset; parametersOfBestFit.maximumActiveIsometricTorque = tiso; parametersOfBestFit.activeTorqueAngleAngleScaling = taAngleScaling; parametersOfBestFit.maximumAngularVelocity = fabs(omegaMax); parametersOfBestFit.summaryDataAtMaximumActivation =tmf.summaryAtMaxActivation; parametersOfBestFit.summaryDataAtMinimumActivation =tmf.summaryAtMinActivation; parametersOfBestFit.summaryDataAtMaximumPassiveTorqueAngleMultiplier =tmf.summaryAtMaxPassiveTorqueAngleMultiplier; }else{ parametersOfBestFit.fittingConverged = false; parametersOfBestFit.isTorqueMuscleActive = !(tmf.isInactive); parametersOfBestFit.indexAtMaximumActivation =numeric_limits::signaling_NaN(); parametersOfBestFit.indexAtMinimumActivation =numeric_limits::signaling_NaN(); parametersOfBestFit.indexAtMaxPassiveTorqueAngleMultiplier =numeric_limits::signaling_NaN(); parametersOfBestFit.activeTorqueAngleBlendingVariable = numeric_limits::signaling_NaN(); parametersOfBestFit.passiveTorqueAngleBlendingVariable = numeric_limits::signaling_NaN(); parametersOfBestFit.passiveTorqueAngleCurveOffset = numeric_limits::signaling_NaN(); parametersOfBestFit.torqueVelocityBlendingVariable = numeric_limits::signaling_NaN(); parametersOfBestFit.maximumActiveIsometricTorque = numeric_limits::signaling_NaN(); parametersOfBestFit.activeTorqueAngleAngleScaling = numeric_limits::signaling_NaN(); parametersOfBestFit.maximumAngularVelocity = numeric_limits::signaling_NaN(); parametersOfBestFit.summaryDataAtMaximumActivation =tmf.summaryAtMaxActivation; parametersOfBestFit.summaryDataAtMinimumActivation =tmf.summaryAtMinActivation; parametersOfBestFit.summaryDataAtMaximumPassiveTorqueAngleMultiplier =tmf.summaryAtMaxPassiveTorqueAngleMultiplier; } } FitTorqueMuscleParameters:: FitTorqueMuscleParameters( const RigidBodyDynamics::Math::VectorNd &jointAngle, const RigidBodyDynamics::Math::VectorNd &jointAngularVelocity, const RigidBodyDynamics::Math::VectorNd &jointTorque, double maxActivation, double maxPassiveTorqueAngleMultiplier, double taLambda, double tvLambda, Millard2016TorqueMuscle &tqMcl): mJointAngle(jointAngle), mJointAngularVelocity(jointAngularVelocity), mJointTorque(jointTorque), mTqMcl(tqMcl) { assert(jointTorque.rows() == jointAngle.rows()); assert(jointTorque.rows() == jointAngularVelocity.rows()); //Initialize all member variables mN = 5; mM = 3*jointTorque.rows(); double angleMin = std::numeric_limits< double >::max(); double omegaMin = std::numeric_limits< double >::max(); double angleMax = -std::numeric_limits< double >::max(); double omegaMax = -std::numeric_limits< double >::max(); double tauMax = 0; for(unsigned int i=0; i angleMax){ angleMax = mJointAngle[i]; } if(mJointAngularVelocity[i] < omegaMin){ omegaMin = mJointAngularVelocity[i]; } if(mJointAngularVelocity[i] > omegaMax){ omegaMax = mJointAngularVelocity[i]; } if(mJointTorque[i]*mTqMcl.getJointTorqueSign() > tauMax){ tauMax = mJointTorque[i]*mTqMcl.getJointTorqueSign(); } } double tvScale = max(fabs(omegaMin),fabs(omegaMax)) /fabs(mTqMcl.getMaximumConcentricJointAngularVelocity()); double taScale = fabs(angleMax-angleMin) /mTqMcl.getActiveTorqueAngleCurveWidth(); double tauScale = tauMax/fabs(mTqMcl.getMaximumActiveIsometricTorque()); mConIdxTauActMaxStart = 0; mConIdxTauActMaxEnd = jointTorque.rows()-1; mConIdxTauActMinStart = jointTorque.rows(); mConIdxTauActMinEnd = 2*jointTorque.rows()-1; mConIdxTauPassiveStart = 2*jointTorque.rows(); mConIdxTauPassiveEnd = 3*jointTorque.rows()-1; mMaxActivation = maxActivation; mMinActivation = 0.0; mMaxTp = maxPassiveTorqueAngleMultiplier; mTauIso = mTqMcl.getMaximumActiveIsometricTorque(); mTaLambda = taLambda; mTvLambda = tvLambda; mTaAngleAtOneNormTorque = mTqMcl.mAngleAtOneNormActiveTorque; mOmegaMax = mTqMcl.mOmegaMax; mTaAngleScaleStart = max(taScale, mTqMcl.getActiveTorqueAngleCurveAngleScaling()); mTvOmegaMaxScaleStart = max(tvScale, 1.); mTpLambdaStart = mTqMcl.getPassiveTorqueAngleCurveBlendingVariable(); mTpAngleOffsetStart = mTqMcl.getPassiveCurveAngleOffset(); mTauScalingStart = max(tauScale, 1.); mTaAngleScaleLB = 1.; mTvOmegaMaxScaleLB = 1.; mTpLambdaLB = 0.; mTpAngleOffsetLB = -M_PI*0.5; mTauScalingLB = 1.0; mTaAngleScaleUB = 2.0e19; mTvOmegaMaxScaleUB = 2.0e19; mTpLambdaUB = 1.0; mTpAngleOffsetUB = M_PI*0.5; mTauScalingUB = 2.0e19; mIndexTaAngleScale = 0; mIndexTvOmegaMaxScale = 1; mIndexTpLambda = 2; mIndexTpOffset = 3; mIndexTauScaling = 4; //Initialize vectors mConstraintErrors.resize(mM); mXOffset.resize(mN); mWeights.resize(mN); for(unsigned int i=0; i= 0 m = mM; //Constraint Jacobian //This is a dense Jacobian nnz_jac_g = mM*mN; //Hessian //This is a symmetric matrix 5x5 matrix, but its densley populated so //we must fill in the bottom left triangle. nnz_h_lag = 15; index_style = Ipopt::TNLP::C_STYLE; return true; } bool FitTorqueMuscleParameters:: get_bounds_info(Ipopt::Index n, Ipopt::Number *x_l, Ipopt::Number *x_u, Ipopt::Index m, Ipopt::Number *g_l, Ipopt::Number *g_u) { assert(n==mN); assert(m==mM); x_l[0] = mTaAngleScaleLB; x_l[1] = mTvOmegaMaxScaleLB; x_l[2] = mTpLambdaLB; x_l[3] = mTpAngleOffsetLB; x_l[4] = mTauScalingLB; x_u[0] = mTaAngleScaleUB; x_u[1] = mTvOmegaMaxScaleUB; x_u[2] = mTpLambdaUB; x_u[3] = mTpAngleOffsetUB; x_u[4] = mTauScalingUB; //The optimization routine will only strengthen //the muscle to satisfy the constraint - but it //will not weaken it. double tauActMaxLB = 0; double tauActMaxUB = 0; double tauActMinLB = 0; double tauActMinUB = 0; double tauPassiveLB = 0; double tauPassiveUB = 0; if(mTqMcl.getJointTorqueSign() > 0){ tauActMaxLB = 0; tauActMaxUB = 2e19; tauActMinLB = -2e19; tauActMinUB = 0; tauPassiveLB = -2e19; tauPassiveUB = 0; }else{ tauActMaxLB = -2.e19; tauActMaxUB = 0.; tauActMinLB = 0; tauActMinUB = 2e19; tauPassiveLB = -2e19; tauPassiveUB = 0; } for(unsigned int i = mConIdxTauActMaxStart; i <= mConIdxTauActMaxEnd; ++i){ g_l[i] = tauActMaxLB; g_u[i] = tauActMaxUB; } for(unsigned int i = mConIdxTauActMinStart; i <= mConIdxTauActMinEnd; ++i){ g_l[i] = tauActMinLB; g_u[i] = tauActMinUB; } for(unsigned int i = mConIdxTauPassiveStart; i <= mConIdxTauPassiveEnd; ++i){ g_l[i] = tauPassiveLB; g_u[i] = tauPassiveUB; } return true; } bool FitTorqueMuscleParameters:: get_starting_point(Ipopt::Index n, bool init_x, Ipopt::Number *x, bool init_z, Ipopt::Number *z_L, Ipopt::Number *z_U, Ipopt::Index m, bool init_lambda, Ipopt::Number *lambda) { assert(n==mN); x[0] = mTaAngleScaleStart; x[1] = mTvOmegaMaxScaleStart; x[2] = mTpLambdaStart; x[3] = mTpAngleOffsetStart; x[4] = mTauScalingStart; init_x = true; init_z = false; init_lambda = false; mDtp_Dx.resize(mN); return true; } bool FitTorqueMuscleParameters:: eval_f(Ipopt::Index n, const Ipopt::Number *x, bool new_x, Ipopt::Number &obj_value) { assert(n==mN); obj_value = 0.; double xUpd = 0.; for(unsigned i=0; i