%-------------------------------------------------------------------------%
% Copyright (c) 2019 Modenese L., Ceseracciu, E., Reggiani M., Lloyd, D.G.%
% %
% Licensed under the Apache License, Version 2.0 (the "License"); %
% you may not use this file except in compliance with the License. %
% You may obtain a copy of the License at %
% http://www.apache.org/licenses/LICENSE-2.0. %
% %
% Unless required by applicable law or agreed to in writing, software %
% distributed under the License is distributed on an "AS IS" BASIS, %
% WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or %
% implied. See the License for the specific language governing %
% permissions and limitations under the License. %
% %
% Author: Luca Modenese, January 2015 %
% email: l.modenese@imperial.ac.uk %
% ----------------------------------------------------------------------- %
%
% This function optimizes the muscle parameters as described in Modenese L,
% Ceseracciu E, Reggiani M, Lloyd DG (2015). Estimation of
% musculotendon parameters for scaled and subject specific musculoskeletal
% models using an optimization technique. Journal of Biomechanics (submitted)
% and prints the results to command window.
% Also it stores information about the optimization in the structure SimInfo
function [osimModel_opt, SimInfo] = optimMuscleParams(osimModel_ref_filepath, osimModel_targ_filepath, N_eval, log_folder)
% import opensim libraries
import org.opensim.modeling.*
% results file identifier
res_file_id_exp = ['_N',num2str(N_eval)];
% import models
osimModel_ref = Model(osimModel_ref_filepath);
osimModel_targ = Model(osimModel_targ_filepath);
% models details
[~, name, ext] = fileparts(osimModel_targ_filepath);
% assigning new name to the model
osimModel_opt_name = [name,'_opt',res_file_id_exp,ext];
osimModel_targ.setName(osimModel_opt_name);
% initializing log file
if ~isdir(log_folder)
warning([log_folder,' does not exist. It will be created']);
mkdir(log_folder);
end
log_filepath = fullfile(log_folder,[name,'_opt',res_file_id_exp,'.log']);
% cleaning file (otherwise it appends)
fopen(log_filepath,'w+'); fclose all;
% set up diary, without writing at the moment
diary(log_filepath); diary off
% get muscles
muscles = osimModel_ref.getMuscles;
muscles_scaled = osimModel_targ.getMuscles;
% init model
% si = osimModel_ref.initSystem;
% si_scaled = osimModel_targ.initSystem;
% initialize with recognizable values
LmOptLts_opt = ones(muscles.getSize,2)*(-1000);
for n_mus = 0:muscles.getSize-1
tic
% current muscle name (here so that it is possible to choose a single
% muscle when developing.
curr_mus_name = char(muscles.get(n_mus).getName);
display(['processing mus ',num2str(n_mus+1),': ',char(curr_mus_name)]);
% import muscles
curr_mus = muscles.get(n_mus);
curr_mus_scaled = muscles_scaled.get(curr_mus_name);
% extracting the muscle parameters from reference model
LmOptLts = [curr_mus.getOptimalFiberLength, curr_mus.getTendonSlackLength];
PenAngleOpt = curr_mus.getPennationAngleAtOptimalFiberLength();
Mus_ref = sampleMuscleQuantities(osimModel_ref,curr_mus,'all',N_eval);
% calculating minimum fiber length before having pennation 90 deg
% acos(0.1) = 1.47 red = 84 degrees, chosen as in OpenSim
limitPenAngle = acos(0.1);
% this is the minimum length the fiber can be for geometrical reasons.
LfibNorm_min = sin(PenAngleOpt)/sin(limitPenAngle);
% LfibNorm as calculated above can be shorter than the minimum length
% at which the fiber can generate force (taken to be 0.5 Zajac 1989)
if (LfibNorm_min<0.5)==1
LfibNorm_min = 0.5;
end
% checking the muscle configuration that do not respect the condition.
LfibNorm_ref = Mus_ref(:,2);
okList = (LfibNorm_ref>LfibNorm_min);
% keeping only acceptable values
LfibNorm_ref = LfibNorm_ref(okList);
LtenNorm_ref = Mus_ref(okList,3)/LmOptLts(2);
%Ffib = Mus_ref(okList,4)/curr_mus.getMaxIsometricForce;
MTL_ref = Mus_ref(okList,1);
penAngle_ref = Mus_ref(okList,5);
LfibNormOnTen_ref = LfibNorm_ref.*cos(penAngle_ref);
% in the target only MTL is needed for all muscles
MTL_targ = sampleMuscleQuantities(osimModel_targ,curr_mus_scaled,'MTL',N_eval);
evalTotPoints = length(MTL_targ);
MTL_targ = MTL_targ(okList)';
evalOkPoints = length(MTL_targ);
% The problem to be solved is:
% [LmNorm*cos(penAngle) LtNorm]*[Lmopt Lts]' = MTL;
% written as Ax = b
A = [LfibNormOnTen_ref LtenNorm_ref];
b = MTL_targ;
% ===== LINSOL =======
% solving the problem to calculate the muscle param
x = A\b;
LmOptLts_opt(n_mus+1,:) = x;
% checking the results
if min(x)<=0
diary on
% informing the user
display(['Negative value estimated for muscle parameter of muscle ',curr_mus_name]);
display( ' Lm Opt Lts' );
display(['Template model : ',num2str(LmOptLts)]);
display(['Optimized param : ',num2str(LmOptLts_opt(n_mus+1,:))]);
% ===== IMPLEMENTING CORRECTIONS IF ESTIMATION IS NOT CORRECT =======
% first try lsqnonlin
x = lsqnonneg(A,b);
LmOptLts_opt(n_mus+1,:) = x;
display(['Opt params (lsqnonneg): ',num2str(LmOptLts_opt(n_mus+1,:))]);
% In our tests, if something goes wrong is generally tendon slack
% length becoming negative or zero because tendon length doesn't change
% throughout the range of motion, so lowering the rank of A.
% if x(2)==0
if min(x)<=0
if (max(Mus_ref(okList,3))-min(Mus_ref(okList,3)))<0.0001
display('Tendon length not changing throughout range of motion')
end
% calculating proportion of tendon and fiber
% Lfib_fraction = LfibNormOnTen*LmOptLts(1)./MTL;
Lten_fraction = Mus_ref(okList,3)./MTL_ref;
Lten_targ = (Lten_fraction.*MTL_targ);
% first round: optimizing Lopt maintaing the proportion of
% tendon as in the reference model
A_1 = LfibNormOnTen_ref;
b_1 = (MTL_targ-Lten_targ);
x(1) = A_1\b_1;
% second round: using the optimized Lopt to recalculate Lts
b_2 = MTL_targ-A_1*x(1);
A_2 = LtenNorm_ref;
x(2) = A_2\b_2;
LmOptLts_opt(n_mus+1,:) = x;
end
diary off
end
% Here tests about '\' against optimizers were implemented.
% calculating the error (sum of squared errors)
fval = norm(A*x-b).^2.0;
% update muscles from scaled model
curr_mus_scaled.setOptimalFiberLength(LmOptLts_opt(n_mus+1,1));
curr_mus_scaled.setTendonSlackLength(LmOptLts_opt(n_mus+1,2));
% PRINT LOGS
diary on
display(' ');
display(['Calculated optimized muscle parameters for ', char(curr_mus),' in ',num2str(toc),' seconds.'])
display( ' Lm Opt Lts' );
display(['Template model : ',num2str(LmOptLts)]);
display(['Optimized param : ',num2str(LmOptLts_opt(n_mus+1,:))]);
display(['Nr of eval points : ',num2str(evalOkPoints), '/',num2str(evalTotPoints),' used'])
display(['fval : ',num2str(fval)]);
display(['var from template [%]: ',num2str(100*(abs(LmOptLts-LmOptLts_opt(n_mus+1,:)))./LmOptLts),'%'])
display(' ');
diary off
% SIMULATION INFO AND RESULTS
SimInfo.colheader(n_mus+1) = {char(curr_mus)};
SimInfo.LmOptLts_ref(1:2,n_mus+1) = LmOptLts;
SimInfo.LmOptLts_opt(1:2,n_mus+1) = LmOptLts_opt(n_mus+1,:);
SimInfo.varPercLmOptLts(1:2,n_mus+1) = 100*(abs(LmOptLts-LmOptLts_opt(n_mus+1,:)))./LmOptLts;
SimInfo.sampledEvalPoints(n_mus+1) = evalOkPoints;
SimInfo.usedEvalPoints(n_mus+1) = evalTotPoints;
SimInfo.fval(n_mus+1) = fval;
end
% assigning optimized model as output
osimModel_opt = osimModel_targ;
end