From 011877291f3be70adc4f50c5465fea6e580f5767 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Mon, 15 May 2023 18:24:04 -0500 Subject: [PATCH 01/72] Added metabolic cost calculation --- .../MuscleCalculations/calcMetabolicCost.m | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 src/core/MuscleCalculations/calcMetabolicCost.m diff --git a/src/core/MuscleCalculations/calcMetabolicCost.m b/src/core/MuscleCalculations/calcMetabolicCost.m new file mode 100644 index 000000000..3992d47eb --- /dev/null +++ b/src/core/MuscleCalculations/calcMetabolicCost.m @@ -0,0 +1,64 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function metabolicCost = calcMetabolicCost(time, statePositions, ... + muscleActivations, params) + +for indx = 1 : numel(params.integral.minimizing) + if strcmpi(params.integral.minimizing{indx}.type, 'metabolic_cost') + import org.opensim.modeling.* + model = Model(params.model); + for i = 1 : params.numMuscles + controller = PrescribedController(); + controller.addActuator(model.getMuscles().get(params.muscleNames{i})); + controlFunction = PiecewiseLinearFunction(); + for j = 1:size(muscleActivations, 1) + controlFunction.addPoint(time(j), muscleActivations(j, i)); + end + controller.prescribeControlForActuator(params.muscleNames{i}, ... + controlFunction); + model.addComponent(controller); + end + + state = model.initSystem(); + for i = 1:size(muscleActivations, 1) + for j = 1 : size(params.coordinateNames, 2) + if ~model.getCoordinateSet.get(params.coordinateNames(j)). .... + get_locked + model.getCoordinateSet.get(params.coordinateNames(j)). ... + setValue(state, statePositions(i, j)); + end + end + state.setTime(time(i)); + model.realizeDynamics(state); + model.equilibrateMuscles(state); + tempTotalCost = model.getProbeSet().get(0).getProbeOutputs(state); + metabolicCost(i, :) = tempTotalCost.get(0); + end + end +end +end \ No newline at end of file From c9025c696ce3c3d53ea8dae5bf5a2e7c41fba37e Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Mon, 15 May 2023 18:25:01 -0500 Subject: [PATCH 02/72] Added ability to include metabolic cost as a cost term --- .../calcDesignOptimizationIntegrand.m | 3 ++ .../calcSynergyBasedModeledValues.m | 2 ++ .../calcMinimizingMetabolicCost.m | 32 +++++++++++++++++++ 3 files changed, 37 insertions(+) create mode 100644 src/core/TreatmentOptimization/IntegrandTerms/calcMinimizingMetabolicCost.m diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index 91c0464eb..ecfdbb266 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -55,6 +55,9 @@ integrand = cat(2, integrand, ... calcMinimizingJointJerkIntegrand(values.controlJerks, ... params, costTerm.coordinate)); + case "metabolic_cost" + integrand = cat(2, integrand, ... + calcMinimizingMetabolicCost(phaseout.metabolicCost)); otherwise throw(MException('', ['Cost term type ' costTerm.type ... ' does not exist for this tool.'])) diff --git a/src/TrackingOptimization/calcSynergyBasedModeledValues.m b/src/TrackingOptimization/calcSynergyBasedModeledValues.m index e69237637..7ff654d19 100644 --- a/src/TrackingOptimization/calcSynergyBasedModeledValues.m +++ b/src/TrackingOptimization/calcSynergyBasedModeledValues.m @@ -35,6 +35,8 @@ calcNormalizedMuscleFiberLengthsAndVelocities(params, ... ones(1, params.numMuscles), ones(1, params.numMuscles)); phaseout.muscleActivations = calcMuscleActivationFromSynergies(values); +phaseout.metabolicCost = calcMetabolicCost(values.time, ... + values.statePositions, phaseout.muscleActivations, params); muscleJointMoments = calcMuscleJointMoments(params, ... phaseout.muscleActivations, phaseout.normalizedFiberLength, ... phaseout.normalizedFiberVelocity); diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcMinimizingMetabolicCost.m b/src/core/TreatmentOptimization/IntegrandTerms/calcMinimizingMetabolicCost.m new file mode 100644 index 000000000..aba62da00 --- /dev/null +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcMinimizingMetabolicCost.m @@ -0,0 +1,32 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function cost = calcMinimizingMetabolicCost(metabolicCost) + +cost = calcMinimizingCostArrayTerm(metabolicCost); +end + From d4c5e7559f1afb71e639b9c45f9367e39b9de1c6 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 15 May 2023 19:40:36 -0500 Subject: [PATCH 03/72] Fix preprocessing file manipulation --- src/Preprocessing/splitIntoTrials.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/Preprocessing/splitIntoTrials.m b/src/Preprocessing/splitIntoTrials.m index a45fc5597..2ac1e7494 100644 --- a/src/Preprocessing/splitIntoTrials.m +++ b/src/Preprocessing/splitIntoTrials.m @@ -36,6 +36,7 @@ function splitIntoTrials(timePairs, inputSettings, outputSettings) for i=1:length(filesToSection) delete(filesToSection(i)); end +delete(fullfile(outputDir, emgOutputDir, trialName + ".sto")); moveMAFilesToSeparateDirectories(trialName, outputDir, ... maOutputDir, timePairs) end @@ -173,7 +174,7 @@ function throwCantFindMAFileException(fileName) function moveMAFilesToSeparateDirectories(trialName, outputDir, ... maOutputDir, timePairs) -for i=1:length(timePairs) +for i=1:size(timePairs, 1) mkdir(fullfile(outputDir, maOutputDir, trialName + "_" ... + i)); files = dir(fullfile(outputDir, maOutputDir)); From d02741fb10b3d2ed0842733ea2c4122019909e26 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 15 May 2023 20:27:38 -0500 Subject: [PATCH 04/72] Account for extra EMG columns in MTP, "_moment" suffix in ID --- .../parseMuscleTendonPersonalizationSettingsTree.m | 1 + src/core/parse/parseMtpNcpSharedInputs.m | 6 ++++++ 2 files changed, 7 insertions(+) diff --git a/src/MuscleTendonPersonalization/parseMuscleTendonPersonalizationSettingsTree.m b/src/MuscleTendonPersonalization/parseMuscleTendonPersonalizationSettingsTree.m index f568ae441..1b877362e 100644 --- a/src/MuscleTendonPersonalization/parseMuscleTendonPersonalizationSettingsTree.m +++ b/src/MuscleTendonPersonalization/parseMuscleTendonPersonalizationSettingsTree.m @@ -69,6 +69,7 @@ [inputs.fullEmgData, inputs.emgDataColumnNames] = parseMtpStandard(emgDataFileNames); collectedEmgGroupNamesMembers = ismember(inputs.emgDataColumnNames, collectedEmgGroupNames); inputs.emgData = inputs.fullEmgData(:, collectedEmgGroupNamesMembers, :); +inputs.emgDataColumnNames = inputs.emgDataColumnNames(collectedEmgGroupNamesMembers); firstEmgDataExpanded = expandEmgDatas(inputs.model, squeeze(inputs.emgData(1, :, :)), collectedEmgGroupNames, inputs.muscleNames); inputs.emgDataExpanded = zeros(size(inputs.emgData, 1), size(firstEmgDataExpanded, 1), size(firstEmgDataExpanded, 2)); inputs.emgDataExpanded(1, :, :) = firstEmgDataExpanded; diff --git a/src/core/parse/parseMtpNcpSharedInputs.m b/src/core/parse/parseMtpNcpSharedInputs.m index 0aa5d1559..9823ad79a 100644 --- a/src/core/parse/parseMtpNcpSharedInputs.m +++ b/src/core/parse/parseMtpNcpSharedInputs.m @@ -40,6 +40,12 @@ [inputs.inverseDynamicsMoments, ... inputs.inverseDynamicsMomentsColumnNames] = ... parseMtpStandard(inverseDynamicsFileNames); +for i = 1:length(inputs.inverseDynamicsMomentsColumnNames) + if endsWith(inputs.inverseDynamicsMomentsColumnNames(i), "_moment") + inputs.inverseDynamicsMomentsColumnNames(i) = extractBefore( ... + inputs.inverseDynamicsMomentsColumnNames(i), "_moment"); + end +end directories = findFirstLevelSubDirectoriesFromPrefixes(fullfile( ... dataDirectory, "MAData"), inputs.prefixes); [inputs.muscleTendonLength, inputs.muscleTendonColumnNames] = ... From 572c04f1a6afe9231e8142dd657fc0bbaa82f32d Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 15 May 2023 20:56:50 -0500 Subject: [PATCH 05/72] Fix saving MTP results to .osimx --- src/core/osimx/buildRcnlMuscle.m | 59 +++++++++++-------- ...riteMuscleTendonPersonalizationOsimxFile.m | 3 +- 2 files changed, 38 insertions(+), 24 deletions(-) diff --git a/src/core/osimx/buildRcnlMuscle.m b/src/core/osimx/buildRcnlMuscle.m index c1e00a861..7d432acdc 100644 --- a/src/core/osimx/buildRcnlMuscle.m +++ b/src/core/osimx/buildRcnlMuscle.m @@ -15,7 +15,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond, Marleny Vega % +% Author(s): Claire V. Hammond, Marleny Vega, Spencer Williams % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -42,34 +42,47 @@ muscles = muscleObjects.RCNLMuscle; muscles{i}.Attributes.name = convertStringsToChars(muscleName); -muscles{i}.electromechanical_delay.Comment = 'Optimized electromechanical delay'; -muscles{i}.electromechanical_delay.Text = convertStringsToChars( ... - num2str(muscleParameters.electromechanicalDelay, 15)); +if isfield(muscleParameters, 'electromechanicalDelay') + muscles{i}.electromechanical_delay.Comment = 'Optimized electromechanical delay'; + muscles{i}.electromechanical_delay.Text = convertStringsToChars( ... + num2str(muscleParameters.electromechanicalDelay, 15)); +end -muscles{i}.activation_time_constant.Comment = 'Optimized activation time constant'; -muscles{i}.activation_time_constant.Text = convertStringsToChars( ... - num2str(muscleParameters.activationTimeConstant, 15)); +if isfield(muscleParameters, 'activationTimeConstant') + muscles{i}.activation_time_constant.Comment = 'Optimized activation time constant'; + muscles{i}.activation_time_constant.Text = convertStringsToChars( ... + num2str(muscleParameters.activationTimeConstant, 15)); +end -muscles{i}.activation_nonlinearity_constant.Comment = 'Optimized activation nonlinearity constant'; -muscles{i}.activation_nonlinearity_constant.Text = convertStringsToChars( ... - num2str(muscleParameters.activationNonlinearityConstant, 15)); +if isfield(muscleParameters, 'activationNonlinearityConstant') + muscles{i}.activation_nonlinearity_constant.Comment = 'Optimized activation nonlinearity constant'; + muscles{i}.activation_nonlinearity_constant.Text = convertStringsToChars( ... + num2str(muscleParameters.activationNonlinearityConstant, 15)); +end -muscles{i}.emg_scale_factor.Comment = 'Optimized EMG scale factor'; -muscles{i}.emg_scale_factor.Text = convertStringsToChars( ... - num2str(muscleParameters.emgScaleFactor, 15)); +if isfield(muscleParameters, 'emgScaleFactor') + muscles{i}.emg_scale_factor.Comment = 'Optimized EMG scale factor'; + muscles{i}.emg_scale_factor.Text = convertStringsToChars( ... + num2str(muscleParameters.emgScaleFactor, 15)); +end -muscles{i}.optimal_fiber_length.Comment = 'Optimized optimal fiber length'; -muscles{i}.optimal_fiber_length.Text = convertStringsToChars( ... - num2str(muscleParameters.optimalFiberLength, 15)); +if isfield(muscleParameters, 'optimalFiberLength') + muscles{i}.optimal_fiber_length.Comment = 'Optimized optimal fiber length'; + muscles{i}.optimal_fiber_length.Text = convertStringsToChars( ... + num2str(muscleParameters.optimalFiberLength, 15)); +end -muscles{i}.tendon_slack_length.Comment = 'Optimized tendon slack length'; -muscles{i}.tendon_slack_length.Text = convertStringsToChars( ... - num2str(muscleParameters.tendonSlackLength, 15)); +if isfield(muscleParameters, 'tendonSlackLength') + muscles{i}.tendon_slack_length.Comment = 'Optimized tendon slack length'; + muscles{i}.tendon_slack_length.Text = convertStringsToChars( ... + num2str(muscleParameters.tendonSlackLength, 15)); +end -muscles{i}.max_isometric_force.Comment = 'Optimized max isometric force'; -muscles{i}.max_isometric_force.Text = convertStringsToChars( ... - num2str(muscleParameters.maxIsometricForce, 15)); +if isfield(muscleParameters, 'maxIsometricForce') + muscles{i}.max_isometric_force.Comment = 'Optimized max isometric force'; + muscles{i}.max_isometric_force.Text = convertStringsToChars( ... + num2str(muscleParameters.maxIsometricForce, 15)); +end osimx.NMSMPipelineDocument.OsimxModel.RCNLMuscleSet.objects.RCNLMuscle = muscles; end - diff --git a/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m b/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m index 516fea45d..692a1f213 100644 --- a/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m +++ b/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m @@ -43,7 +43,8 @@ function writeMuscleTendonPersonalizationOsimxFile(modelFileName, ... [~, name, ~] = fileparts(modelFileName); outfile = fullfile(results_directory, strcat(name, "_mtp.xml")); end - +osimx.modelName = name; +osimx.model = modelFileName; for i = 1:length(muscleNames) muscleParams = makeMuscleParams(model, muscleNames(i), optimizedParams, i); osimx.muscles.(muscleNames(i)) = muscleParams; From d080cb76097f28212ccadd0eb535a165854e3d49 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" Date: Mon, 15 May 2023 21:28:21 -0700 Subject: [PATCH 06/72] changed cost term format --- .../computeDesignOptimizationMainFunction.m | 5 ++ .../getContinuousCostTerms.m | 52 ----------------- .../getDiscreteCostTerms.m | 58 ------------------- .../getTreatmentOptimizationInputs.m | 4 +- 4 files changed, 8 insertions(+), 111 deletions(-) delete mode 100644 src/core/TreatmentOptimization/getContinuousCostTerms.m delete mode 100644 src/core/TreatmentOptimization/getDiscreteCostTerms.m diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index 210c97f9d..6cd25b275 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -48,4 +48,9 @@ bounds.parameter.upper = 0.5 * ones(1, length(inputs.minParameter)); end end +for i = 1:length(inputs.userDefinedParameters) + bounds.parameter.lower = [bounds.parameter.lower, inputs.userDefinedParameters{i}.lower]; + bounds.parameter.upper = [bounds.parameter.upper, inputs.userDefinedParameters{i}.upper]; +end +end end diff --git a/src/core/TreatmentOptimization/getContinuousCostTerms.m b/src/core/TreatmentOptimization/getContinuousCostTerms.m deleted file mode 100644 index 42832f5c4..000000000 --- a/src/core/TreatmentOptimization/getContinuousCostTerms.m +++ /dev/null @@ -1,52 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function inputs = getContinuousCostTerms(tree, inputs) -trackingIntegralTermsTree = getFieldByNameOrError(tree, ... - 'RCNLTrackingCostTerms'); -if isfield(trackingIntegralTermsTree.RCNLCostTermSet.objects, 'RCNLCostTerm') -rcnlCostTermTree = ... - trackingIntegralTermsTree.RCNLCostTermSet.objects.RCNLCostTerm; -if length(rcnlCostTermTree) > 1 - inputs.integral.tracking = parseRcnlCostTermSet(rcnlCostTermTree); -else - inputs.integral.tracking = parseRcnlCostTermSet({rcnlCostTermTree}); -end -end - -minimizingIntegralTermsTree = getFieldByNameOrError(tree, ... - 'RCNLMinimizationCostTerms'); -if isfield(minimizingIntegralTermsTree.RCNLCostTermSet.objects, 'RCNLCostTerm') -rcnlCostTermTree = ... - minimizingIntegralTermsTree.RCNLCostTermSet.objects.RCNLCostTerm; -if length(rcnlCostTermTree) > 1 - inputs.integral.minimizing = parseRcnlCostTermSet(rcnlCostTermTree); -else - inputs.integral.minimizing = parseRcnlCostTermSet({rcnlCostTermTree}); -end -end -end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/getDiscreteCostTerms.m b/src/core/TreatmentOptimization/getDiscreteCostTerms.m deleted file mode 100644 index fe2786d9c..000000000 --- a/src/core/TreatmentOptimization/getDiscreteCostTerms.m +++ /dev/null @@ -1,58 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function inputs = getDiscreteCostTerms(tree, inputs) -trackingDiscreteTermsTree = getFieldByName(tree, 'RCNLTrackingCostTerms'); -if isstruct(trackingDiscreteTermsTree) - if isfield(trackingDiscreteTermsTree.RCNLCostTermSet.objects, 'RCNLCostTerm') - rcnlCostTermTree = ... - trackingDiscreteTermsTree.RCNLCostTermSet.objects.RCNLCostTerm; - if length(rcnlCostTermTree) > 1 - inputs.discrete.tracking = ... - parseRcnlCostTermSet(rcnlCostTermTree); - else - inputs.discrete.tracking = ... - parseRcnlCostTermSet({rcnlCostTermTree}); - end - end -end - -minimizingDiscreteTermsTree = getFieldByName(tree, 'RCNLMinimizationCostTerms'); -if isstruct(minimizingDiscreteTermsTree) - if isfield(minimizingDiscreteTermsTree.RCNLCostTermSet.objects, 'RCNLCostTerm') - rcnlCostTermTree = ... - minimizingDiscreteTermsTree.RCNLCostTermSet.objects.RCNLCostTerm; - if length(rcnlCostTermTree) > 1 - inputs.discrete.minimizing = ... - parseRcnlCostTermSet(rcnlCostTermTree); - else - inputs.discrete.minimizing = ... - parseRcnlCostTermSet({rcnlCostTermTree}); - end - end -end -end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m index be1e19175..a807f1926 100644 --- a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m @@ -60,6 +60,8 @@ parseElementTextByNameOrAlternate(tree, "optimizeSynergyVectors", 0)); inputs = parseTreatmentOptimizationDataDirectory(tree, inputs); inputs.initialGuess = getGpopsInitialGuess(tree); +inputs.costTerms = parseRcnlCostTermSet( ... + getFieldByNameOrError(tree, 'RCNLCostTermSet')); inputs = getContinuousCostTerms(getFieldByNameOrError(tree, ... 'RCNLContinuousCostTermSet'), inputs); inputs = getDiscreteCostTerms(getFieldByName(tree, ... @@ -74,4 +76,4 @@ else inputs.contactSurfaces = {}; end -end \ No newline at end of file +end From 4716dd0e423999ad2092cb7c6f4415f2c767516f Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" Date: Mon, 15 May 2023 21:29:50 -0700 Subject: [PATCH 07/72] fixed TO inputs cost term --- .../TreatmentOptimization/getTreatmentOptimizationInputs.m | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m index a807f1926..33769e1c0 100644 --- a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m @@ -62,10 +62,6 @@ inputs.initialGuess = getGpopsInitialGuess(tree); inputs.costTerms = parseRcnlCostTermSet( ... getFieldByNameOrError(tree, 'RCNLCostTermSet')); -inputs = getContinuousCostTerms(getFieldByNameOrError(tree, ... - 'RCNLContinuousCostTermSet'), inputs); -inputs = getDiscreteCostTerms(getFieldByName(tree, ... - 'RCNLDiscreteCostTermSet'), inputs); inputs.path = getPathConstraintTerms(tree); inputs.terminal = getTerminalConstraintTerms(tree); contactSurfaces = getFieldByName(inputs.osimx, "contactSurface"); From 9a5673d164dbae49b0da5e64a008fdd88da9e28b Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Mon, 15 May 2023 23:45:30 -0500 Subject: [PATCH 08/72] fixed cost term inputs --- src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m index 33769e1c0..ba90bab8f 100644 --- a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m @@ -61,7 +61,7 @@ inputs = parseTreatmentOptimizationDataDirectory(tree, inputs); inputs.initialGuess = getGpopsInitialGuess(tree); inputs.costTerms = parseRcnlCostTermSet( ... - getFieldByNameOrError(tree, 'RCNLCostTermSet')); + getFieldByNameOrError(tree, 'RCNLCostTermSet').RCNLCostTerm); inputs.path = getPathConstraintTerms(tree); inputs.terminal = getTerminalConstraintTerms(tree); contactSurfaces = getFieldByName(inputs.osimx, "contactSurface"); From 5e7ccabfffaa965bc2d357bb6e942642d8c4a0ac Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Tue, 16 May 2023 00:46:14 -0500 Subject: [PATCH 09/72] changed cost terms --- .../oXIaZ30PV4AuIRRG1GZvJ_SIyRMd.xml | 2 + .../oXIaZ30PV4AuIRRG1GZvJ_SIyRMp.xml | 2 + .../8fabh-WBmBGbDz7_h2NqYd4ovLcd.xml | 2 + .../8fabh-WBmBGbDz7_h2NqYd4ovLcp.xml | 2 + .../liHb9kbr54OOKjgYDq6G1l2UsfId.xml | 6 +++ .../liHb9kbr54OOKjgYDq6G1l2UsfIp.xml | 2 + .../NDPaR6DsgCrsBoJWF55J1vGaXlAd.xml | 2 + .../NDPaR6DsgCrsBoJWF55J1vGaXlAp.xml | 2 + .../calcDesignOptimizationIntegrand.m | 29 ++++++-------- ...puteDesignOptimizationContinuousFunction.m | 14 ++++--- .../computeDesignOptimizationMainFunction.m | 9 ++--- .../SetupBounds/getIntegralBounds.m | 29 +++++++------- .../costTerms/isMinimizationCostTerm.m | 38 +++++++++++++++++++ .../costTerms/isTrackingCostTerm.m | 38 +++++++++++++++++++ .../TreatmentOptimization/scaleToBounds.m | 1 - 15 files changed, 136 insertions(+), 42 deletions(-) create mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMd.xml create mode 100644 resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMp.xml create mode 100644 resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcd.xml create mode 100644 resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcp.xml create mode 100644 resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfId.xml create mode 100644 resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfIp.xml create mode 100644 resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAd.xml create mode 100644 resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAp.xml create mode 100644 src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m create mode 100644 src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMd.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMd.xml new file mode 100644 index 000000000..78ad5b1a1 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMp.xml b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMp.xml new file mode 100644 index 000000000..7a38a9153 --- /dev/null +++ b/resources/project/EEtUlUb-dLAdf0KpMVivaUlztwA/oXIaZ30PV4AuIRRG1GZvJ_SIyRMp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcd.xml b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcd.xml new file mode 100644 index 000000000..a75f7a81b --- /dev/null +++ b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcp.xml b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcp.xml new file mode 100644 index 000000000..842de6ab3 --- /dev/null +++ b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/8fabh-WBmBGbDz7_h2NqYd4ovLcp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfId.xml b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfId.xml new file mode 100644 index 000000000..7a6326b99 --- /dev/null +++ b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfId.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfIp.xml b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfIp.xml new file mode 100644 index 000000000..a84fd8d2c --- /dev/null +++ b/resources/project/NDPaR6DsgCrsBoJWF55J1vGaXlA/liHb9kbr54OOKjgYDq6G1l2UsfIp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAd.xml b/resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAd.xml new file mode 100644 index 000000000..a75f7a81b --- /dev/null +++ b/resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAd.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAp.xml b/resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAp.xml new file mode 100644 index 000000000..ecec5d64b --- /dev/null +++ b/resources/project/_2w7KV6EHc1SMHPJ5sv5BJ_TE_4/NDPaR6DsgCrsBoJWF55J1vGaXlAp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index 91c0464eb..114a81fdf 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -25,36 +25,29 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function integrand = calcDesignOptimizationIntegrand(values, params, ... - phaseout) +function integrand = calcDesignOptimizationIntegrand(values, params) integrand = []; -for i = 1:length(params.integral.tracking) - costTerm = params.integral.tracking{i}; +for i = 1:length(params.costTerms) + costTerm = params.costTerms{i}; if costTerm.isEnabled switch costTerm.type - case "coordinate" + case "coordinate_tracking" integrand = cat(2, integrand, ... calcTrackingCoordinateIntegrand(params, ... values.time, values.statePositions, ... costTerm.coordinate)); - case "controller" + case "controller_tracking" integrand = cat(2, integrand, ... calcTrackingControllerIntegrand(params, values, ... - costTerm.controller)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end -for i = 1:length(params.integral.minimizing) - costTerm = params.integral.minimizing{i}; - if costTerm.isEnabled - switch costTerm.type - case "joint_jerk" + costTerm.controller)); + case "joint_jerk_minimization" integrand = cat(2, integrand, ... calcMinimizingJointJerkIntegrand(values.controlJerks, ... params, costTerm.coordinate)); + case "user_defined" + if strcmp(costTerm.cost_term_type, "continuous") + + end otherwise throw(MException('', ['Cost term type ' costTerm.type ... ' does not exist for this tool.'])) diff --git a/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m b/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m index 808e1b6d7..e5c2a24ef 100644 --- a/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -30,8 +30,12 @@ values = getDesignOptimizationValueStruct(inputs.phase, inputs.auxdata); phaseout = calcTorqueBasedModeledValues(values, inputs.auxdata); phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); -phaseout.dynamics = calcDesignOptimizationDynamicsConstraint(values, inputs.auxdata); -phaseout.path = calcDesignOptimizationPathConstraint(values, phaseout, inputs.auxdata); -phaseout.integrand = calcDesignOptimizationIntegrand(values, inputs.auxdata, ... - phaseout); +phaseout.dynamics = calcDesignOptimizationDynamicsConstraint(values, ... + inputs.auxdata); +if ~isempty(inputs.auxdata.path) + phaseout.path = calcDesignOptimizationPathConstraint(values, ... + phaseout, inputs.auxdata); +end +phaseout.integrand = calcDesignOptimizationIntegrand(values, ... + inputs.auxdata); end \ No newline at end of file diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index 6cd25b275..5dd9f3c45 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -48,9 +48,8 @@ bounds.parameter.upper = 0.5 * ones(1, length(inputs.minParameter)); end end -for i = 1:length(inputs.userDefinedParameters) - bounds.parameter.lower = [bounds.parameter.lower, inputs.userDefinedParameters{i}.lower]; - bounds.parameter.upper = [bounds.parameter.upper, inputs.userDefinedParameters{i}.upper]; -end -end +% for i = 1:length(inputs.userDefinedParameters) +% bounds.parameter.lower = [bounds.parameter.lower, inputs.userDefinedParameters{i}.lower]; +% bounds.parameter.upper = [bounds.parameter.upper, inputs.userDefinedParameters{i}.upper]; +% end end diff --git a/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m b/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m index d186e8fc7..bbdfc3313 100644 --- a/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m +++ b/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -27,20 +27,23 @@ function inputs = getIntegralBounds(inputs) inputs.maxIntegral = []; -for i = 1:length(inputs.integral.tracking) - costTerm = inputs.integral.tracking{i}; +inputs.minIntegral = []; +for i = 1:length(inputs.costTerms) + costTerm = inputs.costTerms{i}; if costTerm.isEnabled - inputs.maxIntegral = cat(2, inputs.maxIntegral, ... - costTerm.maxAllowableError); + if isTrackingCostTerm(costTerm) || isMinimizationCostTerm(costTerm) + inputs.maxIntegral = cat(2, inputs.maxIntegral, ... + costTerm.maxAllowableError); + elseif strcmp(costTerm.type, "user_defined") + if strcmp(costTerm.cost_term_type, "continuous") + inputs.maxIntegral = cat(2, inputs.maxIntegral, ... + costTerm.maxAllowableError); + end + else + throw(MException('', ['Cost term type ' costTerm.type ... + ' does not exist for this tool.'])) + end end end -for i = 1:length(inputs.integral.minimizing) - costTerm = inputs.integral.minimizing{i}; - if costTerm.isEnabled - inputs.maxIntegral = cat(2, inputs.maxIntegral, ... - costTerm.maxAllowableError); - end - -end inputs.minIntegral = zeros(1, length(inputs.maxIntegral)); end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m b/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m new file mode 100644 index 000000000..81e8fc776 --- /dev/null +++ b/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m @@ -0,0 +1,38 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega, Claire V. Hammond % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function output = isMinimizationCostTerm(costTerm) +minimizationCostTerms = ["joint_jerk_minimization"]; +output = false; +for i = 1:length(minimizationCostTerms) + if strcmp(costTerm.type, minimizationCostTerms(i)) + output = true; + return + end +end +end + diff --git a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m new file mode 100644 index 000000000..bf537c702 --- /dev/null +++ b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m @@ -0,0 +1,38 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega, Claire V. Hammond % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function output = isTrackingCostTerm(costTerm) +trackingCostTerms = ["coordinate_tracking", "controller_tracking"]; +output = false; +for i = 1:length(trackingCostTerms) + if strcmp(costTerm.type, trackingCostTerms(i)) + output = true; + return + end +end +end + diff --git a/src/core/TreatmentOptimization/scaleToBounds.m b/src/core/TreatmentOptimization/scaleToBounds.m index ab1615015..15f36aad8 100644 --- a/src/core/TreatmentOptimization/scaleToBounds.m +++ b/src/core/TreatmentOptimization/scaleToBounds.m @@ -26,6 +26,5 @@ % ----------------------------------------------------------------------- % function scaledValue = scaleToBounds(value, maximum, minimum) - scaledValue = (value - (maximum + minimum) / 2) ./ (maximum - minimum); end \ No newline at end of file From adcbcf6d1da35f2a21284081116e019bcbd4906e Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Tue, 16 May 2023 01:29:30 -0500 Subject: [PATCH 10/72] added user_defined cost term --- .../calcDesignOptimizationIntegrand.m | 4 +-- ...omputeDesignOptimizationEndpointFunction.m | 3 +- .../computeDesignOptimizationMainFunction.m | 34 ++++++++++++++++--- .../costTerms/isMinimizationCostTerm.m | 2 +- .../costTerms/isTrackingCostTerm.m | 2 +- 5 files changed, 36 insertions(+), 9 deletions(-) diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index 114a81fdf..cd4de05ef 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -46,7 +46,7 @@ params, costTerm.coordinate)); case "user_defined" if strcmp(costTerm.cost_term_type, "continuous") - + end otherwise throw(MException('', ['Cost term type ' costTerm.type ... diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index f8c28e07a..6bd0ab0ec 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -39,6 +39,7 @@ values, modeledValues, inputs.auxdata); end discrete = calcDesignOptimizationDiscreteObjective(values, inputs.auxdata); + output.objective = calcDesignOptimizationObjective(discrete, ... inputs.phase.integral); end diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index 5dd9f3c45..0d666a429 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -28,6 +28,7 @@ function output = computeDesignOptimizationMainFunction(inputs, params) bounds = setupProblemBounds(inputs); guess = setupCommonOptimalControlInitialGuess(inputs); +guess = addUserDefinedTermsToGuess(guess, inputs); setup = setupCommonOptimalControlSolverSettings(inputs, ... bounds, guess, params, ... @computeDesignOptimizationContinuousFunction, ... @@ -48,8 +49,33 @@ bounds.parameter.upper = 0.5 * ones(1, length(inputs.minParameter)); end end -% for i = 1:length(inputs.userDefinedParameters) -% bounds.parameter.lower = [bounds.parameter.lower, inputs.userDefinedParameters{i}.lower]; -% bounds.parameter.upper = [bounds.parameter.upper, inputs.userDefinedParameters{i}.upper]; -% end +for i = 1:length(inputs.costTerms) + if isfield(bounds, "parameter") && isfield(bounds.parameter, "lower") + costTerm = inputs.costTerms{i}; + if strcmp(costTerm.type, "user_defined") + if strcmp(costTerm.cost_term_type, "parameter") + bounds.parameter.lower = [bounds.parameter.lower, ... + costTerm.lower_bounds]; + bounds.parameter.upper = [bounds.parameter.upper, ... + costTerm.upper_bounds]; + end + end + end +end +end + +function guess = addUserDefinedTermsToGuess(guess, inputs) +for i = 1:length(inputs.costTerms) + costTerm = inputs.costTerms{i}; + if strcmp(costTerm.type, "user_defined") + if strcmp(costTerm.cost_term_type, "parameter") + if ~isfield(guess, "phase") || ... + ~isfield(guess.phase, "parameter") + guess.phase.parameter = []; + end + guess.phase.parameter = [guess.phase.parameter, ... + costTerm.initial_values]; + end + end +end end diff --git a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m b/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m index 81e8fc776..01487e42f 100644 --- a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m +++ b/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega, Claire V. Hammond % +% Author(s): Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % diff --git a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m index bf537c702..04117a085 100644 --- a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m +++ b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega, Claire V. Hammond % +% Author(s): Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % From aa5c3cd869eb5f2e891df979b32940f3724335ef Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Tue, 16 May 2023 19:03:02 -0500 Subject: [PATCH 11/72] Make parsing MTP results in NCP optional, fix updating values from precal --- .../updateNcpInitialGuess.m | 2 +- ...NeuralControlPersonalizationSettingsTree.m | 18 +++++++---- src/core/osimx/parseOsimxFile.m | 30 ++++++++++++++----- 3 files changed, 35 insertions(+), 15 deletions(-) diff --git a/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m b/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m index 0452dc1a4..d72417acf 100644 --- a/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m +++ b/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m @@ -2,7 +2,7 @@ precalInputs, optimizedInitialGuess) values = makeMuscleTendonLengthInitializationValuesAsStruct( ... optimizedInitialGuess, precalInputs); -nonMtpEntries = ismember(ncpInputs.muscleTendonColumnNames, ... +nonMtpEntries = ~ismember(ncpInputs.muscleTendonColumnNames, ... ncpInputs.mtpActivationsColumnNames); ncpInputs.optimalFiberLength(nonMtpEntries) = ... precalInputs.optimalFiberLength(nonMtpEntries) ... diff --git a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m index 127873d85..a75b75119 100644 --- a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m +++ b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m @@ -43,14 +43,17 @@ inputs = parseMtpNcpSharedInputs(tree); inputs.synergyGroups = getSynergyGroups(tree, Model(inputs.model)); inputs = matchMuscleNamesFromCoordinatesAndSynergyGroups(inputs); -inputs = loadMtpData(tree, inputs); inputs = reorderPreprocessedDataByMuscleNames(inputs, inputs.muscleNames); [inputs.maxIsometricForce, inputs.optimalFiberLength, ... inputs.tendonSlackLength, inputs.pennationAngle] = ... getMuscleInputs(inputs, inputs.muscleTendonColumnNames); -[inputs.optimalFiberLengthScaleFactors, ... - inputs.tendonSlackLengthScaleFactors] = getMtpDataInputs( ... - inputs.mtpMuscleData, inputs.muscleTendonColumnNames); +mtpResults = getFieldByName(tree, "mtp_results_directory"); +if isstruct(mtpResults) && ~isempty(mtpResults.Text) + inputs = loadMtpData(tree, inputs); + [inputs.optimalFiberLengthScaleFactors, ... + inputs.tendonSlackLengthScaleFactors] = getMtpDataInputs( ... + inputs.mtpMuscleData, inputs.muscleTendonColumnNames); +end end function inputs = loadMtpData(tree, inputs) @@ -59,8 +62,11 @@ [inputs.mtpActivations, inputs.mtpActivationsColumnNames] = ... parseMtpStandard(findFileListFromPrefixList( ... fullfile(mtpResultsDirectory, "muscleActivations"), inputs.prefixes)); -inputs.mtpMuscleData = parseOsimxFile(fullfile(mtpResultsDirectory, ... - "model.osimx")); +osimxFileName = getFieldByName(tree, "input_osimx_file"); +if ~isstruct(osimxFileName) + throw(MException('', 'An input .osimx file is required if using data from MTP.')) +end +inputs.mtpMuscleData = parseOsimxFile(osimxFileName.Text); % Remove activations of muscles from coordinates not included includedSubset = ismember(inputs.mtpActivationsColumnNames, ... inputs.muscleTendonColumnNames); diff --git a/src/core/osimx/parseOsimxFile.m b/src/core/osimx/parseOsimxFile.m index 78bf8b202..4ded75e03 100644 --- a/src/core/osimx/parseOsimxFile.m +++ b/src/core/osimx/parseOsimxFile.m @@ -13,7 +13,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond, Marleny Vega % +% Author(s): Claire V. Hammond, Marleny Vega, Spencer Williams % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -64,13 +64,27 @@ else muscle = musclesTree{i}; end - osimx.muscles.(muscle.Attributes.name).electromechanicalDelay = str2double(muscle.electromechanical_delay.Text); - osimx.muscles.(muscle.Attributes.name).activationTimeConstant = str2double(muscle.activation_time_constant.Text); - osimx.muscles.(muscle.Attributes.name).activationNonlinearityConstant = str2double(muscle.activation_nonlinearity_constant.Text); - osimx.muscles.(muscle.Attributes.name).emgScaleFactor = str2double(muscle.emg_scale_factor.Text); - osimx.muscles.(muscle.Attributes.name).optimalFiberLength = str2double(muscle.optimal_fiber_length.Text); - osimx.muscles.(muscle.Attributes.name).tendonSlackLength = str2double(muscle.tendon_slack_length.Text); - osimx.muscles.(muscle.Attributes.name).maxIsometricForce = str2double(muscle.max_isometric_force.Text); + if isstruct(getFieldByName(muscle, 'electromechanical_delay')) + osimx.muscles.(muscle.Attributes.name).electromechanicalDelay = str2double(muscle.electromechanical_delay.Text); + end + if isstruct(getFieldByName(muscle, 'activation_time_constant')) + osimx.muscles.(muscle.Attributes.name).activationTimeConstant = str2double(muscle.activation_time_constant.Text); + end + if isstruct(getFieldByName(muscle, 'activation_nonlinearity_constant')) + osimx.muscles.(muscle.Attributes.name).activationNonlinearityConstant = str2double(muscle.activation_nonlinearity_constant.Text); + end + if isstruct(getFieldByName(muscle, 'emg_scale_factor')) + osimx.muscles.(muscle.Attributes.name).emgScaleFactor = str2double(muscle.emg_scale_factor.Text); + end + if isstruct(getFieldByName(muscle, 'optimal_fiber_length')) + osimx.muscles.(muscle.Attributes.name).optimalFiberLength = str2double(muscle.optimal_fiber_length.Text); + end + if isstruct(getFieldByName(muscle, 'tendon_slack_length')) + osimx.muscles.(muscle.Attributes.name).tendonSlackLength = str2double(muscle.tendon_slack_length.Text); + end + if isstruct(getFieldByName(muscle, 'max_isometric_force')) + osimx.muscles.(muscle.Attributes.name).maxIsometricForce = str2double(muscle.max_isometric_force.Text); + end end end end From f84bc6e72b7fe26858e344bd0d88bbb0dc57d4ad Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Wed, 17 May 2023 13:45:59 -0500 Subject: [PATCH 12/72] implement initial user_defined --- ...omputeDesignOptimizationEndpointFunction.m | 24 +++++++++++++++---- .../computeDesignOptimizationMainFunction.m | 18 +++++++++----- .../reportDesignOptimizationResults.m | 2 +- .../computeInnerOptimizationHeuristic.m | 3 ++- 4 files changed, 34 insertions(+), 13 deletions(-) diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index 6bd0ab0ec..f4355b970 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -35,11 +35,25 @@ modeledValues = calcTorqueBasedModeledValues(values, inputs.auxdata); if ~isempty(inputs.auxdata.terminal) -output.eventgroup.event = calcDesignOptimizationTerminalConstraint( ... - values, modeledValues, inputs.auxdata); + output.eventgroup.event = calcDesignOptimizationTerminalConstraint( ... + values, modeledValues, inputs.auxdata); end -discrete = calcDesignOptimizationDiscreteObjective(values, inputs.auxdata); - +% discrete = calcDesignOptimizationDiscreteObjective(values, inputs.auxdata); +discrete = computeStaticParameterCost(inputs); output.objective = calcDesignOptimizationObjective(discrete, ... inputs.phase.integral); end + +function cost = computeStaticParameterCost(inputs) +costTerms = inputs.auxdata.costTerms; +cost = 0; +for i = 1:length(costTerms) + costTerm = costTerms{i}; + if strcmp(costTerm.type, "user_defined") && ... + strcmp(costTerm.cost_term_type, "parameter") + func = str2func(costTerm.function_name); + cost = cost + func(inputs); + end +end +end + diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index 0d666a429..bbf506201 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -33,7 +33,9 @@ bounds, guess, params, ... @computeDesignOptimizationContinuousFunction, ... @computeDesignOptimizationEndpointFunction); +% setup.scales.method = 'automatic-bounds'; solution = gpops2(setup); +solution.result solution = solution.result.solution; solution.auxdata = inputs; output = computeDesignOptimizationContinuousFunction(solution); @@ -50,10 +52,14 @@ end end for i = 1:length(inputs.costTerms) - if isfield(bounds, "parameter") && isfield(bounds.parameter, "lower") - costTerm = inputs.costTerms{i}; - if strcmp(costTerm.type, "user_defined") - if strcmp(costTerm.cost_term_type, "parameter") + costTerm = inputs.costTerms{i}; + if strcmp(costTerm.type, "user_defined") + if strcmp(costTerm.cost_term_type, "parameter") + if ~isfield(bounds, "parameter") || ... + ~isfield(bounds.parameter, "lower") + bounds.parameter.lower = [costTerm.lower_bounds]; + bounds.parameter.upper = [costTerm.upper_bounds]; + else bounds.parameter.lower = [bounds.parameter.lower, ... costTerm.lower_bounds]; bounds.parameter.upper = [bounds.parameter.upper, ... @@ -71,9 +77,9 @@ if strcmp(costTerm.cost_term_type, "parameter") if ~isfield(guess, "phase") || ... ~isfield(guess.phase, "parameter") - guess.phase.parameter = []; + guess.parameter = []; end - guess.phase.parameter = [guess.phase.parameter, ... + guess.parameter = [guess.parameter, ... costTerm.initial_values]; end end diff --git a/src/DesignOptimization/reportDesignOptimizationResults.m b/src/DesignOptimization/reportDesignOptimizationResults.m index 87e16a6da..b64df3606 100644 --- a/src/DesignOptimization/reportDesignOptimizationResults.m +++ b/src/DesignOptimization/reportDesignOptimizationResults.m @@ -26,7 +26,7 @@ % ----------------------------------------------------------------------- % function reportDesignOptimizationResults(solution, inputs) - +parameterResults = solution.solution.parameter values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); if strcmp(inputs.controllerType, 'synergy_driven') % plot Muscle Activations diff --git a/src/JointModelPersonalization/KinematicCalibration/computeInnerOptimizationHeuristic.m b/src/JointModelPersonalization/KinematicCalibration/computeInnerOptimizationHeuristic.m index 2863aef06..ec194aac5 100644 --- a/src/JointModelPersonalization/KinematicCalibration/computeInnerOptimizationHeuristic.m +++ b/src/JointModelPersonalization/KinematicCalibration/computeInnerOptimizationHeuristic.m @@ -37,6 +37,7 @@ error = computeInverseKinematicsSquaredError(model, trialIKSolver, ... markersReference, params); trialIKSolver = libpointer; -error = error / params.desiredError; +desiredError = valueOrAlternate(params, "desiredError", 0.01); +error = error / desiredError; end From fc539fa5b335f6d2236231fa50b64d31f0995536 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 17 May 2023 21:46:52 -0500 Subject: [PATCH 13/72] Save optimized max isometric stress from MTP, account for turning off some design variables in saving --- .../MuscleTendonPersonalizationTool.m | 3 ++ .../saveNeuralControlPersonalizationResults.m | 3 ++ ...riteMuscleTendonPersonalizationOsimxFile.m | 35 +++++++++++++------ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m b/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m index 9f3f41186..39810ca8b 100644 --- a/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m +++ b/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m @@ -48,6 +48,9 @@ function MuscleTendonPersonalizationTool(settingsFileName) % reportMuscleTendonPersonalizationResults(optimizedParams, inputs); % end finalValues = makeMtpValuesAsStruct([], optimizedParams, zeros(1, 7)); +if precalInputs.optimizeIsometricMaxForce + finalValues.maxIsometricForce = inputs.maxIsometricForce; +end results = calcMtpSynXModeledValues(finalValues, inputs, params); results.time = inputs.emgTime(:, inputs.numPaddingFrames + 1 : ... end - inputs.numPaddingFrames); diff --git a/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m b/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m index dbe6dd4c3..062d8d9f5 100644 --- a/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m +++ b/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m @@ -23,4 +23,7 @@ function saveNeuralControlPersonalizationResults(synergyWeights, ... ) ... ) end +if isstruct(precalInputs) + writeNeuralControlPersonalizationOsimxFile() +end end diff --git a/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m b/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m index 692a1f213..7bacaccd2 100644 --- a/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m +++ b/src/core/osimx/writeMuscleTendonPersonalizationOsimxFile.m @@ -54,16 +54,29 @@ function writeMuscleTendonPersonalizationOsimxFile(modelFileName, ... end function params = makeMuscleParams(model, muscleName, optimizedParams, index) -params.electromechanicalDelay = optimizedParams.electromechanicalDelays(index); -params.activationTimeConstant = optimizedParams.activationTimeConstants(index); -params.activationNonlinearityConstant = ... - optimizedParams.activationNonlinearityConstants(index); - +if isfield(optimizedParams, 'electromechanicalDelays') + params.electromechanicalDelay = optimizedParams.electromechanicalDelays(index); +end +if isfield(optimizedParams, 'activationTimeConstants') + params.activationTimeConstant = optimizedParams.activationTimeConstants(index); +end +if isfield(optimizedParams, 'activationNonlinearityConstants') + params.activationNonlinearityConstant = ... + optimizedParams.activationNonlinearityConstants(index); +end muscle = model.getForceSet().getMuscles().get(muscleName); - -params.emgScaleFactor = optimizedParams.emgScaleFactors(index); -params.optimalFiberLength = muscle.get_optimal_fiber_length() * ... - optimizedParams.optimalFiberLengthScaleFactors(index); -params.tendonSlackLength = muscle.get_tendon_slack_length() * ... - optimizedParams.tendonSlackLengthScaleFactors(index); +if isfield(optimizedParams, 'emgScaleFactors') + params.emgScaleFactor = optimizedParams.emgScaleFactors(index); +end +if isfield(optimizedParams, 'optimalFiberLengthScaleFactors') + params.optimalFiberLength = muscle.get_optimal_fiber_length() * ... + optimizedParams.optimalFiberLengthScaleFactors(index); +end +if isfield(optimizedParams, 'tendonSlackLengthScaleFactors') + params.tendonSlackLength = muscle.get_tendon_slack_length() * ... + optimizedParams.tendonSlackLengthScaleFactors(index); +end +if isfield(optimizedParams, 'maxIsometricForce') + params.maxIsometricForce = optimizedParams.maxIsometricForce(index); +end end \ No newline at end of file From 8ef0b1fc740c6d117b7de2682fae9d02e84df25d Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 18 May 2023 16:37:26 -0500 Subject: [PATCH 14/72] Fix length scale factors, max isometric force importing from MTP --- ...NeuralControlPersonalizationSettingsTree.m | 29 +++++++++++++++---- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m index a75b75119..5aa03d757 100644 --- a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m +++ b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m @@ -51,8 +51,8 @@ if isstruct(mtpResults) && ~isempty(mtpResults.Text) inputs = loadMtpData(tree, inputs); [inputs.optimalFiberLengthScaleFactors, ... - inputs.tendonSlackLengthScaleFactors] = getMtpDataInputs( ... - inputs.mtpMuscleData, inputs.muscleTendonColumnNames); + inputs.tendonSlackLengthScaleFactors, ... + inputs.maxIsometricForce] = getMtpDataInputs(inputs); end end @@ -146,14 +146,31 @@ end function [optimalFiberLengthScaleFactors, ... - tendonSlackLengthScaleFactors] = getMtpDataInputs(mtpData, muscleNames) + tendonSlackLengthScaleFactors, maxIsometricForce] = ... + getMtpDataInputs(inputs) +mtpData = inputs.mtpMuscleData; +muscleNames = inputs.muscleTendonColumnNames; + optimalFiberLengthScaleFactors = zeros(1, length(muscleNames)); tendonSlackLengthScaleFactors = zeros(1, length(muscleNames)); -mtpDataMuscleNames = fieldnames(mtpData); +maxIsometricForce = inputs.maxIsometricForce; +mtpDataMuscleNames = fieldnames(mtpData.muscles); for i = 1 : length(muscleNames) if ismember(muscleNames(i), mtpDataMuscleNames) - optimalFiberLengthScaleFactors(i) = mtpData.(muscleNames(i)).optimalFiberLengthScaleFactor; - tendonSlackLengthScaleFactors(i) = mtpData.(muscleNames(i)).tendonSlackLengthScaleFactor; + currentMuscle = mtpData.muscles.(muscleNames(i)); + if isfield(currentMuscle, 'optimalFiberLength') + optimalFiberLengthScaleFactors(i) = currentMuscle.optimalFiberLength / inputs.optimalFiberLength(i); + else + optimalFiberLengthScaleFactors(i) = 1; + end + if isfield(currentMuscle, 'tendonSlackLength') + tendonSlackLengthScaleFactors(i) = currentMuscle.tendonSlackLength / inputs.tendonSlackLength(i); + else + tendonSlackLengthScaleFactors(i) = 1; + end + if isfield(currentMuscle, 'maxIsometricForce') + maxIsometricForce(i) = currentMuscle.maxIsometricForce; + end else optimalFiberLengthScaleFactors(i) = 1; tendonSlackLengthScaleFactors(i) = 1; From 9996bd32a9a5a1b37f6842f901e9b7d01e3ab6fc Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 18 May 2023 20:43:30 -0500 Subject: [PATCH 15/72] Fix NCP with no MTP results, save NCP precal to osimx --- .../g0w2BX3hbHutyPMvNe1ZC1Hym4sd.xml | 6 ++ .../g0w2BX3hbHutyPMvNe1ZC1Hym4sp.xml | 2 + .../updateNcpInitialGuess.m | 8 +- .../NeuralControlPersonalizationTool.m | 2 +- .../Optimization/calcNcpCost.m | 16 +++- ...NeuralControlPersonalizationSettingsTree.m | 5 ++ .../saveNeuralControlPersonalizationResults.m | 5 +- ...iteNeuralControlPersonalizationOsimxFile.m | 74 +++++++++++++++++++ 8 files changed, 109 insertions(+), 9 deletions(-) create mode 100644 resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sd.xml create mode 100644 resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sp.xml create mode 100644 src/core/osimx/writeNeuralControlPersonalizationOsimxFile.m diff --git a/resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sd.xml b/resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sd.xml new file mode 100644 index 000000000..7a6326b99 --- /dev/null +++ b/resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sd.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sp.xml b/resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sp.xml new file mode 100644 index 000000000..2861ed5de --- /dev/null +++ b/resources/project/jwn0mMf3vUOgeAWeaJY8jqgb0nM/g0w2BX3hbHutyPMvNe1ZC1Hym4sp.xml @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m b/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m index d72417acf..44bedc255 100644 --- a/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m +++ b/src/MuscleTendonLengthInitialization/updateNcpInitialGuess.m @@ -2,8 +2,12 @@ precalInputs, optimizedInitialGuess) values = makeMuscleTendonLengthInitializationValuesAsStruct( ... optimizedInitialGuess, precalInputs); -nonMtpEntries = ~ismember(ncpInputs.muscleTendonColumnNames, ... - ncpInputs.mtpActivationsColumnNames); +if isfield(ncpInputs, 'mtpActivationsColumnNames') + nonMtpEntries = ~ismember(ncpInputs.muscleTendonColumnNames, ... + ncpInputs.mtpActivationsColumnNames); +else + nonMtpEntries = ones(1, length(ncpInputs.muscleTendonColumnNames)); +end ncpInputs.optimalFiberLength(nonMtpEntries) = ... precalInputs.optimalFiberLength(nonMtpEntries) ... .* values.optimalFiberLengthScaleFactors(nonMtpEntries); diff --git a/src/NeuralControlPersonalization/NeuralControlPersonalizationTool.m b/src/NeuralControlPersonalization/NeuralControlPersonalizationTool.m index 6afb90db6..3b84d2b1c 100644 --- a/src/NeuralControlPersonalization/NeuralControlPersonalizationTool.m +++ b/src/NeuralControlPersonalization/NeuralControlPersonalizationTool.m @@ -47,5 +47,5 @@ function NeuralControlPersonalizationTool(settingsFileName) [synergyWeights, synergyCommands] = normalizeSynergiesByMaximumWeight(... synergyWeights, synergyCommands); saveNeuralControlPersonalizationResults(synergyWeights, ... - synergyCommands, inputs, resultsDirectory); + synergyCommands, inputs, resultsDirectory, precalInputs); end \ No newline at end of file diff --git a/src/NeuralControlPersonalization/Optimization/calcNcpCost.m b/src/NeuralControlPersonalization/Optimization/calcNcpCost.m index bc5ea8e20..c6e7ac074 100644 --- a/src/NeuralControlPersonalization/Optimization/calcNcpCost.m +++ b/src/NeuralControlPersonalization/Optimization/calcNcpCost.m @@ -33,9 +33,13 @@ error = []; % Split activations into subsets ahead of cost computation -[activationsWithMtpData, activationsWithoutMtpData] = ... - makeMtpActivatonSubset(activations, ... - inputs.mtpActivationsColumnNames, inputs.muscleTendonColumnNames); +if isfield(inputs, 'mtpActivationsColumnNames') + [activationsWithMtpData, activationsWithoutMtpData] = ... + makeMtpActivatonSubset(activations, ... + inputs.mtpActivationsColumnNames, inputs.muscleTendonColumnNames); +else + activationsWithoutMtpData = activations; +end for term = 1:length(params.costTerms) costTerm = params.costTerms{term}; if costTerm.isEnabled @@ -51,7 +55,11 @@ rawCost = muscleJointMoments - ... inputs.inverseDynamicsMoments; case "activation_tracking" - rawCost = activationsWithMtpData - inputs.mtpActivations; + if isfield(inputs, 'mtpActivations') + rawCost = activationsWithMtpData - inputs.mtpActivations; + else + rawCost = 0; + end case "activation_minimization" rawCost = reshape(activationsWithoutMtpData, [], 1); case "grouped_activations" diff --git a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m index 5aa03d757..2d3501a82 100644 --- a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m +++ b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m @@ -53,6 +53,11 @@ [inputs.optimalFiberLengthScaleFactors, ... inputs.tendonSlackLengthScaleFactors, ... inputs.maxIsometricForce] = getMtpDataInputs(inputs); +else + inputs.optimalFiberLengthScaleFactors = ... + ones(1, length(inputs.muscleTendonColumnNames)); + inputs.tendonSlackLengthScaleFactors = ... + ones(1, length(inputs.muscleTendonColumnNames)); end end diff --git a/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m b/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m index 062d8d9f5..adf43e70e 100644 --- a/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m +++ b/src/NeuralControlPersonalization/saveNeuralControlPersonalizationResults.m @@ -1,5 +1,5 @@ function saveNeuralControlPersonalizationResults(synergyWeights, ... - synergyCommands, inputs, resultsDirectory) + synergyCommands, inputs, resultsDirectory, precalInputs) if ~exist(resultsDirectory, "dir") mkdir(resultsDirectory); end @@ -24,6 +24,7 @@ function saveNeuralControlPersonalizationResults(synergyWeights, ... ) end if isstruct(precalInputs) - writeNeuralControlPersonalizationOsimxFile() + writeNeuralControlPersonalizationOsimxFile(inputs, ... + resultsDirectory, precalInputs) end end diff --git a/src/core/osimx/writeNeuralControlPersonalizationOsimxFile.m b/src/core/osimx/writeNeuralControlPersonalizationOsimxFile.m new file mode 100644 index 000000000..0abf5b7df --- /dev/null +++ b/src/core/osimx/writeNeuralControlPersonalizationOsimxFile.m @@ -0,0 +1,74 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% This function prints out the optimized muscle tendon parameters from +% Neural Control Personalization in an osimx file +% +% (string, 2D matrix, string) -> (None) +% Prints Neural Control Personalization results in osimx file + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams, Marleny Vega % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function writeNeuralControlPersonalizationOsimxFile(inputs, ... + resultsDirectory, precalInputs) +modelFileName = inputs.model; +model = Model(modelFileName); + +buildFromExisting = false; +if isfield(inputs, 'osimxFileName') + if isfile(inputs.osimxFileName) + osimx = parseOsimxFile(inputs.osimxFileName); + [~, name, ~] = fileparts(inputs.osimxFileName); + outfile = fullfile(resultsDirectory, strcat(name, "_ncp.xml")); + buildFromExisting = true; + end +end +if ~buildFromExisting + % As only muscle parameters are included, the MtpOsimxTemplate can be + % reused + osimx = buildMtpOsimxTemplate(... + replace(model.getName().toCharArray',".","_dot_"), ... + modelFileName); + [~, name, ~] = fileparts(modelFileName); + outfile = fullfile(resultsDirectory, strcat(name, "_ncp.xml")); +end +osimx.modelName = name; +osimx.model = modelFileName; +if ~isfield(osimx, 'muscles') + osimx.muscles = []; +end +for i = 1:length(inputs.muscleTendonColumnNames) + if ~isfield(osimx.muscles, inputs.muscleTendonColumnNames(i)) + osimx.muscles.(inputs.muscleTendonColumnNames(i)) ... + .optimalFiberLength = inputs.optimalFiberLength(i); + osimx.muscles.(inputs.muscleTendonColumnNames(i)) ... + .tendonSlackLength = inputs.tendonSlackLength(i); + if precalInputs.optimizeIsometricMaxForce + osimx.muscles.(inputs.muscleTendonColumnNames(i)) ... + .maxIsometricForce = inputs.maxIsometricForce(i); + end + end +end + +writeOsimxFile(buildOsimxFromOsimxStruct(osimx), outfile) +end + From e198e08b190778066febb58c4ad18f2532a09185 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 18 May 2023 22:09:08 -0500 Subject: [PATCH 16/72] Save GCP kinematics with correct column names, start implementing appending to osimx --- .../GroundContactPersonalizationTool.m | 3 ++- .../saveGroundContactPersonalizationResults.m | 6 +++--- .../writeExperimentalFootKinematicsToSto.m | 5 +++-- ...writeGroundContactPersonalizationOsimxFile.m | 17 +++++++++++++++-- .../Saving/writeOptimizedFootKinematicsToSto.m | 5 +++-- ...seGroundContactPersonalizationSettingsTree.m | 6 ++++-- 6 files changed, 30 insertions(+), 12 deletions(-) diff --git a/src/GroundContactPersonalization/GroundContactPersonalizationTool.m b/src/GroundContactPersonalization/GroundContactPersonalizationTool.m index 6c17d39c0..6258eb5af 100644 --- a/src/GroundContactPersonalization/GroundContactPersonalizationTool.m +++ b/src/GroundContactPersonalization/GroundContactPersonalizationTool.m @@ -34,6 +34,7 @@ function GroundContactPersonalizationTool(settingsFileName) [inputs, params, resultsDirectory] = ... parseGroundContactPersonalizationSettingsTree(settingsTree); results = GroundContactPersonalization(inputs, params); -saveGroundContactPersonalizationResults(results, resultsDirectory); +saveGroundContactPersonalizationResults(results, params, ... + resultsDirectory, inputs.osimxFileName); end diff --git a/src/GroundContactPersonalization/Saving/saveGroundContactPersonalizationResults.m b/src/GroundContactPersonalization/Saving/saveGroundContactPersonalizationResults.m index 1b66c5451..b7007b3a8 100644 --- a/src/GroundContactPersonalization/Saving/saveGroundContactPersonalizationResults.m +++ b/src/GroundContactPersonalization/Saving/saveGroundContactPersonalizationResults.m @@ -29,7 +29,7 @@ % ----------------------------------------------------------------------- % function saveGroundContactPersonalizationResults(inputs, params, ... - resultsDirectory) + resultsDirectory, osimxFileName) [~, name, ~] = fileparts(inputs.bodyModel); if ~exist(resultsDirectory, "dir") mkdir(resultsDirectory); @@ -39,7 +39,7 @@ function saveGroundContactPersonalizationResults(inputs, params, ... writeReplacedExperimentalGroundReactionsToSto(inputs, ... resultsDirectory, name); writeOptimizedGroundReactionsToSto(inputs, params, resultsDirectory, name); -writeGroundContactPersonalizationOsimxFile(inputs,... - fullfile(resultsDirectory, strcat(name, "_groundContactModel.xml"))); +writeGroundContactPersonalizationOsimxFile(inputs, resultsDirectory, ... + osimxFileName); end diff --git a/src/GroundContactPersonalization/Saving/writeExperimentalFootKinematicsToSto.m b/src/GroundContactPersonalization/Saving/writeExperimentalFootKinematicsToSto.m index 5c5fa160c..310be26f9 100644 --- a/src/GroundContactPersonalization/Saving/writeExperimentalFootKinematicsToSto.m +++ b/src/GroundContactPersonalization/Saving/writeExperimentalFootKinematicsToSto.m @@ -29,9 +29,10 @@ function writeExperimentalFootKinematicsToSto(inputs, resultsDirectory, ... modelName) -columnLabels = ["Toe.Angle", "Y.Rotation", "X.Rotation", "Z.Rotation", ... - "X.Translation", "Y.Translation", "Z.Translation"]; for foot = 1:length(inputs.surfaces) + columnLabels = [convertCharsToStrings(inputs.surfaces{foot} ... + .toesCoordinateName), "Rotation1", "Rotation2", "Rotation3", ... + "Translation1", "Translation2", "Translation3"]; writeToSto( ... columnLabels, ... inputs.surfaces{foot}.time, ... diff --git a/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m b/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m index 359b03f19..b78e6db05 100644 --- a/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m +++ b/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m @@ -28,9 +28,22 @@ % ----------------------------------------------------------------------- % function writeGroundContactPersonalizationOsimxFile(inputs, ... - groundContactModelFileName) + resultsDirectory, osimxFileName) +modelFileName = inputs.model; +model = Model(modelFileName); -bodyModel = Model(inputs.bodyModel); +if isfile(osimxFileName) + osimx = parseOsimxFile(inputs.osimxFileName); + [~, name, ~] = fileparts(inputs.osimxFileName); + outfile = fullfile(resultsDirectory, strcat(name, "_gcp.xml")); +else + osimx = buildGcpOsimxTemplate(... + replace(model.getName().toCharArray',".","_dot_"), modelFileName); + [~, name, ~] = fileparts(modelFileName); + outfile = fullfile(resultsDirectory, strcat(name, "_gcp.xml")); +end + +%% To update osimx = buildGcpOsimxTemplate(... replace(bodyModel.getName().toCharArray',".","_dot_"), ... inputs.bodyModel, ... diff --git a/src/GroundContactPersonalization/Saving/writeOptimizedFootKinematicsToSto.m b/src/GroundContactPersonalization/Saving/writeOptimizedFootKinematicsToSto.m index 58d0489cb..18a1fd21f 100644 --- a/src/GroundContactPersonalization/Saving/writeOptimizedFootKinematicsToSto.m +++ b/src/GroundContactPersonalization/Saving/writeOptimizedFootKinematicsToSto.m @@ -29,9 +29,10 @@ function writeOptimizedFootKinematicsToSto(inputs, resultsDirectory, ... modelName) -columnLabels = ["Toe.Angle", "Y.Rotation", "X.Rotation", "Z.Rotation", ... - "X.Translation", "Y.Translation", "Z.Translation"]; for foot = 1:length(inputs.surfaces) + columnLabels = [convertCharsToStrings(inputs.surfaces{foot} ... + .toesCoordinateName), "Rotation1", "Rotation2", "Rotation3", ... + "Translation1", "Translation2", "Translation3"]; [modeledJointPositions, ~] = calcGCPJointKinematics( ... inputs.surfaces{foot}.experimentalJointPositions, ... inputs.surfaces{foot}.jointKinematicsBSplines, ... diff --git a/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m b/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m index ac74ffaa0..308c031a8 100644 --- a/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m +++ b/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m @@ -42,6 +42,8 @@ import org.opensim.modeling.* inputDirectory = getTextFromField(getFieldByNameOrAlternate(tree, ... 'input_directory', pwd)); +inputs.inputOsimxFile = getTextFromField(getFieldByNameOrAlternate( ... + tree, 'input_osimx_file', [])); inputs.bodyModel = getFieldByNameOrError(tree, 'input_model_file').Text; motionFile = getFieldByNameOrError(tree, 'input_motion_file').Text; grfFile = getFieldByNameOrError(tree, 'input_grf_file').Text; @@ -51,11 +53,11 @@ getFieldByNameOrAlternate(tree, 'latching_velocity', '0.05'))); if(~isempty(inputDirectory)) try - bodyModel = Model(fullfile(inputDirectory, inputs.bodyModel)); + bodyModel = Model(inputs.bodyModel); inputs.motionFileName = fullfile(inputDirectory, motionFile); inputs.grfFileName = fullfile(inputDirectory, grfFile); catch - bodyModel = Model(fullfile(pwd, inputDirectory, inputs.bodyModel)); + bodyModel = Model(pwd, inputs.bodyModel); inputs.motionFileName = fullfile(pwd, inputDirectory, motionFile); inputs.grfFileName = fullfile(pwd, inputDirectory, grfFile); inputDirectory = fullfile(pwd, inputDirectory); From 9b8585be6eb2f5dbe183643cde4d3c450aba00b8 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Fri, 19 May 2023 16:25:18 -0500 Subject: [PATCH 17/72] separated design variable parts in xml --- ...puteDesignOptimizationContinuousFunction.m | 4 +- ...omputeDesignOptimizationEndpointFunction.m | 7 ++- .../computeDesignOptimizationMainFunction.m | 46 ++++++++----------- .../getDesignOptimizationValueStruct.m | 4 +- .../parseDesignOptimizationSettingsTree.m | 8 ++++ .../reportDesignOptimizationResults.m | 4 +- .../updateSystemFromUserDefinedFunctions.m | 33 +++++++++++++ src/core/parse/parseRcnlCostTermSet.m | 6 ++- src/core/parse/parseSpaceSeparatedList.m | 2 +- 9 files changed, 80 insertions(+), 34 deletions(-) create mode 100644 src/DesignOptimization/updateSystemFromUserDefinedFunctions.m diff --git a/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m b/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m index e5c2a24ef..2ccdd83da 100644 --- a/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m @@ -28,6 +28,7 @@ function phaseout = computeDesignOptimizationContinuousFunction(inputs) values = getDesignOptimizationValueStruct(inputs.phase, inputs.auxdata); +inputs = updateSystemFromUserDefinedFunctions(inputs, values); phaseout = calcTorqueBasedModeledValues(values, inputs.auxdata); phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); phaseout.dynamics = calcDesignOptimizationDynamicsConstraint(values, ... @@ -38,4 +39,5 @@ end phaseout.integrand = calcDesignOptimizationIntegrand(values, ... inputs.auxdata); -end \ No newline at end of file +end + diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index f4355b970..774ce63e1 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -31,7 +31,10 @@ inputs.phase.time = [inputs.phase.initialtime; inputs.phase.finaltime]; inputs.phase.control = ones(size(inputs.phase.time,1), ... length(inputs.auxdata.minControl)); -values = getDesignOptimizationValueStruct(inputs.phase, inputs.auxdata); +phase = inputs.phase; +phase.parameter = inputs.parameter; +values = getDesignOptimizationValueStruct(phase, inputs.auxdata); +inputs = updateSystemFromUserDefinedFunctions(inputs, values); modeledValues = calcTorqueBasedModeledValues(values, inputs.auxdata); if ~isempty(inputs.auxdata.terminal) @@ -50,7 +53,7 @@ for i = 1:length(costTerms) costTerm = costTerms{i}; if strcmp(costTerm.type, "user_defined") && ... - strcmp(costTerm.cost_term_type, "parameter") + strcmp(costTerm.cost_term_type, "discrete") func = str2func(costTerm.function_name); cost = cost + func(inputs); end diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index bbf506201..c7f8eafb2 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -33,11 +33,11 @@ bounds, guess, params, ... @computeDesignOptimizationContinuousFunction, ... @computeDesignOptimizationEndpointFunction); -% setup.scales.method = 'automatic-bounds'; solution = gpops2(setup); solution.result solution = solution.result.solution; solution.auxdata = inputs; +solution.phase.parameter = [solution.parameter]; output = computeDesignOptimizationContinuousFunction(solution); output.solution = solution; end @@ -51,37 +51,29 @@ bounds.parameter.upper = 0.5 * ones(1, length(inputs.minParameter)); end end -for i = 1:length(inputs.costTerms) - costTerm = inputs.costTerms{i}; - if strcmp(costTerm.type, "user_defined") - if strcmp(costTerm.cost_term_type, "parameter") - if ~isfield(bounds, "parameter") || ... - ~isfield(bounds.parameter, "lower") - bounds.parameter.lower = [costTerm.lower_bounds]; - bounds.parameter.upper = [costTerm.upper_bounds]; - else - bounds.parameter.lower = [bounds.parameter.lower, ... - costTerm.lower_bounds]; - bounds.parameter.upper = [bounds.parameter.upper, ... - costTerm.upper_bounds]; - end - end +for i = 1:length(inputs.userDefinedVariables) + variable = inputs.userDefinedVariables{i}; + if ~isfield(bounds, "parameter") || ... + ~isfield(bounds.parameter, "lower") + bounds.parameter.lower = [variable.lower_bounds]; + bounds.parameter.upper = [variable.upper_bounds]; + else + bounds.parameter.lower = [bounds.parameter.lower, ... + variable.lower_bounds]; + bounds.parameter.upper = [bounds.parameter.upper, ... + variable.upper_bounds]; end end end function guess = addUserDefinedTermsToGuess(guess, inputs) -for i = 1:length(inputs.costTerms) - costTerm = inputs.costTerms{i}; - if strcmp(costTerm.type, "user_defined") - if strcmp(costTerm.cost_term_type, "parameter") - if ~isfield(guess, "phase") || ... - ~isfield(guess.phase, "parameter") - guess.parameter = []; - end - guess.parameter = [guess.parameter, ... - costTerm.initial_values]; - end +for i = 1:length(inputs.userDefinedVariables) + variable = inputs.userDefinedVariables{i}; + if ~isfield(guess, "phase") || ... + ~isfield(guess.phase, "parameter") + guess.parameter = []; end + guess.parameter = [guess.parameter, ... + variable.initial_values]; end end diff --git a/src/DesignOptimization/getDesignOptimizationValueStruct.m b/src/DesignOptimization/getDesignOptimizationValueStruct.m index 994caa750..e99aad65c 100644 --- a/src/DesignOptimization/getDesignOptimizationValueStruct.m +++ b/src/DesignOptimization/getDesignOptimizationValueStruct.m @@ -26,7 +26,6 @@ % ----------------------------------------------------------------------- % function values = getDesignOptimizationValueStruct(inputs, params) - values.time = scaleToOriginal(inputs.time, params.maxTime, ... params.minTime); state = scaleToOriginal(inputs.state, ones(size(inputs.state, 1), 1) .* ... @@ -53,4 +52,7 @@ values.controlTorques = control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numTorqueControls); end +for i = 1:length(params.userDefinedVariables) + values.(params.userDefinedVariables{i}.type) = inputs.parameter(i, 1); +end end \ No newline at end of file diff --git a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m index e9a900fc6..ff924ce64 100644 --- a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m +++ b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m @@ -40,6 +40,14 @@ inputs.synergyWeights = parseTreatmentOptimizationStandard(... {getTextFromField(getFieldByName(tree, 'synergy_vectors_file'))}); end +inputs.systemFns = parseSpaceSeparatedList(tree, "model_functions"); +parameterTree = getFieldByNameOrError(tree, "RCNLParameterTermSet"); +if isstruct(parameterTree) && isfield(parameterTree, "RCNLParameterTerm") + inputs.userDefinedVariables = parseRcnlCostTermSet( ... + parameterTree.RCNLParameterTerm); +else + inputs.userDefinedVariables = struct(); +end end function inputs = getDesignVariableBounds(tree, inputs) diff --git a/src/DesignOptimization/reportDesignOptimizationResults.m b/src/DesignOptimization/reportDesignOptimizationResults.m index b64df3606..cb2f8865e 100644 --- a/src/DesignOptimization/reportDesignOptimizationResults.m +++ b/src/DesignOptimization/reportDesignOptimizationResults.m @@ -26,7 +26,9 @@ % ----------------------------------------------------------------------- % function reportDesignOptimizationResults(solution, inputs) -parameterResults = solution.solution.parameter +if isfield(solution.solution, "parameter") + parameterResults = solution.solution.parameter +end values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); if strcmp(inputs.controllerType, 'synergy_driven') % plot Muscle Activations diff --git a/src/DesignOptimization/updateSystemFromUserDefinedFunctions.m b/src/DesignOptimization/updateSystemFromUserDefinedFunctions.m new file mode 100644 index 000000000..cdd467d7e --- /dev/null +++ b/src/DesignOptimization/updateSystemFromUserDefinedFunctions.m @@ -0,0 +1,33 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Claire V. Hammond % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function inputs = updateSystemFromUserDefinedFunctions(inputs, values) +for i = 1:length(inputs.auxdata.systemFns) + func = str2func(inputs.auxdata.systemFns(i)); + inputs = func(inputs, values); +end +end \ No newline at end of file diff --git a/src/core/parse/parseRcnlCostTermSet.m b/src/core/parse/parseRcnlCostTermSet.m index 5c0c2befa..51f57e0e7 100644 --- a/src/core/parse/parseRcnlCostTermSet.m +++ b/src/core/parse/parseRcnlCostTermSet.m @@ -31,7 +31,11 @@ function costTerms = parseRcnlCostTermSet(tree) costTerms = cell(1, length(tree)); for term = 1:length(tree) - currentTerm = tree{term}; + if length(tree) == 1 + currentTerm = tree; + else + currentTerm = tree{term}; + end % Find general cost term elements costTerms{term}.type = getTextFromField(getFieldByNameOrError( ... currentTerm, 'type')); diff --git a/src/core/parse/parseSpaceSeparatedList.m b/src/core/parse/parseSpaceSeparatedList.m index de3df8454..053d58533 100644 --- a/src/core/parse/parseSpaceSeparatedList.m +++ b/src/core/parse/parseSpaceSeparatedList.m @@ -31,7 +31,7 @@ function prefixes = parseSpaceSeparatedList(tree, elementName) prefixField = getFieldByName(tree, elementName); -if(length(prefixField.Text) > 0) +if ~isempty(prefixField.Text) if(strcmp(prefixField.Text(1), ' ')) prefixField.Text = prefixField.Text(2:end); end From 2c792823b939d4180d604a33a59fe7420c96ff37 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Fri, 19 May 2023 17:25:15 -0500 Subject: [PATCH 18/72] Fix prepareGCPInputs arguments --- src/GroundContactPersonalization/GroundContactPersonalization.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/GroundContactPersonalization/GroundContactPersonalization.m b/src/GroundContactPersonalization/GroundContactPersonalization.m index 8b49a7ed6..0bae13e0c 100644 --- a/src/GroundContactPersonalization/GroundContactPersonalization.m +++ b/src/GroundContactPersonalization/GroundContactPersonalization.m @@ -28,7 +28,7 @@ % ----------------------------------------------------------------------- % function results = GroundContactPersonalization(inputs, params) -inputs = prepareGroundContactPersonalizationInputs(inputs, params); +inputs = prepareGroundContactPersonalizationInputs(inputs); % Optionally initializes the resting spring length. if params.restingSpringLengthInitialization inputs = initializeRestingSpringLength(inputs); From bb87ca0dc61627316c3803733c930a57e706fa03 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Fri, 19 May 2023 20:04:49 -0500 Subject: [PATCH 19/72] added manual scaling --- .../computeDesignOptimizationMainFunction.m | 14 ++++++++------ .../getDesignOptimizationValueStruct.m | 5 ++++- .../reportDesignOptimizationResults.m | 7 ++++--- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index c7f8eafb2..b2b3a8ba5 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -34,7 +34,6 @@ @computeDesignOptimizationContinuousFunction, ... @computeDesignOptimizationEndpointFunction); solution = gpops2(setup); -solution.result solution = solution.result.solution; solution.auxdata = inputs; solution.phase.parameter = [solution.parameter]; @@ -55,13 +54,13 @@ variable = inputs.userDefinedVariables{i}; if ~isfield(bounds, "parameter") || ... ~isfield(bounds.parameter, "lower") - bounds.parameter.lower = [variable.lower_bounds]; - bounds.parameter.upper = [variable.upper_bounds]; + bounds.parameter.lower = [-0.5]; + bounds.parameter.upper = [0.5]; else bounds.parameter.lower = [bounds.parameter.lower, ... - variable.lower_bounds]; + -0.5]; bounds.parameter.upper = [bounds.parameter.upper, ... - variable.upper_bounds]; + 0.5]; end end end @@ -74,6 +73,9 @@ guess.parameter = []; end guess.parameter = [guess.parameter, ... - variable.initial_values]; + scaleToBounds( ... + variable.initial_values, ... + variable.upper_bounds, ... + variable.lower_bounds)]; end end diff --git a/src/DesignOptimization/getDesignOptimizationValueStruct.m b/src/DesignOptimization/getDesignOptimizationValueStruct.m index e99aad65c..17975ab0e 100644 --- a/src/DesignOptimization/getDesignOptimizationValueStruct.m +++ b/src/DesignOptimization/getDesignOptimizationValueStruct.m @@ -53,6 +53,9 @@ params.numCoordinates + params.numTorqueControls); end for i = 1:length(params.userDefinedVariables) - values.(params.userDefinedVariables{i}.type) = inputs.parameter(i, 1); + values.(params.userDefinedVariables{i}.type) = scaleToOriginal( ... + inputs.parameter(i, 1), ... + params.userDefinedVariables{i}.upper_bounds, ... + params.userDefinedVariables{i}.lower_bounds); end end \ No newline at end of file diff --git a/src/DesignOptimization/reportDesignOptimizationResults.m b/src/DesignOptimization/reportDesignOptimizationResults.m index cb2f8865e..c2dc968ff 100644 --- a/src/DesignOptimization/reportDesignOptimizationResults.m +++ b/src/DesignOptimization/reportDesignOptimizationResults.m @@ -26,10 +26,11 @@ % ----------------------------------------------------------------------- % function reportDesignOptimizationResults(solution, inputs) -if isfield(solution.solution, "parameter") - parameterResults = solution.solution.parameter -end values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); +for i = 1:length(inputs.userDefinedVariables) + disp(inputs.userDefinedVariables{i}.type) + disp(values.(inputs.userDefinedVariables{i}.type)) +end if strcmp(inputs.controllerType, 'synergy_driven') % plot Muscle Activations plotMuscleActivations(solution.muscleActivations, values.time, ... From c11debe16ddcf17d1a2457761669c9401dfb7d19 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 22 May 2023 17:25:58 -0500 Subject: [PATCH 20/72] Update GCP osimx --- .../GroundContactPersonalizationTool.m | 2 +- ...iteGroundContactPersonalizationOsimxFile.m | 68 ++++++++++++++----- ...GroundContactPersonalizationSettingsTree.m | 5 +- src/core/osimx/addGcpContactSurface.m | 67 ------------------ src/core/osimx/addGcpSpring.m | 51 -------------- src/core/osimx/buildGcpOsimxTemplate.m | 23 ++----- 6 files changed, 61 insertions(+), 155 deletions(-) delete mode 100644 src/core/osimx/addGcpContactSurface.m delete mode 100644 src/core/osimx/addGcpSpring.m diff --git a/src/GroundContactPersonalization/GroundContactPersonalizationTool.m b/src/GroundContactPersonalization/GroundContactPersonalizationTool.m index 6258eb5af..f9b90bc65 100644 --- a/src/GroundContactPersonalization/GroundContactPersonalizationTool.m +++ b/src/GroundContactPersonalization/GroundContactPersonalizationTool.m @@ -35,6 +35,6 @@ function GroundContactPersonalizationTool(settingsFileName) parseGroundContactPersonalizationSettingsTree(settingsTree); results = GroundContactPersonalization(inputs, params); saveGroundContactPersonalizationResults(results, params, ... - resultsDirectory, inputs.osimxFileName); + resultsDirectory, inputs.inputOsimxFile); end diff --git a/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m b/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m index b78e6db05..86c9db2b3 100644 --- a/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m +++ b/src/GroundContactPersonalization/Saving/writeGroundContactPersonalizationOsimxFile.m @@ -29,12 +29,12 @@ function writeGroundContactPersonalizationOsimxFile(inputs, ... resultsDirectory, osimxFileName) -modelFileName = inputs.model; +modelFileName = inputs.bodyModel; model = Model(modelFileName); if isfile(osimxFileName) - osimx = parseOsimxFile(inputs.osimxFileName); - [~, name, ~] = fileparts(inputs.osimxFileName); + osimx = parseOsimxFile(inputs.inputOsimxFile); + [~, name, ~] = fileparts(inputs.inputOsimxFile); outfile = fullfile(resultsDirectory, strcat(name, "_gcp.xml")); else osimx = buildGcpOsimxTemplate(... @@ -42,20 +42,56 @@ function writeGroundContactPersonalizationOsimxFile(inputs, ... [~, name, ~] = fileparts(modelFileName); outfile = fullfile(resultsDirectory, strcat(name, "_gcp.xml")); end +osimx.modelName = name; +osimx.model = modelFileName; -%% To update -osimx = buildGcpOsimxTemplate(... - replace(bodyModel.getName().toCharArray',".","_dot_"), ... - inputs.bodyModel, ... - inputs.restingSpringLength, ... - inputs.dynamicFrictionCoefficient, ... - inputs.dampingFactor ... - ); - -for surface = 1:length(inputs.surfaces) - osimx = addGcpContactSurface(osimx,inputs.surfaces{surface}, ... - inputs.springConstants); +osimx = addGcpContactSurfaces(osimx, inputs); + +writeOsimxFile(buildOsimxFromOsimxStruct(osimx), outfile); +end + +function osimx = addGcpContactSurfaces(osimx, inputs) +if ~isfield(osimx, 'groundContact') + osimx.groundContact.contactSurface = {}; +end +for foot = 1:length(inputs.surfaces) + newSurface.isLeftFoot = inputs.surfaces{foot}.isLeftFoot; + newSurface.beltSpeed = inputs.surfaces{foot}.beltSpeed; + newSurface.forceColumns = string(inputs.surfaces{foot}.forceColumns)'; + newSurface.momentColumns = string(inputs.surfaces{foot}.momentColumns)'; + newSurface.electricalCenterColumns = string(inputs.surfaces{foot} ... + .electricalCenterColumns)'; + newSurface.toesCoordinateName = inputs.surfaces{foot} ... + .toesCoordinateName; + newSurface.toesJointName = inputs.surfaces{foot}.toesJointName; + newSurface.toeMarker = inputs.surfaces{foot}.markerNames.toe; + newSurface.medialMarker = inputs.surfaces{foot}.markerNames.medial; + newSurface.lateralMarker = inputs.surfaces{foot}.markerNames.lateral; + newSurface.heelMarker = inputs.surfaces{foot}.markerNames.heel; + newSurface.midfootSuperiorMarker = inputs.surfaces{foot} ... + .markerNames.midfootSuperior; + newSurface.restingSpringLength = inputs.restingSpringLength; + newSurface.dynamicFrictionCoefficient = inputs ... + .dynamicFrictionCoefficient; + newSurface.viscousFrictionCoefficient = inputs ... + .viscousFrictionCoefficient; + newSurface.dampingFactor = inputs.dampingFactor; + newSurface.latchingVelocity = inputs.latchingVelocity; + + for i = 1:length(inputs.springConstants) + newSurface.springs{i} = addGcpSpring(inputs, foot, i); + end + + index = 1 + length(osimx.groundContact.contactSurface); + osimx.groundContact.contactSurface{index} = newSurface; +end end -writeOsimxFile(osimx, groundContactModelFileName); +function spring = addGcpSpring(inputs, foot, springNumber) + spring.name = "spring_marker_" + springNumber; + model = Model("footModel_" + foot + ".osim"); + springMarker = model.getMarkerSet.get(spring.name); + spring.parentBody = getMarkerBodyName(model, spring.name); + spring.location = Vec3ToArray(springMarker.get_location()); + spring.springConstant = inputs.springConstants(springNumber); end diff --git a/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m b/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m index 308c031a8..562c74882 100644 --- a/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m +++ b/src/GroundContactPersonalization/parseGroundContactPersonalizationSettingsTree.m @@ -43,7 +43,7 @@ inputDirectory = getTextFromField(getFieldByNameOrAlternate(tree, ... 'input_directory', pwd)); inputs.inputOsimxFile = getTextFromField(getFieldByNameOrAlternate( ... - tree, 'input_osimx_file', [])); + tree, 'input_osimx_file', '')); inputs.bodyModel = getFieldByNameOrError(tree, 'input_model_file').Text; motionFile = getFieldByNameOrError(tree, 'input_motion_file').Text; grfFile = getFieldByNameOrError(tree, 'input_grf_file').Text; @@ -90,8 +90,7 @@ output{counter} = getMotionTime(inputs.bodyModel, ... inputs.motionFileName, output{counter}); verifyTime(output{counter}.grfTime, output{counter}.time); - tempFields = {'forceColumns', 'momentColumns', ... - 'electricalCenterColumns', 'grfTime', 'startTime', 'endTime'}; + tempFields = {'grfTime', 'startTime', 'endTime'}; output{counter} = rmfield(output{counter}, tempFields); counter = counter + 1; end diff --git a/src/core/osimx/addGcpContactSurface.m b/src/core/osimx/addGcpContactSurface.m deleted file mode 100644 index 2f241e01a..000000000 --- a/src/core/osimx/addGcpContactSurface.m +++ /dev/null @@ -1,67 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% This function adds a GcpContactSurface to an existing osimx struct -% created by buildGcpOsimxTemplate() or buildOsimxTemplate() -% -% (string, string, number, number, number) -> (struct) -% Prints a generic template for an osimx file - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function osimx = addGcpContactSurface(osimx, surface, springConstants) - -osimx.NMSMPipelineDocument.OsimxModel.RCNLGroundContact.Comment = ... - 'The modeled ground contact data'; -groundContact = osimx.NMSMPipelineDocument.OsimxModel.RCNLGroundContact; - -groundContact.GCPContactSurfaceSet.GCPContactSurface.is_left_foot.Comment = ... - 'Flag indicating whether foot model should be mirrored'; -isLeftFoot = "false"; if surface.isLeftFoot; isLeftFoot = "true"; end -groundContact.GCPContactSurfaceSet.GCPContactSurface.is_left_foot.Text = ... - convertStringsToChars(isLeftFoot); -groundContact.GCPContactSurfaceSet.GCPContactSurface.toes_coordinate.Comment = ... - 'Name of the toe angle coordinate in the model file'; -groundContact.GCPContactSurfaceSet.GCPContactSurface.toes_coordinate.Text = ... - surface.toesCoordinateName; -groundContact.GCPContactSurfaceSet.GCPContactSurface.toes_joint.Comment = ... - 'Name of the toe joint in the model file'; -groundContact.GCPContactSurfaceSet.GCPContactSurface.toes_joint.Text = ... - surface.toesJointName; - -groundContact.GCPContactSurfaceSet.Comment = 'The set of contact surfaces modeled'; - -groundContact.GCPContactSurfaceSet.GCPContactSurface.Comment = ... - 'The set of contact surfaces modeled'; -contactSurface = groundContact.GCPContactSurfaceSet.GCPContactSurface; -contactSurface.GCPSpringSet.Comment = 'The set of springs for the contact surface'; - -model = Model(surface.model); -for marker = 1:surface.numSpringMarkers - contactSurface = addGcpSpring(contactSurface, model, marker, ... - springConstants(marker)); -end -contactSurface.GCPSpringSet.groups = ''; -groundContact.GCPContactSurfaceSet.GCPContactSurface = contactSurface; -osimx.NMSMPipelineDocument.OsimxModel.RCNLGroundContact = groundContact; -end - diff --git a/src/core/osimx/addGcpSpring.m b/src/core/osimx/addGcpSpring.m deleted file mode 100644 index 188de6ce9..000000000 --- a/src/core/osimx/addGcpSpring.m +++ /dev/null @@ -1,51 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% This function adds the information about a spring to the -% in a struct made fom buildGcpOsimxTemplate -% -% (struct, Model, number, 1D array of number) -> (struct) -% Adds a spring to the contact surface of an osimx file - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function contactSurface = addGcpSpring(contactSurface, model, ... - markerNumber, springConstant) -markerName = "spring_marker_" + markerNumber; -springMarker = model.getMarkerSet.get(markerName); -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.Attributes.name = ... - convertStringsToChars(markerName); -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.parent_body.Comment = ... - 'The body that the spring is attached to'; -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.parent_body.Text = ... - getMarkerBodyName(model, markerName); -location = springMarker.get_location(); -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.location.Comment = ... - 'The location of the spring in the body it is attached to'; -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.location.Text = ... - num2str([location.get(0) location.get(1) location.get(2)], 15); -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.spring_constant.Comment = ... - 'The modeled spring constant for the spring'; -contactSurface.GCPSpringSet.objects.GCPSpring{markerNumber}.spring_constant.Text = ... - num2str(springConstant, 15); -end - diff --git a/src/core/osimx/buildGcpOsimxTemplate.m b/src/core/osimx/buildGcpOsimxTemplate.m index a08a7d434..ba9d009e9 100644 --- a/src/core/osimx/buildGcpOsimxTemplate.m +++ b/src/core/osimx/buildGcpOsimxTemplate.m @@ -15,7 +15,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond % +% Author(s): Claire V. Hammond, Spencer Williams % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -29,25 +29,14 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function osimx = buildGcpOsimxTemplate(modelName, ... - osimModelFileName, restingSpringLength, ... - dynamicFrictionCoefficient, dampingFactor) +function osimx = buildGcpOsimxTemplate(modelName, osimModelFileName) osimx = buildOsimxTemplate(modelName, osimModelFileName); -body = osimx.NMSMPipelineDocument.OsimxModel; - -body.RCNLGroundContact.resting_spring_length.Comment = ... - 'The resting spring length of the surface'; -body.RCNLGroundContact.resting_spring_length.Text = convertStringsToChars(num2str(restingSpringLength)); -body.RCNLGroundContact.dynamic_friction_coefficient.Comment = ... - 'The dynamic friction coefficient of the surface'; -body.RCNLGroundContact.dynamic_friction_coefficient.Text = ... - convertStringsToChars(num2str(dynamicFrictionCoefficient)); -body.RCNLGroundContact.damping_factor.Comment = 'The damping factor of the surface'; -body.RCNLGroundContact.damping_factor.Text = convertStringsToChars(num2str(dampingFactor)); - -osimx.NMSMPipelineDocument.OsimxModel = body; +osimx.NMSMPipelineDocument.OsimxModel.RCNLContactSurfaceSet.Comment = ... + 'Optimized contact surface parameters'; +osimx.NMSMPipelineDocument.OsimxModel.RCNLContactSurfaceSet.objects = ''; +osimx.NMSMPipelineDocument.OsimxModel.RCNLContactSurfaceSet.groups = ''; end From 76f98787336dbc60dec9fc73137583ac22c06475 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" Date: Tue, 23 May 2023 10:24:09 -0700 Subject: [PATCH 21/72] consolidated report fn --- .../DesignOptimizationTool.m | 4 +- .../TrackingOptimizationTool.m | 4 +- .../reportTrackingOptimizationResults.m | 118 ------------------ .../VerificationOptimizationTool.m | 4 +- .../reportVerificationOptimizationResults.m | 118 ------------------ .../reportTreatmentOptimizationResults.m} | 21 ++-- 6 files changed, 17 insertions(+), 252 deletions(-) delete mode 100644 src/TrackingOptimization/reportTrackingOptimizationResults.m delete mode 100644 src/VerificationOptimization/reportVerificationOptimizationResults.m rename src/{DesignOptimization/reportDesignOptimizationResults.m => core/TreatmentOptimization/reportTreatmentOptimizationResults.m} (94%) diff --git a/src/DesignOptimization/DesignOptimizationTool.m b/src/DesignOptimization/DesignOptimizationTool.m index 6afa845de..a1861897b 100644 --- a/src/DesignOptimization/DesignOptimizationTool.m +++ b/src/DesignOptimization/DesignOptimizationTool.m @@ -33,6 +33,6 @@ function DesignOptimizationTool(settingsFileName) settingsTree = xml2struct(settingsFileName); [inputs, params] = parseDesignOptimizationSettingsTree(settingsTree); [outputs, inputs] = DesignOptimization(inputs, params); -reportDesignOptimizationResults(outputs, inputs); +reportTreatmentOptimizationResults(outputs, inputs); saveDesignOptimizationResults(outputs, inputs); -end \ No newline at end of file +end diff --git a/src/TrackingOptimization/TrackingOptimizationTool.m b/src/TrackingOptimization/TrackingOptimizationTool.m index 3f33a5a46..d8d9592f1 100644 --- a/src/TrackingOptimization/TrackingOptimizationTool.m +++ b/src/TrackingOptimization/TrackingOptimizationTool.m @@ -33,6 +33,6 @@ function TrackingOptimizationTool(settingsFileName) settingsTree = xml2struct(settingsFileName); [inputs, params] = parseTrackingOptimizationSettingsTree(settingsTree); [outputs, inputs] = TrackingOptimization(inputs, params); -reportTrackingOptimizationResults(outputs, inputs); +reportTreatmentOptimizationResults(outputs, inputs); saveTrackingOptimizationResults(outputs, inputs); -end \ No newline at end of file +end diff --git a/src/TrackingOptimization/reportTrackingOptimizationResults.m b/src/TrackingOptimization/reportTrackingOptimizationResults.m deleted file mode 100644 index 801b40e65..000000000 --- a/src/TrackingOptimization/reportTrackingOptimizationResults.m +++ /dev/null @@ -1,118 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function reportTrackingOptimizationResults(solution, inputs) - -values = getTrackingOptimizationValueStruct(solution.solution.phase, inputs); -if strcmp(inputs.controllerType, 'synergy_driven') -% plot Muscle Activations -plotMuscleActivations(solution.muscleActivations, values.time, ... - inputs.experimentalMuscleActivations, inputs.experimentalTime, ... - inputs.muscleLabels); -% plot synergy activations -synergyTitles = {}; -for i = 1 : inputs.numSynergies - synergyTitles{end + 1} = strcat('synergy activation', num2str(i)); -end -plotResultsWithOutComparison(values.controlSynergyActivations, values.time, ... - synergyTitles, ["Synergy" "Activations"]); -end -% plot joint angles -plotResultsWithComparison(values.statePositions, values.time, ... - inputs.experimentalJointAngles, inputs.experimentalTime, ... - strcat(inputs.coordinateNames, ' [rad]'), ["Joint" "Angles [rad]"]); -% plot joint moments -plotResultsWithComparison(solution.inverseDynamicMoments, values.time, ... - inputs.experimentalJointMoments, inputs.experimentalTime, ... - strcat(inputs.inverseDynamicMomentLabels, ' [Nm]'), ["Joint" "Moments"]); -% plot ground reactions -for i = 1:length(inputs.contactSurfaces) -plotResultsWithComparison(solution.groundReactionsLab.forces{i}, values.time, ... - inputs.contactSurfaces{i}.experimentalGroundReactionForces, inputs.experimentalTime, ... - ["GRFx", "GRFy", "GRFz"], ["Ground" "Reaction Forces"]); -plotResultsWithComparison(solution.groundReactionsLab.moments{i}, values.time, ... - inputs.contactSurfaces{i}.experimentalGroundReactionMoments, inputs.experimentalTime, ... - ["GRTx", "GRTy", "GRTz"], ["Ground" "Reaction Moments"]); -end -end -function plotMuscleActivations(muscleActivations, time, ... - experimentalMuscleActivations, experimentalTime, muscleLabels) - -figure('name', 'Muscle Activations') -nplots = ceil(sqrt(size(muscleActivations, 2))); -for i = 1 : size(muscleActivations,2) -subplot(nplots, nplots, i) -plot(time, muscleActivations(:, i)); hold on -plot(experimentalTime, experimentalMuscleActivations(:, i), 'r') -axis([0 1 0 experimentalTime(end)]) -title(muscleLabels(i)) -figureXLabels(numel(muscleLabels), nplots, i, "Time") -figureYLabels(numel(muscleLabels), nplots, i, ["Muscle" "Activation"]) -end -end -function plotResultsWithComparison(solutionData, solutionTime, ... - experimentalData, experimentalTime, labels, figureTitle) - -figure('name', strjoin(figureTitle)) -nplots = ceil(sqrt(numel(labels))); -for i = 1 : size(solutionData,2) -subplot(nplots, nplots, i) -plot(solutionTime, solutionData(:, i)); hold on -plot(experimentalTime, experimentalData(:, i), 'r') -xlim([0 experimentalTime(end)]) -title(labels(i)) -figureXLabels(numel(labels), nplots, i, "Time") -end -end -function plotResultsWithOutComparison(solutionData, solutionTime, ... - labels, figureTitle) - -figure('name', strjoin(figureTitle)) -for i = 1 : size(solutionData,2) -subplot(numel(labels), 1, i) -plot(solutionTime, solutionData(:, i)); hold on -xlim([0 solutionTime(end)]) -title(labels(i)) -figureXLabels(numel(labels), 1, i, "Time") -end -end -function figureXLabels(numTotalPlots, numColumnPlots, plotIndex, xLabel) - -if plotIndex > numTotalPlots - numColumnPlots - xlabel(xLabel); -else - xticklabels(''); -end -end -function figureYLabels(numTotalPlots, numColumnPlots, plotIndex, yLabel) - -if ismember(plotIndex, 1 : numColumnPlots : numTotalPlots) - ylabel({yLabel(1); yLabel(2)}); -else - yticklabels(''); -end -end \ No newline at end of file diff --git a/src/VerificationOptimization/VerificationOptimizationTool.m b/src/VerificationOptimization/VerificationOptimizationTool.m index 96bb357aa..7c8935c8d 100644 --- a/src/VerificationOptimization/VerificationOptimizationTool.m +++ b/src/VerificationOptimization/VerificationOptimizationTool.m @@ -33,6 +33,6 @@ function VerificationOptimizationTool(settingsFileName) settingsTree = xml2struct(settingsFileName); [inputs, params] = parseVerificationOptimizationSettingsTree(settingsTree); [outputs, inputs] = VerificationOptimization(inputs, params); -reportVerificationOptimizationResults(outputs, inputs); +reportTreatmentOptimizationResults(outputs, inputs); saveVerificationOptimizationResults(outputs, inputs); -end \ No newline at end of file +end diff --git a/src/VerificationOptimization/reportVerificationOptimizationResults.m b/src/VerificationOptimization/reportVerificationOptimizationResults.m deleted file mode 100644 index 1e873e93e..000000000 --- a/src/VerificationOptimization/reportVerificationOptimizationResults.m +++ /dev/null @@ -1,118 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function reportVerificationOptimizationResults(solution, inputs) - -values = getVerificationOptimizationValueStruct(solution.solution.phase, inputs); -if strcmp(inputs.controllerType, 'synergy_driven') -% plot Muscle Activations -plotMuscleActivations(solution.muscleActivations, values.time, ... - inputs.experimentalMuscleActivations, inputs.experimentalTime, ... - inputs.muscleLabels); -% plot synergy activations -synergyTitles = {}; -for i = 1 : inputs.numSynergies - synergyTitles{end + 1} = strcat('synergy activation', num2str(i)); -end -plotResultsWithOutComparison(values.controlSynergyActivations, values.time, ... - synergyTitles, ["Synergy" "Activations"]); -end -% plot joint angles -plotResultsWithComparison(values.statePositions, values.time, ... - inputs.experimentalJointAngles, inputs.experimentalTime, ... - strcat(inputs.coordinateNames, ' [rad]'), ["Joint" "Angles [rad]"]); -% plot joint moments -plotResultsWithComparison(solution.inverseDynamicMoments, values.time, ... - inputs.experimentalJointMoments, inputs.experimentalTime, ... - strcat(inputs.inverseDynamicMomentLabels, ' [Nm]'), ["Joint" "Moments"]); -% plot ground reactions -for i = 1:length(inputs.contactSurfaces) -plotResultsWithComparison(solution.groundReactionsLab.forces{i}, values.time, ... - inputs.contactSurfaces{i}.experimentalGroundReactionForces, inputs.experimentalTime, ... - ["GRFx", "GRFy", "GRFz"], ["Ground" "Reaction Forces"]); -plotResultsWithComparison(solution.groundReactionsLab.moments{i}, values.time, ... - inputs.contactSurfaces{i}.experimentalGroundReactionMoments, inputs.experimentalTime, ... - ["GRTx", "GRTy", "GRTz"], ["Ground" "Reaction Moments"]); -end -end -function plotMuscleActivations(muscleActivations, time, ... - experimentalMuscleActivations, experimentalTime, muscleLabels) - -figure('name', 'Muscle Activations') -nplots = ceil(sqrt(size(muscleActivations, 2))); -for i = 1 : size(muscleActivations,2) -subplot(nplots, nplots, i) -plot(time, muscleActivations(:, i)); hold on -plot(experimentalTime, experimentalMuscleActivations(:, i), 'r') -axis([0 1 0 experimentalTime(end)]) -title(muscleLabels(i)) -figureXLabels(numel(muscleLabels), nplots, i, "Time") -figureYLabels(numel(muscleLabels), nplots, i, ["Muscle" "Activation"]) -end -end -function plotResultsWithComparison(solutionData, solutionTime, ... - experimentalData, experimentalTime, labels, figureTitle) - -figure('name', strjoin(figureTitle)) -nplots = ceil(sqrt(numel(labels))); -for i = 1 : size(solutionData,2) -subplot(nplots, nplots, i) -plot(solutionTime, solutionData(:, i)); hold on -plot(experimentalTime, experimentalData(:, i), 'r') -xlim([0 experimentalTime(end)]) -title(labels(i)) -figureXLabels(numel(labels), nplots, i, "Time") -end -end -function plotResultsWithOutComparison(solutionData, solutionTime, ... - labels, figureTitle) - -figure('name', strjoin(figureTitle)) -for i = 1 : size(solutionData,2) -subplot(numel(labels), 1, i) -plot(solutionTime, solutionData(:, i)); hold on -xlim([0 solutionTime(end)]) -title(labels(i)) -figureXLabels(numel(labels), 1, i, "Time") -end -end -function figureXLabels(numTotalPlots, numColumnPlots, plotIndex, xLabel) - -if plotIndex > numTotalPlots - numColumnPlots - xlabel(xLabel); -else - xticklabels(''); -end -end -function figureYLabels(numTotalPlots, numColumnPlots, plotIndex, yLabel) - -if ismember(plotIndex, 1 : numColumnPlots : numTotalPlots) - ylabel({yLabel(1); yLabel(2)}); -else - yticklabels(''); -end -end \ No newline at end of file diff --git a/src/DesignOptimization/reportDesignOptimizationResults.m b/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m similarity index 94% rename from src/DesignOptimization/reportDesignOptimizationResults.m rename to src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m index cb2f8865e..50782ad8b 100644 --- a/src/DesignOptimization/reportDesignOptimizationResults.m +++ b/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -25,12 +25,13 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function reportDesignOptimizationResults(solution, inputs) +function reportTreatmentOptimizationResults(solution, inputs) + if isfield(solution.solution, "parameter") parameterResults = solution.solution.parameter end -values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); -if strcmp(inputs.controllerType, 'synergy_driven') +values = getTrackingOptimizationValueStruct(solution.solution.phase, inputs); +if strcmp(inputs.controllerType, 'synergy_driven') % plot Muscle Activations plotMuscleActivations(solution.muscleActivations, values.time, ... inputs.experimentalMuscleActivations, inputs.experimentalTime, ... @@ -105,16 +106,16 @@ function plotResultsWithOutComparison(solutionData, solutionTime, ... function figureXLabels(numTotalPlots, numColumnPlots, plotIndex, xLabel) if plotIndex > numTotalPlots - numColumnPlots - xlabel(xLabel); -else - xticklabels(''); + xlabel(xLabel); +else + xticklabels(''); end end function figureYLabels(numTotalPlots, numColumnPlots, plotIndex, yLabel) if ismember(plotIndex, 1 : numColumnPlots : numTotalPlots) ylabel({yLabel(1); yLabel(2)}); -else - yticklabels(''); +else + yticklabels(''); +end end -end \ No newline at end of file From 786b58bc639476f7ba957a098a65bb947c9121cd Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" Date: Tue, 23 May 2023 11:07:31 -0700 Subject: [PATCH 22/72] consolidated tool input generation --- src/DesignOptimization/DesignOptimization.m | 21 +++------- .../TrackingOptimization.m | 18 ++------ .../VerificationOptimization.m | 21 +++------- .../makeTreatmentOptimizationInputs.m | 41 +++++++++++++++++++ 4 files changed, 54 insertions(+), 47 deletions(-) create mode 100644 src/core/TreatmentOptimization/makeTreatmentOptimizationInputs.m diff --git a/src/DesignOptimization/DesignOptimization.m b/src/DesignOptimization/DesignOptimization.m index 489549639..23ebb3d64 100644 --- a/src/DesignOptimization/DesignOptimization.m +++ b/src/DesignOptimization/DesignOptimization.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -26,19 +26,8 @@ % ----------------------------------------------------------------------- % function [output, inputs] = DesignOptimization(inputs, params) -pointKinematics(inputs.mexModel); -inverseDynamics(inputs.mexModel); -inputs = getStateDerivatives(inputs); -inputs = setupGroundContact(inputs); -inputs = getSplines(inputs); -inputs = checkStateGuess(inputs); -inputs = checkControlGuess(inputs); -inputs = checkParameterGuess(inputs); -inputs = getIntegralBounds(inputs); -inputs = getPathConstraintBounds(inputs); -inputs = getTerminalConstraintBounds(inputs); -inputs = getDesignVariableInputBounds(inputs); -if strcmp(inputs.controllerType, 'synergy_driven') +inputs = makeTreatmentOptimizationInputs(inputs, params); +if strcmp(inputs.controllerType, 'synergy_driven') inputs = setupMuscleSynergies(inputs); end output = computeDesignOptimizationMainFunction(inputs, params); @@ -47,4 +36,4 @@ inputs.splineSynergyActivations = spaps(inputs.initialGuess.time, ... inputs.initialGuess.control(:, inputs.numCoordinates + 1:end)', 0.0000001); inputs.synergyLabels = inputs.initialGuess.controlLabels(:, inputs.numCoordinates + 1:end); -end \ No newline at end of file +end diff --git a/src/TrackingOptimization/TrackingOptimization.m b/src/TrackingOptimization/TrackingOptimization.m index 44aa793a1..98afa64ad 100644 --- a/src/TrackingOptimization/TrackingOptimization.m +++ b/src/TrackingOptimization/TrackingOptimization.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -26,18 +26,6 @@ % ----------------------------------------------------------------------- % function [output, inputs] = TrackingOptimization(inputs, params) -pointKinematics(inputs.mexModel); -inverseDynamics(inputs.mexModel); -inputs -inputs = getStateDerivatives(inputs); -inputs = setupGroundContact(inputs); -inputs = getSplines(inputs); -inputs = checkStateGuess(inputs); -inputs = checkControlGuess(inputs); -inputs = checkParameterGuess(inputs); -inputs = getIntegralBounds(inputs); -inputs = getPathConstraintBounds(inputs); -inputs = getTerminalConstraintBounds(inputs); -inputs = getDesignVariableInputBounds(inputs); +inputs = makeTreatmentOptimizationInputs(inputs, params); output = computeTrackingOptimizationMainFunction(inputs, params); end diff --git a/src/VerificationOptimization/VerificationOptimization.m b/src/VerificationOptimization/VerificationOptimization.m index 4a09333fd..f88f39073 100644 --- a/src/VerificationOptimization/VerificationOptimization.m +++ b/src/VerificationOptimization/VerificationOptimization.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -26,19 +26,8 @@ % ----------------------------------------------------------------------- % function [output, inputs] = VerificationOptimization(inputs, params) -pointKinematics(inputs.mexModel); -inverseDynamics(inputs.mexModel); -inputs = getStateDerivatives(inputs); -inputs = setupGroundContact(inputs); -inputs = getSplines(inputs); -inputs = checkStateGuess(inputs); -inputs = checkControlGuess(inputs); -inputs = checkParameterGuess(inputs); -inputs = getIntegralBounds(inputs); -inputs = getPathConstraintBounds(inputs); -inputs = getTerminalConstraintBounds(inputs); -inputs = getDesignVariableInputBounds(inputs); -if strcmp(inputs.controllerType, 'synergy_driven') +inputs = makeTreatmentOptimizationInputs(inputs, params); +if strcmp(inputs.controllerType, 'synergy_driven') inputs = setupMuscleSynergies(inputs); end output = computeVerificationOptimizationMainFunction(inputs, params); @@ -47,4 +36,4 @@ inputs.splineSynergyActivations = spaps(inputs.initialGuess.time, ... inputs.initialGuess.control(:, inputs.numCoordinates + 1:end)', 0.0000001); inputs.synergyLabels = inputs.initialGuess.controlLabels(:, inputs.numCoordinates + 1:end); -end \ No newline at end of file +end diff --git a/src/core/TreatmentOptimization/makeTreatmentOptimizationInputs.m b/src/core/TreatmentOptimization/makeTreatmentOptimizationInputs.m new file mode 100644 index 000000000..ec7a742b3 --- /dev/null +++ b/src/core/TreatmentOptimization/makeTreatmentOptimizationInputs.m @@ -0,0 +1,41 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega, Claire V. Hammond % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function inputs = makeTreatmentOptimizationInputs(inputs, params) +pointKinematics(inputs.mexModel); +inverseDynamics(inputs.mexModel); +inputs = getStateDerivatives(inputs); +inputs = setupGroundContact(inputs); +inputs = getSplines(inputs); +inputs = checkStateGuess(inputs); +inputs = checkControlGuess(inputs); +inputs = checkParameterGuess(inputs); +inputs = getIntegralBounds(inputs); +inputs = getPathConstraintBounds(inputs); +inputs = getTerminalConstraintBounds(inputs); +inputs = getDesignVariableInputBounds(inputs); +end From 93f7ad04da2f8b5d0268a22e818b3299f50e9cdb Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" Date: Tue, 23 May 2023 11:15:43 -0700 Subject: [PATCH 23/72] consolidated values struct creation --- .../getDesignOptimizationValueStruct.m | 24 +++------- .../getTrackingOptimizationValueStruct.m | 25 +++------- .../getVerificationOptimizationValueStruct.m | 23 ++-------- .../getTreatmentOptimizationValueStruct.m | 46 +++++++++++++++++++ 4 files changed, 63 insertions(+), 55 deletions(-) create mode 100644 src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m diff --git a/src/DesignOptimization/getDesignOptimizationValueStruct.m b/src/DesignOptimization/getDesignOptimizationValueStruct.m index e99aad65c..96ce7b1f6 100644 --- a/src/DesignOptimization/getDesignOptimizationValueStruct.m +++ b/src/DesignOptimization/getDesignOptimizationValueStruct.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -26,18 +26,9 @@ % ----------------------------------------------------------------------- % function values = getDesignOptimizationValueStruct(inputs, params) -values.time = scaleToOriginal(inputs.time, params.maxTime, ... - params.minTime); -state = scaleToOriginal(inputs.state, ones(size(inputs.state, 1), 1) .* ... - params.maxState, ones(size(inputs.state, 1), 1) .* params.minState); -control = scaleToOriginal(inputs.control, ones(size(inputs.control, 1), 1) .* ... - params.maxControl, ones(size(inputs.control, 1), 1) .* params.minControl); -values.statePositions = getCorrectStates(state, 1, params.numCoordinates); -values.stateVelocities = getCorrectStates(state, 2, params.numCoordinates); -values.stateAccelerations = getCorrectStates(state, 3, params.numCoordinates); -values.controlJerks = control(:, 1 : params.numCoordinates); -if strcmp(params.controllerType, 'synergy_driven') - if params.optimizeSynergyVectors +values = getTreatmentOptimizationValueStruct(inputs, params); +if strcmp(params.controllerType, 'synergy_driven') + if params.optimizeSynergyVectors values.synergyWeights = scaleToOriginal(inputs.parameter(1,:), ... params.maxParameter, params.minParameter); values.synergyWeights = getSynergyWeightsFromGroups(... @@ -48,11 +39,8 @@ end values.controlSynergyActivations = control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numSynergies); -else - values.controlTorques = control(:, params.numCoordinates + 1 : ... - params.numCoordinates + params.numTorqueControls); end for i = 1:length(params.userDefinedVariables) values.(params.userDefinedVariables{i}.type) = inputs.parameter(i, 1); end -end \ No newline at end of file +end diff --git a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m index f5aa1e386..00741a496 100644 --- a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m +++ b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -26,19 +26,9 @@ % ----------------------------------------------------------------------- % function values = getTrackingOptimizationValueStruct(inputs, params) - -values.time = scaleToOriginal(inputs.time, params.maxTime, ... - params.minTime); -state = scaleToOriginal(inputs.state, ones(size(inputs.state, 1), 1) .* ... - params.maxState, ones(size(inputs.state, 1), 1) .* params.minState); -control = scaleToOriginal(inputs.control, ones(size(inputs.control, 1), 1) .* ... - params.maxControl, ones(size(inputs.control, 1), 1) .* params.minControl); -values.statePositions = getCorrectStates(state, 1, params.numCoordinates); -values.stateVelocities = getCorrectStates(state, 2, params.numCoordinates); -values.stateAccelerations = getCorrectStates(state, 3, params.numCoordinates); -values.controlJerks = control(:, 1 : params.numCoordinates); -if strcmp(params.controllerType, 'synergy_driven') - if params.optimizeSynergyVectors +values = getTreatmentOptimizationValueStruct(inputs, params); +if strcmp(params.controllerType, 'synergy_driven') + if params.optimizeSynergyVectors values.synergyWeights = scaleToOriginal(inputs.parameter(1,:), ... params.maxParameter, params.minParameter); values.synergyWeights = getSynergyWeightsFromGroups(... @@ -49,8 +39,5 @@ end values.controlSynergyActivations = control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numSynergies); -else - values.controlTorques = control(:, params.numCoordinates + 1 : ... - params.numCoordinates + params.numTorqueControls); end -end \ No newline at end of file +end diff --git a/src/VerificationOptimization/getVerificationOptimizationValueStruct.m b/src/VerificationOptimization/getVerificationOptimizationValueStruct.m index 542e2b562..f123b38ce 100644 --- a/src/VerificationOptimization/getVerificationOptimizationValueStruct.m +++ b/src/VerificationOptimization/getVerificationOptimizationValueStruct.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % +% Author(s): Marleny Vega, Claire V. Hammond % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -26,24 +26,11 @@ % ----------------------------------------------------------------------- % function values = getVerificationOptimizationValueStruct(inputs, params) - -values.time = scaleToOriginal(inputs.time, params.maxTime, ... - params.minTime); -state = scaleToOriginal(inputs.state, ones(size(inputs.state, 1), 1) .* ... - params.maxState, ones(size(inputs.state, 1), 1) .* params.minState); -control = scaleToOriginal(inputs.control, ones(size(inputs.control, 1), 1) .* ... - params.maxControl, ones(size(inputs.control, 1), 1) .* params.minControl); -values.statePositions = getCorrectStates(state, 1, params.numCoordinates); -values.stateVelocities = getCorrectStates(state, 2, params.numCoordinates); -values.stateAccelerations = getCorrectStates(state, 3, params.numCoordinates); -values.controlJerks = control(:, 1 : params.numCoordinates); -if strcmp(params.controllerType, 'synergy_driven') +values = getTreatmentOptimizationValueStruct(inputs, params); +if strcmp(params.controllerType, 'synergy_driven') values.controlSynergyActivations = control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numSynergies); values.synergyWeights = getSynergyWeightsFromGroups(... params.synergyWeightsGuess, params); -else - values.controlTorques = control(:, params.numCoordinates + 1 : ... - params.numCoordinates + params.numTorqueControls); end -end \ No newline at end of file +end diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m b/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m new file mode 100644 index 000000000..0460295e5 --- /dev/null +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m @@ -0,0 +1,46 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega, Claire V. Hammond % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function values = getTreatmentOptimizationValueStruct(inputs, params) + +values.time = scaleToOriginal(inputs.time, params.maxTime, ... + params.minTime); +state = scaleToOriginal(inputs.state, ones(size(inputs.state, 1), 1) .* ... + params.maxState, ones(size(inputs.state, 1), 1) .* params.minState); +control = scaleToOriginal(inputs.control, ones(size(inputs.control, 1), 1) .* ... + params.maxControl, ones(size(inputs.control, 1), 1) .* params.minControl); +values.statePositions = getCorrectStates(state, 1, params.numCoordinates); +values.stateVelocities = getCorrectStates(state, 2, params.numCoordinates); +values.stateAccelerations = getCorrectStates(state, 3, params.numCoordinates); +values.controlJerks = control(:, 1 : params.numCoordinates); + +if ~strcmp(params.controlType, 'synergy_driven') + values.controlTorques = control(:, params.numCoordinates + 1 : ... + params.numCoordinates + params.numTorqueControls); +end + +end From f7c4b4f017ba01e6c982ee4d56069aa03581f3a5 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Tue, 23 May 2023 16:57:45 -0500 Subject: [PATCH 24/72] update DO integrand calc --- .../calcDesignOptimizationIntegrand.m | 61 +++++++++++-------- .../getDesignOptimizationValueStruct.m | 2 - .../calcTrackingOptimizationIntegrand.m | 22 ++----- ...teTrackingOptimizationContinuousFunction.m | 1 - .../computeTrackingOptimizationMainFunction.m | 1 - .../calcVerificationOptimizationIntegrand.m | 22 ++----- .../calcTrackingControllerIntegrand.m | 14 ++--- .../calcTrackingCoordinateIntegrand.m | 10 +-- .../costTerms/isMinimizationCostTerm.m | 4 +- .../costTerms/isTrackingCostTerm.m | 6 +- .../getTreatmentOptimizationValueStruct.m | 2 +- 11 files changed, 67 insertions(+), 78 deletions(-) diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index cd4de05ef..b221f2a0b 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -25,35 +25,42 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function integrand = calcDesignOptimizationIntegrand(values, params) +function integrand = calcDesignOptimizationIntegrand(values, auxdata) integrand = []; -for i = 1:length(params.costTerms) - costTerm = params.costTerms{i}; +costTermType.coordinate_tracking = @(values, auxdata, costTerm) ... + calcTrackingCoordinateIntegrand( ... + auxdata, ... + values.time, ... + values.statePositions, ... + costTerm.coordinate ... + ); +costTermType.controller_tracking = @(values, auxdata, costTerm) ... + calcTrackingControllerIntegrand(auxdata, values, ... + costTerm.controller); +costTermType.joint_jerk_minimization = @(values, auxdata, costTerm) ... + calcMinimizingJointJerkIntegrand(values.controlJerks, ... + auxdata, costTerm.coordinate); +costTermType.user_defined = @(values, auxdata, costTerm) ... + userDefinedFunction(values, auxdata, costTerm); +for i = 1:length(auxdata.costTerms) + costTerm = auxdata.costTerms{i}; if costTerm.isEnabled - switch costTerm.type - case "coordinate_tracking" - integrand = cat(2, integrand, ... - calcTrackingCoordinateIntegrand(params, ... - values.time, values.statePositions, ... - costTerm.coordinate)); - case "controller_tracking" - integrand = cat(2, integrand, ... - calcTrackingControllerIntegrand(params, values, ... - costTerm.controller)); - case "joint_jerk_minimization" - integrand = cat(2, integrand, ... - calcMinimizingJointJerkIntegrand(values.controlJerks, ... - params, costTerm.coordinate)); - case "user_defined" - if strcmp(costTerm.cost_term_type, "continuous") - - end - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) + if isfield(costTermType, costTerm.type) + fn = costTermType.(costTerm.type); + integrand = cat(2, integrand, fn(values, auxdata, costTerm)); + else + throw(MException('', ['Cost term type ' costTerm.type ... + ' does not exist for this tool.'])) end end end -integrand = scaleToBounds(integrand, params.maxIntegral, params.minIntegral); +integrand = scaleToBounds(integrand, auxdata.maxIntegral, auxdata.minIntegral); integrand = integrand .^ 2; -end \ No newline at end of file +end + +function output = userDefinedFunction(values, auxdata, costTerm) +output = []; +if strcmp(costTerm.cost_term_type, 'continuous') + output = costTerm.function_name(values, auxdata, costTerm); +end +end diff --git a/src/DesignOptimization/getDesignOptimizationValueStruct.m b/src/DesignOptimization/getDesignOptimizationValueStruct.m index 96ce7b1f6..3f81d6ad8 100644 --- a/src/DesignOptimization/getDesignOptimizationValueStruct.m +++ b/src/DesignOptimization/getDesignOptimizationValueStruct.m @@ -37,8 +37,6 @@ values.synergyWeights = getSynergyWeightsFromGroups(... params.synergyWeightsGuess, params); end - values.controlSynergyActivations = control(:, params.numCoordinates + 1 : ... - params.numCoordinates + params.numSynergies); end for i = 1:length(params.userDefinedVariables) values.(params.userDefinedVariables{i}.type) = inputs.parameter(i, 1); diff --git a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m index 5443eef18..7916c15ab 100644 --- a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m +++ b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m @@ -28,16 +28,16 @@ function integrand = calcTrackingOptimizationIntegrand(values, params, ... phaseout) integrand = []; -for i = 1:length(params.integral.tracking) - costTerm = params.integral.tracking{i}; +for i = 1:length(params.costTerms) + costTerm = params.costTerms{i}; if costTerm.isEnabled switch costTerm.type - case "coordinate" + case "coordinate_tracking" integrand = cat(2, integrand, ... calcTrackingCoordinateIntegrand(params, ... values.time, values.statePositions, ... costTerm.coordinate)); - case "inverse_dynamics_load" + case "inverse_dynamics_load_tracking" integrand = cat(2, integrand, ... calcTrackingInverseDynamicLoadsIntegrand(params, ... values.time, phaseout.inverseDynamicMoments, ... @@ -57,23 +57,13 @@ calcTrackingMuscleActivationIntegrand( ... phaseout.muscleActivations, ... values.time, params, costTerm.muscle)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end -for i = 1:length(params.integral.minimizing) - costTerm = params.integral.minimizing{i}; - if costTerm.isEnabled - switch costTerm.type - case "joint_jerk" + case "joint_jerk_minimization" integrand = cat(2, integrand, ... calcMinimizingJointJerkIntegrand(values.controlJerks, ... params, costTerm.coordinate)); otherwise throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) + ' does not exist for this tool.'])) end end end diff --git a/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m b/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m index 8c641b69e..8a7098684 100644 --- a/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m +++ b/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m @@ -26,7 +26,6 @@ % ----------------------------------------------------------------------- % function phaseout = computeTrackingOptimizationContinuousFunction(inputs) - values = getTrackingOptimizationValueStruct(inputs.phase, inputs.auxdata); phaseout = calcTorqueBasedModeledValues(values, inputs.auxdata); phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); diff --git a/src/TrackingOptimization/computeTrackingOptimizationMainFunction.m b/src/TrackingOptimization/computeTrackingOptimizationMainFunction.m index 031218884..28091e0e7 100644 --- a/src/TrackingOptimization/computeTrackingOptimizationMainFunction.m +++ b/src/TrackingOptimization/computeTrackingOptimizationMainFunction.m @@ -32,7 +32,6 @@ bounds, guess, params, ... @computeTrackingOptimizationContinuousFunction, ... @computeTrackingOptimizationEndpointFunction); - solution = gpops2(setup); solution = solution.result.solution; solution.auxdata = inputs; diff --git a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m index 78499ea88..30adf244b 100644 --- a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m +++ b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m @@ -28,30 +28,20 @@ function integrand = calcVerificationOptimizationIntegrand(values, params, ... phaseout) integrand = []; -for i = 1:length(params.integral.tracking) - costTerm = params.integral.tracking{i}; +for i = 1:length(params.costTerms) + costTerm = params.costTerms{i}; if costTerm.isEnabled switch costTerm.type - case "coordinate" + case "coordinate_tracking" integrand = cat(2, integrand, ... calcTrackingCoordinateIntegrand(params, ... values.time, values.statePositions, ... costTerm.coordinate)); - case "controller" + case "controller_tracking" integrand = cat(2, integrand, ... calcTrackingControllerIntegrand(params, values, ... - costTerm.controller)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end -for i = 1:length(params.integral.minimizing) - costTerm = params.integral.minimizing{i}; - if costTerm.isEnabled - switch costTerm.type - case "joint_jerk" + costTerm.controller)); + case "joint_jerk_minimization" integrand = cat(2, integrand, ... calcMinimizingJointJerkIntegrand(values.controlJerks, ... params, costTerm.coordinate)); diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m index ec694bded..b4ecbf8cd 100644 --- a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m @@ -25,20 +25,20 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function cost = calcTrackingControllerIntegrand(params, values, ... +function cost = calcTrackingControllerIntegrand(auxdata, values, ... controllerName) -switch params.controllerType +switch auxdata.controllerType case 'synergy_driven' indx = find(strcmp(convertCharsToStrings( ... - params.synergyLabels), controllerName)); - synergyActivations = fnval(params.splineSynergyActivations, values.time)'; + auxdata.synergyLabels), controllerName)); + synergyActivations = fnval(auxdata.splineSynergyActivations, values.time)'; cost = calcTrackingCostArrayTerm(synergyActivations, values.controlSynergyActivations, indx); case 'torque_driven' indx1 = find(strcmp(convertCharsToStrings( ... - params.inverseDynamicMomentLabels), controllerName)); + auxdata.inverseDynamicMomentLabels), controllerName)); indx2 = find(strcmp(convertCharsToStrings( ... - strcat(params.controlTorqueNames, '_moment')), controllerName)); - experimentalJointMoments = fnval(params.splineJointMoments, values.time)'; + strcat(auxdata.controlTorqueNames, '_moment')), controllerName)); + experimentalJointMoments = fnval(auxdata.splineJointMoments, values.time)'; cost = experimentalJointMoments(:, indx1) - values.controlTorques(:, indx2); end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingCoordinateIntegrand.m b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingCoordinateIntegrand.m index 649c59fbe..f681ec3a0 100644 --- a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingCoordinateIntegrand.m +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingCoordinateIntegrand.m @@ -25,15 +25,15 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function cost = calcTrackingCoordinateIntegrand(params, time, ... +function cost = calcTrackingCoordinateIntegrand(auxdata, time, ... statePositions, coordinateName) -indx = find(strcmp(convertCharsToStrings(params.coordinateNames), ... +indx = find(strcmp(convertCharsToStrings(auxdata.coordinateNames), ... coordinateName)); -if params.splineJointAngles.dim > 1 - experimentalJointAngles = fnval(params.splineJointAngles, time)'; +if auxdata.splineJointAngles.dim > 1 + experimentalJointAngles = fnval(auxdata.splineJointAngles, time)'; else - experimentalJointAngles = fnval(params.splineJointAngles, time); + experimentalJointAngles = fnval(auxdata.splineJointAngles, time); end cost = calcTrackingCostArrayTerm(experimentalJointAngles, ... diff --git a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m b/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m index 01487e42f..1a6690922 100644 --- a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m +++ b/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m @@ -26,7 +26,9 @@ % ----------------------------------------------------------------------- % function output = isMinimizationCostTerm(costTerm) -minimizationCostTerms = ["joint_jerk_minimization"]; +minimizationCostTerms = [ ... + "joint_jerk_minimization" ... + ]; output = false; for i = 1:length(minimizationCostTerms) if strcmp(costTerm.type, minimizationCostTerms(i)) diff --git a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m index 04117a085..27cee3f9a 100644 --- a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m +++ b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m @@ -26,7 +26,11 @@ % ----------------------------------------------------------------------- % function output = isTrackingCostTerm(costTerm) -trackingCostTerms = ["coordinate_tracking", "controller_tracking"]; +trackingCostTerms = [ ... + "coordinate_tracking", ... + "controller_tracking", ... + "inverse_dynamics_load_tracking" ... + ]; output = false; for i = 1:length(trackingCostTerms) if strcmp(costTerm.type, trackingCostTerms(i)) diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m b/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m index 0460295e5..1a601ed5d 100644 --- a/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m @@ -38,7 +38,7 @@ values.stateAccelerations = getCorrectStates(state, 3, params.numCoordinates); values.controlJerks = control(:, 1 : params.numCoordinates); -if ~strcmp(params.controlType, 'synergy_driven') +if ~strcmp(params.controllerType, 'synergy_driven') values.controlTorques = control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numTorqueControls); end From e0ffc574687084db91f978f4ad7bded3507851c8 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Tue, 23 May 2023 18:21:41 -0500 Subject: [PATCH 25/72] moved cost terms to separate fn --- .../calcDesignOptimizationIntegrand.m | 30 +---- .../generateCostTermStruct.m | 122 ++++++++++++++++++ 2 files changed, 129 insertions(+), 23 deletions(-) create mode 100644 src/core/TreatmentOptimization/generateCostTermStruct.m diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index b221f2a0b..6d191c2a6 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -27,26 +27,15 @@ function integrand = calcDesignOptimizationIntegrand(values, auxdata) integrand = []; -costTermType.coordinate_tracking = @(values, auxdata, costTerm) ... - calcTrackingCoordinateIntegrand( ... - auxdata, ... - values.time, ... - values.statePositions, ... - costTerm.coordinate ... - ); -costTermType.controller_tracking = @(values, auxdata, costTerm) ... - calcTrackingControllerIntegrand(auxdata, values, ... - costTerm.controller); -costTermType.joint_jerk_minimization = @(values, auxdata, costTerm) ... - calcMinimizingJointJerkIntegrand(values.controlJerks, ... - auxdata, costTerm.coordinate); -costTermType.user_defined = @(values, auxdata, costTerm) ... - userDefinedFunction(values, auxdata, costTerm); +[costTermCalculations, allowedTypes] = ... + generateCostTermStruct("continuous", "DesignOptimization"); + for i = 1:length(auxdata.costTerms) costTerm = auxdata.costTerms{i}; if costTerm.isEnabled - if isfield(costTermType, costTerm.type) - fn = costTermType.(costTerm.type); + if isfield(costTermCalculations, costTerm.type) && ... + any(ismember(allowedTypes, costTerm.type)) + fn = costTermCalculations.(costTerm.type); integrand = cat(2, integrand, fn(values, auxdata, costTerm)); else throw(MException('', ['Cost term type ' costTerm.type ... @@ -58,9 +47,4 @@ integrand = integrand .^ 2; end -function output = userDefinedFunction(values, auxdata, costTerm) -output = []; -if strcmp(costTerm.cost_term_type, 'continuous') - output = costTerm.function_name(values, auxdata, costTerm); -end -end + diff --git a/src/core/TreatmentOptimization/generateCostTermStruct.m b/src/core/TreatmentOptimization/generateCostTermStruct.m new file mode 100644 index 000000000..b9f5ba314 --- /dev/null +++ b/src/core/TreatmentOptimization/generateCostTermStruct.m @@ -0,0 +1,122 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% This function returns all of the cost term calculation methods including +% user_defined and existing cost term values. Tools use this function for +% the discrete and continuous cost calculations. +% +% inputs: +% costTermType - one of ["discrete", "continuous"] +% toolName - one of ["TrackingOptimization", "TreatmentOptimization", ... +% "DesignOptimization"] +% +% (string, string) -> (struct of function handles, Array of string) +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega, Claire V. Hammond % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function [costTermCalculations, allowedTypes] = ... + generateCostTermStruct(costTermType, toolName) +allowedTypes = getAllowedTypes(costTermType, toolName); +costTermCalculations = getCostTermCalculations(costTermType); +end + +function allowedTypes = getAllowedTypes(costTermType, toolName) +if strcmp(costTermType, "continuous") + switch toolName + case "TrackingOptimization" + allowedTypes = [ ... + + ]; + case "VerificationOptimization" + allowedTypes = [ ... + + ]; + case "DesignOptimization" + allowedTypes = [ ... + "coordinate_tracking", ... + "controller_tracking", ... + "joint_jerk_minimization", ... + "user_defined", ... + ]; + otherwise + throw(MException('', ['Tool name' toolName 'is not valid'])) + end +elseif strcmp(costTermType, "discrete") + switch toolName + case "TrackingOptimization" + allowedTypes = [ ... + + ]; + case "VerificationOptimization" + allowedTypes = [ ... + + ]; + case "DesignOptimization" + allowedTypes = [ ... + + ]; + otherwise + throw(MException('', ['Tool name' toolName 'is not valid'])) + end +else + throw(MException('', ['Cost term type ' costTermType ... + ' is not valid, must be continuous or discrete'])) +end +end + +function costTermCalculations = getCostTermCalculations(costTermType) + +costTermCalculations.coordinate_tracking = @(values, auxdata, costTerm) ... + calcTrackingCoordinateIntegrand( ... + auxdata, ... + values.time, ... + values.statePositions, ... + costTerm.coordinate ... + ); + +costTermCalculations.controller_tracking = @(values, auxdata, costTerm) ... + calcTrackingControllerIntegrand( ... + auxdata, ... + values, ... + costTerm.controller ... + ); + +costTermCalculations.joint_jerk_minimization = @(values, auxdata, costTerm) ... + calcMinimizingJointJerkIntegrand( ... + values.controlJerks, ... + auxdata, ... + costTerm.coordinate ... + ); + +costTermCalculations.user_defined = @(values, auxdata, costTerm) ... + userDefinedFunction(values, auxdata, costTerm, costTermType); + +end + +function output = ... + userDefinedFunction(values, auxdata, costTerm, costTermType) +output = []; +if strcmp(costTerm.cost_term_type, costTermType) + output = costTerm.function_name(values, auxdata, costTerm); +end +end From 2649f6f11c7a35689228ba712b3e48c6d85a1ac5 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Tue, 23 May 2023 18:41:18 -0500 Subject: [PATCH 26/72] merged metabolic cost in --- .../calcDesignOptimizationIntegrand.m | 7 +++++-- ...omputeDesignOptimizationContinuousFunction.m | 17 +++++++++-------- .../generateCostTermStruct.m | 17 ++++++++++------- 3 files changed, 24 insertions(+), 17 deletions(-) diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index 6d191c2a6..58a2c1010 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -25,7 +25,8 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function integrand = calcDesignOptimizationIntegrand(values, auxdata) +function integrand = calcDesignOptimizationIntegrand(values, ... + modeledValues, auxdata) integrand = []; [costTermCalculations, allowedTypes] = ... generateCostTermStruct("continuous", "DesignOptimization"); @@ -36,7 +37,9 @@ if isfield(costTermCalculations, costTerm.type) && ... any(ismember(allowedTypes, costTerm.type)) fn = costTermCalculations.(costTerm.type); - integrand = cat(2, integrand, fn(values, auxdata, costTerm)); + integrand = cat(2, ... + integrand, ... + fn(values, modeledValues, auxdata, costTerm)); else throw(MException('', ['Cost term type ' costTerm.type ... ' does not exist for this tool.'])) diff --git a/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m b/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m index 2ccdd83da..301a6643d 100644 --- a/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationContinuousFunction.m @@ -25,19 +25,20 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function phaseout = computeDesignOptimizationContinuousFunction(inputs) +function modeledValues = computeDesignOptimizationContinuousFunction(inputs) values = getDesignOptimizationValueStruct(inputs.phase, inputs.auxdata); inputs = updateSystemFromUserDefinedFunctions(inputs, values); -phaseout = calcTorqueBasedModeledValues(values, inputs.auxdata); -phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); -phaseout.dynamics = calcDesignOptimizationDynamicsConstraint(values, ... +modeledValues = calcTorqueBasedModeledValues(values, inputs.auxdata); +modeledValues = calcSynergyBasedModeledValues(values, inputs.auxdata, ... + modeledValues); +modeledValues.dynamics = calcDesignOptimizationDynamicsConstraint(values, ... inputs.auxdata); if ~isempty(inputs.auxdata.path) - phaseout.path = calcDesignOptimizationPathConstraint(values, ... - phaseout, inputs.auxdata); + modeledValues.path = calcDesignOptimizationPathConstraint(values, ... + modeledValues, inputs.auxdata); end -phaseout.integrand = calcDesignOptimizationIntegrand(values, ... - inputs.auxdata); +modeledValues.integrand = calcDesignOptimizationIntegrand(values, ... + modeledValues, inputs.auxdata); end diff --git a/src/core/TreatmentOptimization/generateCostTermStruct.m b/src/core/TreatmentOptimization/generateCostTermStruct.m index b9f5ba314..2d3185395 100644 --- a/src/core/TreatmentOptimization/generateCostTermStruct.m +++ b/src/core/TreatmentOptimization/generateCostTermStruct.m @@ -86,7 +86,7 @@ function costTermCalculations = getCostTermCalculations(costTermType) -costTermCalculations.coordinate_tracking = @(values, auxdata, costTerm) ... +costTermCalculations.coordinate_tracking = @(values, modeledValues, auxdata, costTerm) ... calcTrackingCoordinateIntegrand( ... auxdata, ... values.time, ... @@ -94,29 +94,32 @@ costTerm.coordinate ... ); -costTermCalculations.controller_tracking = @(values, auxdata, costTerm) ... +costTermCalculations.controller_tracking = @(values, modeledValues, auxdata, costTerm) ... calcTrackingControllerIntegrand( ... auxdata, ... values, ... costTerm.controller ... ); -costTermCalculations.joint_jerk_minimization = @(values, auxdata, costTerm) ... +costTermCalculations.joint_jerk_minimization = @(values, modeledValues, auxdata, costTerm) ... calcMinimizingJointJerkIntegrand( ... values.controlJerks, ... auxdata, ... costTerm.coordinate ... ); -costTermCalculations.user_defined = @(values, auxdata, costTerm) ... - userDefinedFunction(values, auxdata, costTerm, costTermType); +costTermCalculations.metabolic_cost = @(values, modeledValues, auxdata, costTerm) ... + calcMinimizingMetabolicCost(modeledValues.metabolicCost); + +costTermCalculations.user_defined = @(values, modeledValues, auxdata, costTerm) ... + userDefinedFunction(values, modeledValues, auxdata, costTerm, costTermType); end function output = ... - userDefinedFunction(values, auxdata, costTerm, costTermType) + userDefinedFunction(values, modeledValues, auxdata, costTerm, costTermType) output = []; if strcmp(costTerm.cost_term_type, costTermType) - output = costTerm.function_name(values, auxdata, costTerm); + output = costTerm.function_name(values, modeledValues, auxdata, costTerm); end end From c6c678f6fff24d4bca254630f498341282626d11 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Tue, 23 May 2023 20:18:14 -0500 Subject: [PATCH 27/72] Fix saving contact surface to osimx when a surface already exists in file --- src/core/osimx/buildGcpContactSurface.m | 10 +++++++--- src/core/osimx/parseOsimxFile.m | 9 ++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/core/osimx/buildGcpContactSurface.m b/src/core/osimx/buildGcpContactSurface.m index 834225d83..5a4bf4c01 100644 --- a/src/core/osimx/buildGcpContactSurface.m +++ b/src/core/osimx/buildGcpContactSurface.m @@ -37,6 +37,10 @@ groundContact.RCNLContactSurface = {}; else i = length(groundContact.RCNLContactSurface) + 1; + if length(groundContact.RCNLContactSurface) == 1 + groundContact.RCNLContactSurface = ... + {groundContact.RCNLContactSurface}; + end end contact = groundContact.RCNLContactSurface; @@ -103,12 +107,12 @@ newContactSurface = contact{i}; newContactSurface.GCPSpringSet.Comment = ... 'The set of springs for the contact surface'; -for i = 1:length(contactSurface.springs) +for j = 1:length(contactSurface.springs) newContactSurface = buildGcpSpring(newContactSurface, ... - contactSurface.springs{i}); + contactSurface.springs{j}); end newContactSurface.GCPSpringSet.groups = ''; osimx.NMSMPipelineDocument.OsimxModel.RCNLContactSurfaceSet.objects. ... - RCNLContactSurface = newContactSurface; + RCNLContactSurface{i} = newContactSurface; end diff --git a/src/core/osimx/parseOsimxFile.m b/src/core/osimx/parseOsimxFile.m index 4ded75e03..a5c9cb598 100644 --- a/src/core/osimx/parseOsimxFile.m +++ b/src/core/osimx/parseOsimxFile.m @@ -43,13 +43,12 @@ if(isstruct(rcnlGroundContactTree)) contactSurfaceTree = getFieldByNameOrError(rcnlGroundContactTree, "objects").RCNLContactSurface; + if isstruct(contactSurfaceTree) + contactSurfaceTree = {contactSurfaceTree}; + end for i = 1:length(contactSurfaceTree) - if length(contactSurfaceTree) == 1 - contactSurface = contactSurfaceTree; - else - contactSurface = contactSurfaceTree{i}; - end + contactSurface = contactSurfaceTree{i}; osimx.groundContact.contactSurface{i} = parseContactSurface(contactSurface); end end From 17e35c6c30aa08588450500b1dbf5834b8f13d64 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Wed, 24 May 2023 11:44:56 -0500 Subject: [PATCH 28/72] fix small no-arg issues --- .../computeDesignOptimizationEndpointFunction.m | 4 +++- src/DesignOptimization/parseDesignOptimizationSettingsTree.m | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index 774ce63e1..58fecf64d 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -32,7 +32,9 @@ inputs.phase.control = ones(size(inputs.phase.time,1), ... length(inputs.auxdata.minControl)); phase = inputs.phase; -phase.parameter = inputs.parameter; +if isfield(inputs, "parameter") + phase.parameter = inputs.parameter; +end values = getDesignOptimizationValueStruct(phase, inputs.auxdata); inputs = updateSystemFromUserDefinedFunctions(inputs, values); modeledValues = calcTorqueBasedModeledValues(values, inputs.auxdata); diff --git a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m index ff924ce64..e8b10c705 100644 --- a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m +++ b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m @@ -46,7 +46,7 @@ inputs.userDefinedVariables = parseRcnlCostTermSet( ... parameterTree.RCNLParameterTerm); else - inputs.userDefinedVariables = struct(); + inputs.userDefinedVariables = {}; end end From a0317c5c6e6a774fc65dc09c2c980809685e59dd Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Wed, 24 May 2023 15:11:37 -0500 Subject: [PATCH 29/72] fixed formatting --- .../computeDesignOptimizationMainFunction.m | 1 - .../calcSynergyBasedModeledValues.m | 34 +++++++++---------- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index c7f8eafb2..fd3ae40e6 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -34,7 +34,6 @@ @computeDesignOptimizationContinuousFunction, ... @computeDesignOptimizationEndpointFunction); solution = gpops2(setup); -solution.result solution = solution.result.solution; solution.auxdata = inputs; solution.phase.parameter = [solution.parameter]; diff --git a/src/TrackingOptimization/calcSynergyBasedModeledValues.m b/src/TrackingOptimization/calcSynergyBasedModeledValues.m index 7ff654d19..d275dc0ad 100644 --- a/src/TrackingOptimization/calcSynergyBasedModeledValues.m +++ b/src/TrackingOptimization/calcSynergyBasedModeledValues.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -26,22 +26,22 @@ % ----------------------------------------------------------------------- % function phaseout = calcSynergyBasedModeledValues(values, params, phaseout) -if strcmp(params.controllerType, 'synergy_driven') -[jointAngles, jointVelocities] = getMuscleActuatedDOFs(values, params); -[params.muscleTendonLength, params.momentArms, ... - params.muscleTendonVelocity] = calcSurrogateModel(params, ... - jointAngles, jointVelocities); -[phaseout.normalizedFiberLength, phaseout.normalizedFiberVelocity] = ... - calcNormalizedMuscleFiberLengthsAndVelocities(params, ... - ones(1, params.numMuscles), ones(1, params.numMuscles)); -phaseout.muscleActivations = calcMuscleActivationFromSynergies(values); -phaseout.metabolicCost = calcMetabolicCost(values.time, ... - values.statePositions, phaseout.muscleActivations, params); -muscleJointMoments = calcMuscleJointMoments(params, ... - phaseout.muscleActivations, phaseout.normalizedFiberLength, ... - phaseout.normalizedFiberVelocity); -phaseout.muscleJointMoments = muscleJointMoments(:, params.surrogateModelIndex); -phaseout.muscleJointMoments = phaseout.muscleJointMoments(:, params.dofsActuatedIndex); +if strcmp(params.controllerType, 'synergy_driven') + [jointAngles, jointVelocities] = getMuscleActuatedDOFs(values, params); + [params.muscleTendonLength, params.momentArms, ... + params.muscleTendonVelocity] = calcSurrogateModel(params, ... + jointAngles, jointVelocities); + [phaseout.normalizedFiberLength, phaseout.normalizedFiberVelocity] = ... + calcNormalizedMuscleFiberLengthsAndVelocities(params, ... + ones(1, params.numMuscles), ones(1, params.numMuscles)); + phaseout.muscleActivations = calcMuscleActivationFromSynergies(values); + phaseout.metabolicCost = calcMetabolicCost(values.time, ... + values.statePositions, phaseout.muscleActivations, params); + muscleJointMoments = calcMuscleJointMoments(params, ... + phaseout.muscleActivations, phaseout.normalizedFiberLength, ... + phaseout.normalizedFiberVelocity); + phaseout.muscleJointMoments = muscleJointMoments(:, params.surrogateModelIndex); + phaseout.muscleJointMoments = phaseout.muscleJointMoments(:, params.dofsActuatedIndex); end end From 2758807a64d0b99b6ad725c194d7ed76a07e233a Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Wed, 24 May 2023 15:24:25 -0500 Subject: [PATCH 30/72] Delete reportDesignOptimizationResults.m --- .../reportDesignOptimizationResults.m | 121 ------------------ 1 file changed, 121 deletions(-) delete mode 100644 src/DesignOptimization/reportDesignOptimizationResults.m diff --git a/src/DesignOptimization/reportDesignOptimizationResults.m b/src/DesignOptimization/reportDesignOptimizationResults.m deleted file mode 100644 index c2dc968ff..000000000 --- a/src/DesignOptimization/reportDesignOptimizationResults.m +++ /dev/null @@ -1,121 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function reportDesignOptimizationResults(solution, inputs) -values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); -for i = 1:length(inputs.userDefinedVariables) - disp(inputs.userDefinedVariables{i}.type) - disp(values.(inputs.userDefinedVariables{i}.type)) -end -if strcmp(inputs.controllerType, 'synergy_driven') -% plot Muscle Activations -plotMuscleActivations(solution.muscleActivations, values.time, ... - inputs.experimentalMuscleActivations, inputs.experimentalTime, ... - inputs.muscleLabels); -% plot synergy activations -synergyTitles = {}; -for i = 1 : inputs.numSynergies - synergyTitles{end + 1} = strcat('synergy activation', num2str(i)); -end -plotResultsWithOutComparison(values.controlSynergyActivations, values.time, ... - synergyTitles, ["Synergy" "Activations"]); -end -% plot joint angles -plotResultsWithComparison(values.statePositions, values.time, ... - inputs.experimentalJointAngles, inputs.experimentalTime, ... - strcat(inputs.coordinateNames, ' [rad]'), ["Joint" "Angles [rad]"]); -% plot joint moments -plotResultsWithComparison(solution.inverseDynamicMoments, values.time, ... - inputs.experimentalJointMoments, inputs.experimentalTime, ... - strcat(inputs.inverseDynamicMomentLabels, ' [Nm]'), ["Joint" "Moments"]); -% plot ground reactions -for i = 1:length(inputs.contactSurfaces) -plotResultsWithComparison(solution.groundReactionsLab.forces{i}, values.time, ... - inputs.contactSurfaces{i}.experimentalGroundReactionForces, inputs.experimentalTime, ... - ["GRFx", "GRFy", "GRFz"], ["Ground" "Reaction Forces"]); -plotResultsWithComparison(solution.groundReactionsLab.moments{i}, values.time, ... - inputs.contactSurfaces{i}.experimentalGroundReactionMoments, inputs.experimentalTime, ... - ["GRTx", "GRTy", "GRTz"], ["Ground" "Reaction Moments"]); -end -end -function plotMuscleActivations(muscleActivations, time, ... - experimentalMuscleActivations, experimentalTime, muscleLabels) - -figure('name', 'Muscle Activations') -nplots = ceil(sqrt(size(muscleActivations, 2))); -for i = 1 : size(muscleActivations,2) -subplot(nplots, nplots, i) -plot(time, muscleActivations(:, i)); hold on -plot(experimentalTime, experimentalMuscleActivations(:, i), 'r') -axis([0 1 0 experimentalTime(end)]) -title(muscleLabels(i)) -figureXLabels(numel(muscleLabels), nplots, i, "Time") -figureYLabels(numel(muscleLabels), nplots, i, ["Muscle" "Activation"]) -end -end -function plotResultsWithComparison(solutionData, solutionTime, ... - experimentalData, experimentalTime, labels, figureTitle) - -figure('name', strjoin(figureTitle)) -nplots = ceil(sqrt(numel(labels))); -for i = 1 : size(solutionData,2) -subplot(nplots, nplots, i) -plot(solutionTime, solutionData(:, i)); hold on -plot(experimentalTime, experimentalData(:, i), 'r') -xlim([0 experimentalTime(end)]) -title(labels(i)) -figureXLabels(numel(labels), nplots, i, "Time") -end -end -function plotResultsWithOutComparison(solutionData, solutionTime, ... - labels, figureTitle) - -figure('name', strjoin(figureTitle)) -for i = 1 : size(solutionData,2) -subplot(numel(labels), 1, i) -plot(solutionTime, solutionData(:, i)); hold on -xlim([0 solutionTime(end)]) -title(labels(i)) -figureXLabels(numel(labels), 1, i, "Time") -end -end -function figureXLabels(numTotalPlots, numColumnPlots, plotIndex, xLabel) - -if plotIndex > numTotalPlots - numColumnPlots - xlabel(xLabel); -else - xticklabels(''); -end -end -function figureYLabels(numTotalPlots, numColumnPlots, plotIndex, yLabel) - -if ismember(plotIndex, 1 : numColumnPlots : numTotalPlots) - ylabel({yLabel(1); yLabel(2)}); -else - yticklabels(''); -end -end \ No newline at end of file From eaee26c794a8394ba543ec3e3a4f5e456d127821 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 24 May 2023 15:28:16 -0500 Subject: [PATCH 31/72] Point kinematics mex alternative --- .../70qGohwUKdJepml9XrUSQBg8iXAd.xml | 6 -- .../70qGohwUKdJepml9XrUSQBg8iXAp.xml | 2 - .../jwQJUlUQLZW-X5QWOUQ_Ed6LSTUd.xml | 6 -- .../jwQJUlUQLZW-X5QWOUQ_Ed6LSTUp.xml | 2 - .../TrackingOptimization.m | 5 +- .../TrackingOptimizationTool.m | 6 ++ .../calcTorqueBasedModeledValues.m | 12 ++-- .../getTrackingOptimizationValueStruct.m | 2 + src/TrackingOptimization/inverseDynamics.m | 65 ++++++++++++++++++ .../inverseDynamicsMatlab.m | 65 ++++++++++++++++++ src/TrackingOptimization/pointKinematics.m | 64 +++++++++++++++++ .../pointKinematics.mexw64 | Bin 31744 -> 0 bytes .../inverseDynamics.mexw64 | Bin 40960 -> 0 bytes .../pointKinematics.mexw64 | Bin 31744 -> 0 bytes 14 files changed, 211 insertions(+), 24 deletions(-) delete mode 100644 resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAd.xml delete mode 100644 resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAp.xml delete mode 100644 resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUd.xml delete mode 100644 resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUp.xml create mode 100644 src/TrackingOptimization/inverseDynamics.m create mode 100644 src/TrackingOptimization/inverseDynamicsMatlab.m create mode 100644 src/TrackingOptimization/pointKinematics.m delete mode 100644 src/TrackingOptimization/pointKinematics.mexw64 delete mode 100644 src/VerificationOptimization/inverseDynamics.mexw64 delete mode 100644 src/VerificationOptimization/pointKinematics.mexw64 diff --git a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAd.xml b/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAd.xml deleted file mode 100644 index 7de97d4a9..000000000 --- a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAd.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAp.xml b/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAp.xml deleted file mode 100644 index 13873994b..000000000 --- a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/70qGohwUKdJepml9XrUSQBg8iXAp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUd.xml b/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUd.xml deleted file mode 100644 index 7de97d4a9..000000000 --- a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUd.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - \ No newline at end of file diff --git a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUp.xml b/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUp.xml deleted file mode 100644 index bd8c96c4a..000000000 --- a/resources/project/D7EGYpvIiO_nGXQuTOYjsSP_JQw/jwQJUlUQLZW-X5QWOUQ_Ed6LSTUp.xml +++ /dev/null @@ -1,2 +0,0 @@ - - \ No newline at end of file diff --git a/src/TrackingOptimization/TrackingOptimization.m b/src/TrackingOptimization/TrackingOptimization.m index 44aa793a1..024ed1b22 100644 --- a/src/TrackingOptimization/TrackingOptimization.m +++ b/src/TrackingOptimization/TrackingOptimization.m @@ -26,9 +26,10 @@ % ----------------------------------------------------------------------- % function [output, inputs] = TrackingOptimization(inputs, params) -pointKinematics(inputs.mexModel); +% pointKinematics(inputs.mexModel); inverseDynamics(inputs.mexModel); -inputs +% inputs +gcp; inputs = getStateDerivatives(inputs); inputs = setupGroundContact(inputs); inputs = getSplines(inputs); diff --git a/src/TrackingOptimization/TrackingOptimizationTool.m b/src/TrackingOptimization/TrackingOptimizationTool.m index 3f33a5a46..df6c96ba4 100644 --- a/src/TrackingOptimization/TrackingOptimizationTool.m +++ b/src/TrackingOptimization/TrackingOptimizationTool.m @@ -32,7 +32,13 @@ function TrackingOptimizationTool(settingsFileName) settingsTree = xml2struct(settingsFileName); [inputs, params] = parseTrackingOptimizationSettingsTree(settingsTree); + +tic + [outputs, inputs] = TrackingOptimization(inputs, params); + +toc + reportTrackingOptimizationResults(outputs, inputs); saveTrackingOptimizationResults(outputs, inputs); end \ No newline at end of file diff --git a/src/TrackingOptimization/calcTorqueBasedModeledValues.m b/src/TrackingOptimization/calcTorqueBasedModeledValues.m index a4257aace..95ea3da40 100644 --- a/src/TrackingOptimization/calcTorqueBasedModeledValues.m +++ b/src/TrackingOptimization/calcTorqueBasedModeledValues.m @@ -41,7 +41,7 @@ end phaseout.inverseDynamicMoments = inverseDynamics(values.time, ... values.statePositions, values.stateVelocities, ... - values.stateAccelerations, params.coordinateNames, appliedLoads); + values.stateAccelerations, params.coordinateNames, appliedLoads);%, params.mexModel); end function [springPositions, springVelocities] = getSpringLocations(time, .... statePositions, stateVelocities, params) @@ -52,13 +52,13 @@ params.contactSurfaces{i}.parentSpringPointsOnBody', ... params.contactSurfaces{i}.parentBody * ones(1, ... size(params.contactSurfaces{i}.parentSpringPointsOnBody, 1)), ... - params.coordinateNames); + params.mexModel, params.coordinateNames); [springPositions.child{i}, springVelocities.child{i}] = ... pointKinematics(time, statePositions, stateVelocities, ... params.contactSurfaces{i}.childSpringPointsOnBody', ... params.contactSurfaces{i}.childBody * ones(1, ... size(params.contactSurfaces{i}.childSpringPointsOnBody, 1)), ... - params.coordinateNames); + params.mexModel, params.coordinateNames); end end function bodyLocations = getBodyLocations(time, statePositions, ... @@ -67,14 +67,14 @@ for i = 1:length(params.contactSurfaces) bodyLocations.midfootSuperior{i} = pointKinematics(time, statePositions, ... stateVelocities, params.contactSurfaces{i}.midfootSuperiorPointOnBody', ... - params.contactSurfaces{i}.midfootSuperiorBody, params.coordinateNames); + params.contactSurfaces{i}.midfootSuperiorBody, params.mexModel, params.coordinateNames); bodyLocations.midfootSuperior{i}(:, 2) = 0; bodyLocations.parent{i} = pointKinematics(time, statePositions, ... stateVelocities, [0 0 0]', params.contactSurfaces{i}.parentBody, ... - params.coordinateNames); + params.mexModel, params.coordinateNames); bodyLocations.child{i} = pointKinematics(time, statePositions, ... stateVelocities, [0 0 0]', params.contactSurfaces{i}.childBody, ... - params.coordinateNames); + params.mexModel, params.coordinateNames); end end function groundReactionsBody = tranferGroundReactionMoments( ... diff --git a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m index f5aa1e386..2302cba25 100644 --- a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m +++ b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m @@ -27,6 +27,8 @@ function values = getTrackingOptimizationValueStruct(inputs, params) +% values.mexModel = + values.time = scaleToOriginal(inputs.time, params.maxTime, ... params.minTime); state = scaleToOriginal(inputs.state, ones(size(inputs.state, 1), 1) .* ... diff --git a/src/TrackingOptimization/inverseDynamics.m b/src/TrackingOptimization/inverseDynamics.m new file mode 100644 index 000000000..30336a26f --- /dev/null +++ b/src/TrackingOptimization/inverseDynamics.m @@ -0,0 +1,65 @@ +function IDLoads = inverseDynamics(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) + % Load Library + import org.opensim.modeling.*; + + % Open a Model by name + osimModel = Model(modelFile); + + % Initialize the system and get the initial state + osimState = osimModel.initSystem(); + idSolver = InverseDynamicsSolver(osimModel); + + % Get the number of coords and markers + numPts = size(time,1); + numControls = size(AppliedLoads,2); + numCoords = osimState.getNQ; + + AccelsTempVec = zeros(numPts,numCoords); + for j=1:numPts + osimState.setTime(time(j,1)); + + for k=1:size(IKLabels,2) + if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked + osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); + osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); + end + end + osimModel.realizeVelocity(osimState); + + for i=1:osimState.getNQ + StateQ = osimState.getQ.get(i-1); + + for ii = 1:size(q,2) + if abs(q(j,ii)-StateQ) <= 1e-6 + AccelsTempVec(j,i) = qpp(j,ii); + end + end + end + + tempMarkerGlobalPos=Vec3; + + newControls = Vector(numControls,0); + + for i=0:numControls-1 + newControls.set(i,AppliedLoads(j,i+1)); + end + + osimModel.setControls(osimState,newControls); + osimModel.markControlsAsValid(osimState); + osimModel.realizeDynamics(osimState); + + AccelsVec = Vector(osimState.getNQ,0); + + for i=0:osimState.getNQ-1 + AccelsVec.set(i,AccelsTempVec(j,i+1)); + end + + IDLoadsVec = Vector; + IDLoadsVec = idSolver.solve(osimState,AccelsVec); + + for i=0:numCoords-1 + IDLoads(j,i+1) = IDLoadsVec.get(i); + end + + end +end \ No newline at end of file diff --git a/src/TrackingOptimization/inverseDynamicsMatlab.m b/src/TrackingOptimization/inverseDynamicsMatlab.m new file mode 100644 index 000000000..30336a26f --- /dev/null +++ b/src/TrackingOptimization/inverseDynamicsMatlab.m @@ -0,0 +1,65 @@ +function IDLoads = inverseDynamics(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) + % Load Library + import org.opensim.modeling.*; + + % Open a Model by name + osimModel = Model(modelFile); + + % Initialize the system and get the initial state + osimState = osimModel.initSystem(); + idSolver = InverseDynamicsSolver(osimModel); + + % Get the number of coords and markers + numPts = size(time,1); + numControls = size(AppliedLoads,2); + numCoords = osimState.getNQ; + + AccelsTempVec = zeros(numPts,numCoords); + for j=1:numPts + osimState.setTime(time(j,1)); + + for k=1:size(IKLabels,2) + if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked + osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); + osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); + end + end + osimModel.realizeVelocity(osimState); + + for i=1:osimState.getNQ + StateQ = osimState.getQ.get(i-1); + + for ii = 1:size(q,2) + if abs(q(j,ii)-StateQ) <= 1e-6 + AccelsTempVec(j,i) = qpp(j,ii); + end + end + end + + tempMarkerGlobalPos=Vec3; + + newControls = Vector(numControls,0); + + for i=0:numControls-1 + newControls.set(i,AppliedLoads(j,i+1)); + end + + osimModel.setControls(osimState,newControls); + osimModel.markControlsAsValid(osimState); + osimModel.realizeDynamics(osimState); + + AccelsVec = Vector(osimState.getNQ,0); + + for i=0:osimState.getNQ-1 + AccelsVec.set(i,AccelsTempVec(j,i+1)); + end + + IDLoadsVec = Vector; + IDLoadsVec = idSolver.solve(osimState,AccelsVec); + + for i=0:numCoords-1 + IDLoads(j,i+1) = IDLoadsVec.get(i); + end + + end +end \ No newline at end of file diff --git a/src/TrackingOptimization/pointKinematics.m b/src/TrackingOptimization/pointKinematics.m new file mode 100644 index 000000000..2eebd2a7d --- /dev/null +++ b/src/TrackingOptimization/pointKinematics.m @@ -0,0 +1,64 @@ +function [SpringPos, SpringVel] = pointKinematics(time,q,qp,SpringMat,SpringBodyMat,... + modelFile,IKLabels) + +% Load Library +import org.opensim.modeling.*; + +% Open a Model by name +osimModel = Model(modelFile); + +% Initialize the system and get the initial state +osimState = osimModel.initSystem(); + +% Get the number of coords and markers +numPts = size(time,1); +numSprings = size(SpringMat,1); + +refBodySet = osimModel.getBodySet; + +% Split time points into parallel problems +numWorkers = gcp().NumWorkers; +SpringPosJobs = cell(1, numWorkers); +SpringVelJobs = cell(1, numWorkers); + +parfor worker = 1:numWorkers + + for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) + osimState.setTime(time(j,1)); + for k=1:size(IKLabels,2) + if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked + osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); + osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); + end + end + osimModel.realizeVelocity(osimState); + + for i=1:numSprings + tempRefParentBody = refBodySet.get(SpringBodyMat(i)); + tempLocalPos = Vec3(3,0); + tempGlobalPos = Vec3(3,0); + tempGlobalVel = Vec3(3,0); + tempLocalPos.set(0,SpringMat(i,1)); + tempLocalPos.set(1,SpringMat(i,2)); + tempLocalPos.set(2,SpringMat(i,3)); + + osimModel.getSimbodyEngine.getPosition(osimState,tempRefParentBody,tempLocalPos,tempGlobalPos); + osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); + + for k=0:2 + SpringPosJobs{worker}(j,(i-1)*3+k+1)=tempGlobalPos.get(k); + SpringVelJobs{worker}(j,(i-1)*3+k+1)=tempGlobalVel.get(k); + end + end + end + +end + +SpringPos = SpringPosJobs{1}; +SpringVel = SpringVelJobs{1}; +for job = 2 : numWorkers + SpringPos = cat(1, SpringPos, SpringPosJobs{job}); + SpringVel = cat(1, SpringVel, SpringVelJobs{job}); +end + +end \ No newline at end of file diff --git a/src/TrackingOptimization/pointKinematics.mexw64 b/src/TrackingOptimization/pointKinematics.mexw64 deleted file mode 100644 index b8ca6b73ff4ebabcec6ebb4571420156aaf6c7dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31744 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 diff --git a/src/VerificationOptimization/pointKinematics.mexw64 b/src/VerificationOptimization/pointKinematics.mexw64 deleted file mode 100644 index b8ca6b73ff4ebabcec6ebb4571420156aaf6c7dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31744 zcmeHw4SbZvwfAh24ND;If(Zt+vS7GI!$%;XLBQ;iEItbxSRr6ka1%Bg5=l1O{ea-d zHMn52Jg!YGy~X})-&Dm0>g}7h)<(Q-1FIJ)nJ3wV zAhx&n{r%p)efT}knK^Uj%$YN1&U|htxucmSGsaTz#^a2&1JYyR-%tLSbc|g#{m^CX z+3_#UXxBMjno(L^6EOSy^;`TEb!K;k*IOSnZ}yn|A+Nc{Yc5{1&Rkbt>6x3Jo|Y|H zZ!LXc#k@tYXKL@il&r|iMLc8c8yT}X?8#Wl;mM3_4qwZ-4)E;pt25^Se!2Ly45I&o zktOp1v_v`mFmc+_IDl%!{!nnY++{suIvgT}L zEQ<OEjAaAgvy!o^G~5_)0O@-Z@fK8t8vP>A+@Pl+h%~EK&pbp% z(VH17pX;xz2v#um&W%X7;4Q$r5O2a`LCRb~#s(%Lp%E`Bsupj;V_~dgu1^s1I-;(; zOn(9qf}pd^-BMLk8DMNSCAHv%?%6iH36F*7`~klkIIcVBkkd6J5Fu#cbe`IJ5Rxug z2VUZL58i~w!r1bJ=s!eI3BQvQ&;0EI^#072eEL4h!;j;ugkQ{2;HV zQ@VsZ zN!dwOqpe%*R8NMF8?p_?;*(0(E!9M*9tn4w8h@Q)EItys#iyuUiVEfsBG1*S6@Oxk z@+~M`QFk_wIBsVOB>3N4f?a7GHJnl$eULE4kzHv)B97`)Iia;t2_J5ZokX{S{A(Ja z4_G|~n@9u;7XgZB1=g}|Wc}4MifYYL!hUd=t^=<#sbBCt~$HgOn%Lv5)nn*!P_}(m&KUq<) zJeo|6lXqw52`-OWi9SZoLQ#Jbdkf71h-CAfkU0Y~D{kvYN^W-_=Ya+@D3M^ci5k+Z zexRslV&4#XQ17{@;khndaMGa|@r$aYu-Ga{qo~2`0wo$2I^CF^M4%!r1c!eI2Wc%*s3B`f%&$a0^CLJCCAzE(enU|wD7%jZ(-c*Ah`M~-q=Y*S%93B) z_Zuae{?HAK9o0Voau8~D?fc;sj5#93*;z`YG~1*^9NC6-8*a01fFcWqdPIpj0F^3G zZ1@$U5{`eoO^-bq76 z|F&0&tQhRih}}Y|kP8GbvPskk{TWoTatB%!UG2W?YHHtxlHKQm(}kHq>dX6KYp)V+ zLWz1*QD2Vb5ILc@z|pd?j*kmLn2CYstRw^W{fV@~JlzSnqP(g&r~T5&h$w)u%(N1n+@h7DY8s z(@#W2$`rRrNj}+s8NyBqt3?G$GJ!0j$~~#<{9r_HejVdYV(os+UT8^^&RA^j&2hwvoH31hX z(UmZYhak1LT;7^eV?29S?JZiq$6+{y3P0b0P_6x;9}1`sw^2)jJLoJejZwEkERqT zi>)S3)-1*uB2yw(H02KA*2W)A(XsqfAL9qD%Qy;%XXLoFgTky0%m3QdKCeu_yy%Sk_f8m{nEbT{Z{xRz99 z?&*cXMimZSaP>`q>J;^jm@)fPYLByosgXU~cMi%k%3@$d(H$Se}XJ&JmqOuFyOlSkO_@c5yqJD`X&%90Pb5l)y-lbv)pH1uaU-6JsK z?F*F1tJ+N4Z&DP{NW?#SZsL%ewWXGS%u+e~;RN-Dv(7b2i=C8UC+$ zXuNO?h_xDmxenECv8$8F`lh49RNI&I1g5|V!tohOB044cINVX0HMg6)B8zm5d5XHO z%;8R28BNhCOS%JQYt#~tBq{1DP)t&y%XLabr~cT|biFK?e1jqes(Ak-VyU=cL-3-x z@TZuay+Y$&_V5-x;AzI`tR3@<2QcI*OS%Gj5QY9oY>ZSVbTFFzQN_ps zSsandY^MXEG6#w?JEBV$+hN$zRl(SAKcGp^sw@n04s|~D=gVP`cJ&P!8}FR~t;5P! zLm)WYK(!J<1&iQt36vE_xTNQbd?^A`5S*-ZK}h7ns1V8PdyImd*xAKsve5TAp18`a zs7J}s>54FXf}x5gCxkus4Mhj#qWB_<`f~1ZYw{~f1c^m(?^IB58p4=HkN}uEfSXoW z|2)d$AWlUE4vaydE6IJ?k^HhP_oOv`MCNgb0i-x$0y`KN^g-Uq5;Rgrgi28NSPOYZ zlo?({B&0VL8jOP{wIgYF%*GrxHtL_M6=E>5@?{8TIhL?(3&S74VOno;#)GLCS(8H7 zAjKJHM61LNJe7x4gL8+1M@&7{9jN0^9qLI%9f!dO^RUI%>Hr$R zTFtu_O;9n*GwwRXHS<|C(Pc0USXhMLPYPiiT8??u%4#Lo;7C5)KOIOys|)X zA3;NcnQ_MoRm@RFx`MmV1RJsYOWIpMfg&8>zK0v(N+QYjt|9|uWA;c9MDI*kT%mm|>q=B7Eaayp$tH(-20jBdruji)6M?k@tWjad zl9R?={{d#?0Zi6&hjl@(UA-4WMj;DUmh3)dZ2C7UN?iwKqnG3+IhOPoKmQg3!j zfP=D6k+N4wWrIgju(|}Dz%J%#8$l$rjIJWJY#=S8Gnx8t=Jbo~;Q|(%hw>V(A6eD| z)6~gCYRxri1!=m1IiSrTc7BH=aye$7Tq_E+0-vu`=o-N9|z0h#ukpH3*+1?eUJ~1xs1zyrT9f(UOkXb?4*pW*G0SW|AREaVsfsB$E_J zbkPrg5s%xW4>c=DiP(BUULDW;0_d8%YZbh zVx%~|SHnfWKnu)BY5LC&p~_gul*D39e-Z-pOdk?}1}-p-XU^tAowxod1JsV_oY$^p z?0H%l#@13z)zgrxnap=G=sPeR#4{r^X&IMG^RHj5j68lr+ioKKNj$Rw2|TbJft8V6 zH#pIUmGDu6J^UwK=zv4juZ9HF9U#0tx!bP37SH@9P)GHpm5k}A`msA9tkA<_NTIr( z=DP#8^3_sF$EfAfF36v_pa}B#@PeX}$K`_JHbLAS`_6cccmZ3gem18AMgQtXBzl8I+yz7RK0pXlWE4v30e1r=j`8?`l2pqge7<&H>gb{U#wAEBHGG*dtUQ7;87Z`lo zGCtC-M+>4)6;s`Lm5ZSOs&oD^>+_J0+0?_a6x0o#upBW#y9gSYB{?0hP5h!MNV<}Qh4WQ)2ic(3SK|+)(G!1(tb_1cJ zSJ|jUC2}?2n?aw9rE<~%BBlA8gJ$KJUcC~#f)cuH+@a-TKT>mPH;44aBVAf5xwJRX zH#=e*;0>|8W^0#jucOBu+a<>EPbeZf zzwyEEQv^NV__?P!O?T`Cl+nEFE#q!g7|f9PO`BQ@t`@>yrup!ZQdho9HIC{hUMRKQ z{WBqW8RUiF)^B641l{JW(Ufr-SSSOZM>j3!2~&9EE^8RilAd1M;62@^3}bCFI}5Ws^1D2H4pJMO1?eB&KmE+oNZxo z@~>@6?ni}!*^2SuLzvZ5ALNsHG|x?^hnok!nA>AK^FOYn9{C6K`d0L$;>9_}N2pJg zs2`y(D(d%|sDFiznu?S?2C z#ei{=trt2?J&OSEj}}KX^LuY%BA`BeM7Bg0IYAn4;j+kS_M*Sq)xAeV6AVrxW^lF0 zc;A>ATgJ}Vg^YIhYbf2;;ZSW&5N=XRk2RWdv*PY1Y&XYr+ucVg*27~R2$r;fyqTgC zdnlz<&{0Yo$97obKPFX2Q|>HI?k!FpD2YCR-PdjCyh4FXa*Pl6!atMd<6WBnd*A`y zD7-29{(?nlJoCG7ktgDr&j_$zfUf~G$1}eyV$A|X1b9%S-78{20qO*(5@{|GD-+;W z0aj94tQOlHidr&ALyv`QXXlGFnhXy)cWz4Do;uN`yFMw2b%8Q(T<3}Stfd$^Jgts^2cXA%)d|bZ!7=a!Ra^h?_K;`&cB=a zw~~LW__vyW@8;jV7vS&Vm|Ff7d|I)c9iMfCe_!O^NBFmee;?)F$NBd;{uT6h@NhBz zuHfGu?0kX`|31sVPxJ3S{@ugB_wlcnf6MWe=Pl}){uwkZ6-{Ccd#96?@Q3lxxY%S2 zM*aF&KaB-Q%|$BBJzvK*hWXCUz8Q#Pjn#)~dYrGVF~*k}lt{HniTJXVNP}7IuiDky zF&LiZ`>TC+TBW>_?5cC8+tJR-ilYFwQ6u-MJ&WVA>(HJBwY7%F(77uQK1?!{*cC@b7iO9QsTN ze*jW0J8kZC#=bI+>uf84@wpC;vt#9fv_2Z0v6eM{=`fGZCMQJaK|}k|QeV)OLwz^@ zSnM+Ron1KO$nWTjkt~m(J@%Y#f~&E7Ul(5XuPz{iY0sl57V~1A)TOTmiu4$J6{Nd5 zf?Hx=n}|}P>AwbuO{Rs!UuYo_P5+p}I{KK9Q;B9Sh7kdOJwR+7M1>t(7b_EN%t#+E z($8Y^KQ;&HUHT6(u|v;emId)WKZ#{xf^OLrH||~yjm6%iQm|a1g)EL#yJL6ox&+~Y zt|VJ3L`G}@^Hq^ueL4PWT%UJ4?rV_nvXS}jwAeJ`?K`BaEWQ_Q+4HWd|EP?y{I_;@ zJdl##(eI3RNA$l3CgwrQTJ{V=Qw8B=w~?5dnr@*Q`%$aNXDqtoL$qMpVqG!Is=D~cFYpuUOv$DaHzIAfU<+%|;HhEfGO9M^?% z(Iz-3IGTTovKWvy6mcW}P-HbYW7AL{jwgq%AL3<6ECL&fvg8>B&@9LyFCps1hu0gYhlARP(xDZAea>7zxu{>jw7WSW?K(xmpKIJWsm zc3ZB^q4pGKx5Ni$qVJ+ZVS)bujGFQz>&prDKpFeCW8flx6Q8vn%O&m1m`*8=zfoj%}j)LY`s;wLY5%F>yRRbSACw zKLgi2CjUS3ex#ptQU2qG`M<-NjLCn;#rQu${D-7Q{iFI4$tHZ*&=*2-!uc{%=TRyy z6JT@ur??Veh^+;QIBX`@M#~Kg-)ZKj)p=-dVdLR<6ea4&HphMiMWg4hxemk}@n`fN z@_{maCi%u_=J$uvUlr*)44~I{pb*7qvl?JPmUT82UH_-n@UI6Q>M`p%hE11~!ON|x z#ZZD<4(Crhuovd;c3gAP?(STvW|*+kYf=>h&X5e}tjWe;n(;Yna=3TEk=*UbKWs%#{elGZ*q`_o4AAjC?V_Ly$!{I_*Y93Z92oW3k5| zHwLzG&yzcQ={i`^y7fRG?qi($&v4lqo=n{sxg=&0mjyqdmt4e$|1idjR#Ski485*bgD zqsApSIxQ)nK5D^I996R593`IVPDgh2yMy4GALn~krL^jPIi6{y6jXMx60Xd~DIgja zD{!Pbu)=^^FcUvYNFPOVs0(exHeO~Dvc|!HoQl0mb<3^q;{WhW@xfjE8Ny{`1s-DeqR1cUy(}W*Vn0Tp|DA;VvF`#C z`xZVDN%_S%Ji@SX#eIFsKn6DY1(|02C?qe^I zT^WlzoAlpBJhmFcUhH*{NiAf=xboK)T`+vVj^Lnz*>lO7KG8**^4jQmD2fx^x zXbQAvE*$!`kTZu^-;UhCk#6q+ZZ_d;9B16y(Z2=zsb6|~&c$>gGURHXfP7Vpty!T` zNA3~h_pKR96ldAVJqTh8Z(^e(I`Oy?$;h@ZIT_4SS59=`wo{tA+>lI{S#ateK9bY_ z3!Lp^v?rBhgzw2__fseh#+?e7#BDuG^h-!*-g74Q07T9|1uah4^;T${^0~~TkdRL-p8#%8F-xh0Ij7VcKsq#WVLJrgkXv zDcmf=(Gs(WH&NVXMmCdjxYw-YBb;wDDfx$$=OG0iaJmMOe66r0osjr2-0U(c6E>hI z=X(&nfug9yChiw#;$Tw3%dW*5lF`+nIP7zHW{&YP{j20`KQSj+ybW5U(}zx zbX|B>G3Zf7$w2-oYkLopVz1KH5fHd%2w@cUt1(XTrPCyg`V)9;tgk5rOqOb%I@-%q zInl7UhR{*kcjEq%!JzkEG1uqc7`cpHcLYx_|b?LwG9~e;!GR6&Q$0z5Kw8Bc;-(^B483I?@qRqJ2WrB)p0vdm^P}d0xY)J=0Lv*$ac})|%F28`+5B_oqUPoXg#LvX93TYi7?1`Zdgfr@^#S_l#JbR1B8_GYX4i-;1#ckL41pH;OdaO87GN^V(HoX^F|B(%D z2bVO`OHgFo8tzV+HBcBg7Dx2Iyp^$K9jQ7N%vn~Rn#6)LmiY)w)3?3<5f&ll?N=%o zGw2^QY<#jaFag4q2 z`Nz0uBvHvAgoJm;8%JnNCiYCQDIjDWXGzi)-mRhRxzgkTmt>B_)5qPQyH z05e^Wa`;zfWkQb?6Npu|5wpm^zSa#*R@EGR9}Uhb|JI^)dev zD@vm$K5IEopUuBl^RI<}6Z8F+=NshTX*_iy|NaaA=JD?y{$0wybNQFH*yFQqO2FUD z!wUbd-i2D9IMJr-g#27=A>7b+c)ugwJA~u`Mcre8V&LYdSp7rgFN(!A}4-$}5xM!8l#_ z{Tvicawv)J;VI$6 zCa5h;c!BA2B{G%wb)zlF9((_tA$46KPX`G4_MQSkbPo~tH6?`aO9=ZA9+G)aiLl+i zAL6+B#1K06J9w=Yzit0i13VS$8KNobJpy={We8xkIp z@M#GjmasuWO=tH7M5s!_t0jEHFYx~+;RXp!5+3=iNDoT5K*F;=fp3@4A>kAWpRO0_ zJ_#2}IOrAlHza&k!mmguQW<+s%J(w~GlC*zri4c&UZX!D@y|;5xP-rz@IDEvB-|+B zN(oa#g43fiuJJ9EctygyB-GLyW!x{}qY^$N;mZ>KUc#9YrporYTEf{9R>^dY|2Cx;nwxoMV(&b1vAnA`w_%%tlQNpJrUT|ToO!9k3!XpwURSAkmC1hJf z+$-S;3C~J6yIQ1IOZXKDpOf$n2~%nW-SrY~lkkTUz9-@Gy9M1n622(m-CG6zkc5M^ zBHkonl7v|@U8G`hHA?@En^>cc5|>G+5t>HDHTq1^uQtxG@ArHIFQ^`uJLu`e=hOMgzQd(uj4jhlv3kaG3AwcI9 zAuzy$Jbs>lF!J&|ga?lhe!2$^xaw5lWh*?JSxJSTS$%%M9c(4aUKy%o*3cHV&g0|W zXN^0^R@ZN1#U3~1303%aP(D8ofDiJ!$t=O6hRg;$xb=e%F9*Cr0qMtxr>AbSN4j$c z-t`m%|3oyEH6lJ!|HY}P;9b2QANfFqtOfBl87Dg=IvE`T+&(ujW|kN|Akn1&E(9Jk z7gh>r0BlR3)A$jjy&dgIMj#JyvqWh8h;HL3y6ObFPvVz@eZSHCNFE>X+A|q}HpFQy zN)Pc1`YYU|>`KpOv>0PG-YPz_ZLjcqYv5!&et$jb+U>6i*0?Kpudc7EVoj^0eNPtf zgX;xkxdOIL70`T@fRiQOJYB@Emho%}ub0p#=}lJ)x``5>C1I+BY_&*#dy;?o)R>Bqu(or^4^4G}n3R>is*+RsQ-qbG^^w z4b;>*>ubEhRrp+L}tU#QW+4uoL*6eNLg*g|mm-huXz-tBlVVaF>MZ zy<5HY+r4H_gBv}ZIEbg|BJ`UpD$NzO_~~thAU3QWn=hyH`zv;sy`JsnT90>2P>g_| z6Z9<-Qfb<6x6=`R7@hjzwRo|HrDv}IfA_EXK`Hkr&PuVsJ0%>IEIKcc)Q7dl6h{AE zg?@ndVh{PM?u7JSgbj`hsfm}y8{0S7Da7U!UgDql_&}#UiFA2m@FY6osU)P0F1sE4 zNJi};AM3>1n2<+HBbbbLUqV_t!o7G4N)oAt!-@P8lDjz}y&d6R zycXHZ?ri1%3fq4E-+Hqa$WL}L96#D9#3c$yQ^E+tD7_-l1eLI?6-EV}4J^rsWjV+mo(L_D-7QBLBU_#~EV*$w}LO<9a}7k!A{nUG$d z5T;DTLwgd-CBBJIV!2t!(}r}6nHPOXkKGCBy$Fvkn=%tGjdx=C#6R)TXb4x0Jb4&1 zF&1C+Av;kL(yI}^m<+^(c#dUv0_|NA4^m3;M@2VBQ&#ssd;OY{RvVW)r^ zE;}!enU*m&AB6~91C$RL(ZS|o*%PeDUIcQ6@tA&V%I4Dr5F?9A9ZvkHq2B6m#+sI6KeVFcQb<8b{!qoUUmkj?*=dz?E{keIs$4t_8Rl7D3wI zUZHtH$~Y~a?I+pzoQHJ5|2|9hJ@?#GxtZ1lS*Q)KuRrL{wp)1_<`K9%co{i~xKcrv z2OQTO>2rV3k;OUp^g^Vo|E*mIUsiDn#JGH z(f-y9YY~4(M*K~gY`#_{wx8yix^x1|#b&{kx|`X=fwW#jPijYsFS$I)iQm5Dq90@4 z`76|w_r041$BBSIu_75?4@mu~0+8sOux;&WoGx%VW`QXKq`W~uRGA$Bq`bcYOaY9` zxMKz)c*+6MJnRKP;yo1?%v0AU%EOwL2kmR99gCZv6BHVpgOOvehlB|Sl2F?PgJ!bUX0z@nt z0&pF0NIrPSXfo@e|}9;eJhK8YUkVWcmo%=eg# z>&7Lsam$mPNqJbi>64f~IG$n6%~ni9O7z~*G;}_Q zbcXcNX}VlJ%dJG4FHB~sKCO%-hIKhp@J>X3odNk0o&!t;v|USBOnCvv z0e%&b%AzZL#BVDg^{dYT68)2aME4^=qSH-9ozWgs(JoU<$FZr<#nc6vy<~%vc%K?S zqEA($4wTl9@vLDE#x+?-9ZsE77bH0+8B>`Nyo}&uTrk0>(NCTW8^ZghMxS9|8KBER znHiwXSYYyvpO(s|Ei|%e{wci~zRSzCykA1T!g-AS8F>lvveqU!Wtn+8IOCKDfltyg zvP0c?9UH$H(2&9me(ufk#uLp<_&k0p!7gXTi? zB$iV;Mmd6Nct7K3;7j_Ty3*T?cn{vwcxjv%Ae%&Z!bZxeW7-Chrw*Gd?tqJSg-V>R z)t>G1=VH%j`wa^jz>1L9&G%jqrd=ez=d+<2Y&li#=qO5k_HHs}tx!34T8;M#$sxrg@rd^2tz{ml5ao-H+jpvPaj!{-UOu>W3HR9{y| z8|W|T3JVL?Y`)v$4qD{SzQwZMw%qDkZM7}mkT>6A!N~U}s7fwLWlkx?ANKTkE3K84 z;P0#V;=Y|_I5fn+#AdaYIc?Sr^A`~NYZCI6*00+EN$adt5O-Z@vs>d67@N^@Ync^{ zN!V|BYd7>W3qz$frrzF}RkBIrrvG-nj2 z;vL?KIt+e)9hbSIxY)gcV1?K1`RlmM8^xsrfBUxf6IfU;s7~G}4o-P2^9fAG(8+>P zT-JJg_5O=AI&Z5nc;t`bQCd^?S88M7Xb$zh`r7&}J3gT<7b=7;8pUM;{n^%<;3shz zqc)GH7S;r;ma%(_)3&@A7C&)(AswDB_f*yUJx-jKjj1skx?x$_9c;M2&aJGi#roii z!Y!Vl3&L&nRHBOY{>mCJ+3HXSyA?UneO;@O`_<1B20XzH6}2H!>e#fr4~?+*)vnc$ z^BJCZozLT`{Hqy|-+0DIl3EJ0*WtTitkO3~J;4!-mOhbwfrY$LVfN;VK#kia4iYWJ z>kG5p)fIjh4ybE_0ZXwZ5UjLVKqgQ9>ixihGe?s=A6`!K3|Tt71o;e`cMEO=k1QV= z8P&Fga8~a?`4zP*@HePE;4FmN;Bi~B^A@beKA~kHaMlesEF{;na0-<+WJUkOrF|u- zurN=!fdoxjt$!cQ%^F|0IaE_yDgEmh<&dS`IJL0Gi^E;+XoWqEK}i}KVQk3hpH}D> z{&`4W7v#K{|HzzC81hvvC+`n4yC9ntUBS9ROI~0>X}Xlc!ug{J*JDtkwC~KiaI{3z zg=*y~@_!l%D*}O35d+?lhE4homX=oN#v!=pLgM9CIRc{0c2HrU3k5w~FB>ZS zH5Hp{J;fe>%{COf%@Y_a|AjV6HvZgY!=tb^nqDOLCytxH)c6$Dd%c(*l-3J{jiEMP zYcZA&Xt`FvCDCvzb7QF|>oCTAz@}+#IE_yX*hHhOi^gpQ?kG6@^*)b3STmMV&;n2= z%hB@LVU1j+m&z#T5hKi!w3(-Ig3}1DL)VurIV|Z=XXCS)G2I5wN46<% zv}hdvKI~ZO5I=a!A$5}ug@VSmfl&?<>I;+q_tInww(PDKldiFCSGGu+bXOTZbok?M3C63jYn_XC#bQZQ+PaRE@%j#-1xfN)R#{6Ng zBgXy3Nf&x1D|*?EfCoDjltYUN`h^q6pmzsD6~TIcK+tP3o}pZSA$@6meXXG7Ax?L> zzOJE&53!8hh42c$2fH?pBXo-=Si;z55i0W3*1B%Y&tqQ`@zsodSA?8?b|XSX@c2Q` z>QEiroLe*}k+UBOs&)Jt5$7-g7)}6(#<8rnW^-Kwm*#)bMw89&F9~cZrTjKm3Z6;DOo=1|qerYj=5U5-8xfj#u9ZXTSV4R+<@=lYEEj71 zH(jA?xwUxBiWQ;ekkEOhC~4@9smonY^fNEOS9;JF z>UXeNG9`$;HPo@H-X&99wb*kbO`%NGkLt8`{pwPCiET)?iy3n8%dswB1+6JjL6;YI zZ^6ZjtpVJl#~vMIMW3w2<l8fh7$F zRacOfDWq4IUv4qFWJ;BG^aIU#8a!^SZL}nj>1xR054tb|pwW2>Vm!cbHZ-R$FlT#> zcMj6$;Qx0DVn1k3?fg0O=LnM=MN#dk@Fga*Z>6ja))rOxpgmfC@c(V-dxUe;*KZB^ zv_mPI7js3%{)19B)c7$ks$K6zv6YNHs$cIFCQ&(LQ!BU{8C$EzO#Y(D#w5&vib8%r zbcUI;+kG7j;!CSgf3upnq zU@%?C-CoHu60qn8=%YZg|0JwfuG5B^HGm3uD8#PNVTMf&Cx{(2oBjB?1r>9rO>CB#<*eLn|8K*l@^n4`abdF2U;^RO5!T*o1s6Aua->-OSllfHDYxpA} z`{ca`5G3K9XVxeMq;}F?!sM(Rc`tr$p;g;X99ooLjBbqe`}n1%N8l3tFEK~w(?HJdllWY6!hR7skk5t9Te(;g zQjcAZ%U&PYY*EC_MG(sG*z{scm?kz*pqQRsY+-N-9bSt6C6^qZAm`BRz+xG*oklH2 zIj6DY8Jd?Q=w~cC&X4bqVA|jk3)+M%BlP+|7BMk)E#8fob>{(o1FwR6XKjFVH!uTX zfUa`pl~Jyq~byemg{=AOp;Fk~ip@=C~p zFu}{O!u2?W@w+sZay57$oOtJr?!u)^N1cEtI1O(~nYdR&ch2aZE5WyM$HoDC9`3Z& z;w3%M{k9MBS`aqjUe|oQWeDRpQ_O{zXvzWG@RIxl-CVs>Wti?M^~o@skN>C;FX_A(uo7%JuEt%&z#Lr2@Ye_GtAcZ| zJ#kY-planW%}axR{BYck75As)mM`)lX8@~O z{N#BD=VQjRlYH$v8>^M)#o>`Uiq`n=g`by|yy z?W=DoE0cl$PMldVE~0Ifr<_lfKh^$J$5Y);^*nX@sotkzPYpaZ_!Qf3*l*gOwcory zXMf)Qg8i2L%6{kmvi;@ztM~i%H|%GvhE`K+R;#%+r!}v&pw-fJ19=Au4p4W z4wN6LKHxjhaG>!((}Cs#`wo~7<{T_IXgR1HtUl;F*l@7<;J$+`2RjaSA5^|s{>|!d zl2w+Y4y?t{Vrt21F}LKjX2c}sPRuce`-v8Ab{xn*BVOG{fzM@vsj tZ_7Xn`?~4t Date: Wed, 24 May 2023 15:35:24 -0500 Subject: [PATCH 32/72] Fix result reporting --- src/TrackingOptimization/TrackingOptimizationTool.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/TrackingOptimization/TrackingOptimizationTool.m b/src/TrackingOptimization/TrackingOptimizationTool.m index fbf2c1e80..cd6f928d3 100644 --- a/src/TrackingOptimization/TrackingOptimizationTool.m +++ b/src/TrackingOptimization/TrackingOptimizationTool.m @@ -39,6 +39,6 @@ function TrackingOptimizationTool(settingsFileName) toc -reportTrackingOptimizationResults(outputs, inputs); +reportTreatmentOptimizationResults(outputs, inputs); saveTrackingOptimizationResults(outputs, inputs); end From a6199059582f9f7628df54008e94bbb1bfb47207 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 24 May 2023 15:58:51 -0500 Subject: [PATCH 33/72] Parallelize inverse dynamics --- src/DesignOptimization/inverseDynamics.mexw64 | Bin 40960 -> 0 bytes src/DesignOptimization/pointKinematics.mexw64 | Bin 31744 -> 0 bytes .../calcTorqueBasedModeledValues.m | 5 +- src/TrackingOptimization/inverseDynamics.m | 65 ------------- .../inverseDynamicsMatlab.m | 2 +- .../inverseDynamicsMatlabParallel.m | 86 ++++++++++++++++++ .../makeTreatmentOptimizationInputs.m | 2 +- 7 files changed, 91 insertions(+), 69 deletions(-) delete mode 100644 src/DesignOptimization/inverseDynamics.mexw64 delete mode 100644 src/DesignOptimization/pointKinematics.mexw64 delete mode 100644 src/TrackingOptimization/inverseDynamics.m create mode 100644 src/TrackingOptimization/inverseDynamicsMatlabParallel.m diff --git a/src/DesignOptimization/inverseDynamics.mexw64 b/src/DesignOptimization/inverseDynamics.mexw64 deleted file mode 100644 index b9220e37d142138f9092ffca43f8f4545a5ef4dc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 40960 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 diff --git a/src/DesignOptimization/pointKinematics.mexw64 b/src/DesignOptimization/pointKinematics.mexw64 deleted file mode 100644 index b8ca6b73ff4ebabcec6ebb4571420156aaf6c7dd..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31744 zcmeHw4SbZvwfAh24ND;If(Zt+vS7GI!$%;XLBQ;iEItbxSRr6ka1%Bg5=l1O{ea-d zHMn52Jg!YGy~X})-&Dm0>g}7h)<(Q-1FIJ)nJ3wV zAhx&n{r%p)efT}knK^Uj%$YN1&U|htxucmSGsaTz#^a2&1JYyR-%tLSbc|g#{m^CX z+3_#UXxBMjno(L^6EOSy^;`TEb!K;k*IOSnZ}yn|A+Nc{Yc5{1&Rkbt>6x3Jo|Y|H zZ!LXc#k@tYXKL@il&r|iMLc8c8yT}X?8#Wl;mM3_4qwZ-4)E;pt25^Se!2Ly45I&o zktOp1v_v`mFmc+_IDl%!{!nnY++{suIvgT}L zEQ<OEjAaAgvy!o^G~5_)0O@-Z@fK8t8vP>A+@Pl+h%~EK&pbp% z(VH17pX;xz2v#um&W%X7;4Q$r5O2a`LCRb~#s(%Lp%E`Bsupj;V_~dgu1^s1I-;(; zOn(9qf}pd^-BMLk8DMNSCAHv%?%6iH36F*7`~klkIIcVBkkd6J5Fu#cbe`IJ5Rxug z2VUZL58i~w!r1bJ=s!eI3BQvQ&;0EI^#072eEL4h!;j;ugkQ{2;HV zQ@VsZ zN!dwOqpe%*R8NMF8?p_?;*(0(E!9M*9tn4w8h@Q)EItys#iyuUiVEfsBG1*S6@Oxk z@+~M`QFk_wIBsVOB>3N4f?a7GHJnl$eULE4kzHv)B97`)Iia;t2_J5ZokX{S{A(Ja z4_G|~n@9u;7XgZB1=g}|Wc}4MifYYL!hUd=t^=<#sbBCt~$HgOn%Lv5)nn*!P_}(m&KUq<) zJeo|6lXqw52`-OWi9SZoLQ#Jbdkf71h-CAfkU0Y~D{kvYN^W-_=Ya+@D3M^ci5k+Z zexRslV&4#XQ17{@;khndaMGa|@r$aYu-Ga{qo~2`0wo$2I^CF^M4%!r1c!eI2Wc%*s3B`f%&$a0^CLJCCAzE(enU|wD7%jZ(-c*Ah`M~-q=Y*S%93B) z_Zuae{?HAK9o0Voau8~D?fc;sj5#93*;z`YG~1*^9NC6-8*a01fFcWqdPIpj0F^3G zZ1@$U5{`eoO^-bq76 z|F&0&tQhRih}}Y|kP8GbvPskk{TWoTatB%!UG2W?YHHtxlHKQm(}kHq>dX6KYp)V+ zLWz1*QD2Vb5ILc@z|pd?j*kmLn2CYstRw^W{fV@~JlzSnqP(g&r~T5&h$w)u%(N1n+@h7DY8s z(@#W2$`rRrNj}+s8NyBqt3?G$GJ!0j$~~#<{9r_HejVdYV(os+UT8^^&RA^j&2hwvoH31hX z(UmZYhak1LT;7^eV?29S?JZiq$6+{y3P0b0P_6x;9}1`sw^2)jJLoJejZwEkERqT zi>)S3)-1*uB2yw(H02KA*2W)A(XsqfAL9qD%Qy;%XXLoFgTky0%m3QdKCeu_yy%Sk_f8m{nEbT{Z{xRz99 z?&*cXMimZSaP>`q>J;^jm@)fPYLByosgXU~cMi%k%3@$d(H$Se}XJ&JmqOuFyOlSkO_@c5yqJD`X&%90Pb5l)y-lbv)pH1uaU-6JsK z?F*F1tJ+N4Z&DP{NW?#SZsL%ewWXGS%u+e~;RN-Dv(7b2i=C8UC+$ zXuNO?h_xDmxenECv8$8F`lh49RNI&I1g5|V!tohOB044cINVX0HMg6)B8zm5d5XHO z%;8R28BNhCOS%JQYt#~tBq{1DP)t&y%XLabr~cT|biFK?e1jqes(Ak-VyU=cL-3-x z@TZuay+Y$&_V5-x;AzI`tR3@<2QcI*OS%Gj5QY9oY>ZSVbTFFzQN_ps zSsandY^MXEG6#w?JEBV$+hN$zRl(SAKcGp^sw@n04s|~D=gVP`cJ&P!8}FR~t;5P! zLm)WYK(!J<1&iQt36vE_xTNQbd?^A`5S*-ZK}h7ns1V8PdyImd*xAKsve5TAp18`a zs7J}s>54FXf}x5gCxkus4Mhj#qWB_<`f~1ZYw{~f1c^m(?^IB58p4=HkN}uEfSXoW z|2)d$AWlUE4vaydE6IJ?k^HhP_oOv`MCNgb0i-x$0y`KN^g-Uq5;Rgrgi28NSPOYZ zlo?({B&0VL8jOP{wIgYF%*GrxHtL_M6=E>5@?{8TIhL?(3&S74VOno;#)GLCS(8H7 zAjKJHM61LNJe7x4gL8+1M@&7{9jN0^9qLI%9f!dO^RUI%>Hr$R zTFtu_O;9n*GwwRXHS<|C(Pc0USXhMLPYPiiT8??u%4#Lo;7C5)KOIOys|)X zA3;NcnQ_MoRm@RFx`MmV1RJsYOWIpMfg&8>zK0v(N+QYjt|9|uWA;c9MDI*kT%mm|>q=B7Eaayp$tH(-20jBdruji)6M?k@tWjad zl9R?={{d#?0Zi6&hjl@(UA-4WMj;DUmh3)dZ2C7UN?iwKqnG3+IhOPoKmQg3!j zfP=D6k+N4wWrIgju(|}Dz%J%#8$l$rjIJWJY#=S8Gnx8t=Jbo~;Q|(%hw>V(A6eD| z)6~gCYRxri1!=m1IiSrTc7BH=aye$7Tq_E+0-vu`=o-N9|z0h#ukpH3*+1?eUJ~1xs1zyrT9f(UOkXb?4*pW*G0SW|AREaVsfsB$E_J zbkPrg5s%xW4>c=DiP(BUULDW;0_d8%YZbh zVx%~|SHnfWKnu)BY5LC&p~_gul*D39e-Z-pOdk?}1}-p-XU^tAowxod1JsV_oY$^p z?0H%l#@13z)zgrxnap=G=sPeR#4{r^X&IMG^RHj5j68lr+ioKKNj$Rw2|TbJft8V6 zH#pIUmGDu6J^UwK=zv4juZ9HF9U#0tx!bP37SH@9P)GHpm5k}A`msA9tkA<_NTIr( z=DP#8^3_sF$EfAfF36v_pa}B#@PeX}$K`_JHbLAS`_6cccmZ3gem18AMgQtXBzl8I+yz7RK0pXlWE4v30e1r=j`8?`l2pqge7<&H>gb{U#wAEBHGG*dtUQ7;87Z`lo zGCtC-M+>4)6;s`Lm5ZSOs&oD^>+_J0+0?_a6x0o#upBW#y9gSYB{?0hP5h!MNV<}Qh4WQ)2ic(3SK|+)(G!1(tb_1cJ zSJ|jUC2}?2n?aw9rE<~%BBlA8gJ$KJUcC~#f)cuH+@a-TKT>mPH;44aBVAf5xwJRX zH#=e*;0>|8W^0#jucOBu+a<>EPbeZf zzwyEEQv^NV__?P!O?T`Cl+nEFE#q!g7|f9PO`BQ@t`@>yrup!ZQdho9HIC{hUMRKQ z{WBqW8RUiF)^B641l{JW(Ufr-SSSOZM>j3!2~&9EE^8RilAd1M;62@^3}bCFI}5Ws^1D2H4pJMO1?eB&KmE+oNZxo z@~>@6?ni}!*^2SuLzvZ5ALNsHG|x?^hnok!nA>AK^FOYn9{C6K`d0L$;>9_}N2pJg zs2`y(D(d%|sDFiznu?S?2C z#ei{=trt2?J&OSEj}}KX^LuY%BA`BeM7Bg0IYAn4;j+kS_M*Sq)xAeV6AVrxW^lF0 zc;A>ATgJ}Vg^YIhYbf2;;ZSW&5N=XRk2RWdv*PY1Y&XYr+ucVg*27~R2$r;fyqTgC zdnlz<&{0Yo$97obKPFX2Q|>HI?k!FpD2YCR-PdjCyh4FXa*Pl6!atMd<6WBnd*A`y zD7-29{(?nlJoCG7ktgDr&j_$zfUf~G$1}eyV$A|X1b9%S-78{20qO*(5@{|GD-+;W z0aj94tQOlHidr&ALyv`QXXlGFnhXy)cWz4Do;uN`yFMw2b%8Q(T<3}Stfd$^Jgts^2cXA%)d|bZ!7=a!Ra^h?_K;`&cB=a zw~~LW__vyW@8;jV7vS&Vm|Ff7d|I)c9iMfCe_!O^NBFmee;?)F$NBd;{uT6h@NhBz zuHfGu?0kX`|31sVPxJ3S{@ugB_wlcnf6MWe=Pl}){uwkZ6-{Ccd#96?@Q3lxxY%S2 zM*aF&KaB-Q%|$BBJzvK*hWXCUz8Q#Pjn#)~dYrGVF~*k}lt{HniTJXVNP}7IuiDky zF&LiZ`>TC+TBW>_?5cC8+tJR-ilYFwQ6u-MJ&WVA>(HJBwY7%F(77uQK1?!{*cC@b7iO9QsTN ze*jW0J8kZC#=bI+>uf84@wpC;vt#9fv_2Z0v6eM{=`fGZCMQJaK|}k|QeV)OLwz^@ zSnM+Ron1KO$nWTjkt~m(J@%Y#f~&E7Ul(5XuPz{iY0sl57V~1A)TOTmiu4$J6{Nd5 zf?Hx=n}|}P>AwbuO{Rs!UuYo_P5+p}I{KK9Q;B9Sh7kdOJwR+7M1>t(7b_EN%t#+E z($8Y^KQ;&HUHT6(u|v;emId)WKZ#{xf^OLrH||~yjm6%iQm|a1g)EL#yJL6ox&+~Y zt|VJ3L`G}@^Hq^ueL4PWT%UJ4?rV_nvXS}jwAeJ`?K`BaEWQ_Q+4HWd|EP?y{I_;@ zJdl##(eI3RNA$l3CgwrQTJ{V=Qw8B=w~?5dnr@*Q`%$aNXDqtoL$qMpVqG!Is=D~cFYpuUOv$DaHzIAfU<+%|;HhEfGO9M^?% z(Iz-3IGTTovKWvy6mcW}P-HbYW7AL{jwgq%AL3<6ECL&fvg8>B&@9LyFCps1hu0gYhlARP(xDZAea>7zxu{>jw7WSW?K(xmpKIJWsm zc3ZB^q4pGKx5Ni$qVJ+ZVS)bujGFQz>&prDKpFeCW8flx6Q8vn%O&m1m`*8=zfoj%}j)LY`s;wLY5%F>yRRbSACw zKLgi2CjUS3ex#ptQU2qG`M<-NjLCn;#rQu${D-7Q{iFI4$tHZ*&=*2-!uc{%=TRyy z6JT@ur??Veh^+;QIBX`@M#~Kg-)ZKj)p=-dVdLR<6ea4&HphMiMWg4hxemk}@n`fN z@_{maCi%u_=J$uvUlr*)44~I{pb*7qvl?JPmUT82UH_-n@UI6Q>M`p%hE11~!ON|x z#ZZD<4(Crhuovd;c3gAP?(STvW|*+kYf=>h&X5e}tjWe;n(;Yna=3TEk=*UbKWs%#{elGZ*q`_o4AAjC?V_Ly$!{I_*Y93Z92oW3k5| zHwLzG&yzcQ={i`^y7fRG?qi($&v4lqo=n{sxg=&0mjyqdmt4e$|1idjR#Ski485*bgD zqsApSIxQ)nK5D^I996R593`IVPDgh2yMy4GALn~krL^jPIi6{y6jXMx60Xd~DIgja zD{!Pbu)=^^FcUvYNFPOVs0(exHeO~Dvc|!HoQl0mb<3^q;{WhW@xfjE8Ny{`1s-DeqR1cUy(}W*Vn0Tp|DA;VvF`#C z`xZVDN%_S%Ji@SX#eIFsKn6DY1(|02C?qe^I zT^WlzoAlpBJhmFcUhH*{NiAf=xboK)T`+vVj^Lnz*>lO7KG8**^4jQmD2fx^x zXbQAvE*$!`kTZu^-;UhCk#6q+ZZ_d;9B16y(Z2=zsb6|~&c$>gGURHXfP7Vpty!T` zNA3~h_pKR96ldAVJqTh8Z(^e(I`Oy?$;h@ZIT_4SS59=`wo{tA+>lI{S#ateK9bY_ z3!Lp^v?rBhgzw2__fseh#+?e7#BDuG^h-!*-g74Q07T9|1uah4^;T${^0~~TkdRL-p8#%8F-xh0Ij7VcKsq#WVLJrgkXv zDcmf=(Gs(WH&NVXMmCdjxYw-YBb;wDDfx$$=OG0iaJmMOe66r0osjr2-0U(c6E>hI z=X(&nfug9yChiw#;$Tw3%dW*5lF`+nIP7zHW{&YP{j20`KQSj+ybW5U(}zx zbX|B>G3Zf7$w2-oYkLopVz1KH5fHd%2w@cUt1(XTrPCyg`V)9;tgk5rOqOb%I@-%q zInl7UhR{*kcjEq%!JzkEG1uqc7`cpHcLYx_|b?LwG9~e;!GR6&Q$0z5Kw8Bc;-(^B483I?@qRqJ2WrB)p0vdm^P}d0xY)J=0Lv*$ac})|%F28`+5B_oqUPoXg#LvX93TYi7?1`Zdgfr@^#S_l#JbR1B8_GYX4i-;1#ckL41pH;OdaO87GN^V(HoX^F|B(%D z2bVO`OHgFo8tzV+HBcBg7Dx2Iyp^$K9jQ7N%vn~Rn#6)LmiY)w)3?3<5f&ll?N=%o zGw2^QY<#jaFag4q2 z`Nz0uBvHvAgoJm;8%JnNCiYCQDIjDWXGzi)-mRhRxzgkTmt>B_)5qPQyH z05e^Wa`;zfWkQb?6Npu|5wpm^zSa#*R@EGR9}Uhb|JI^)dev zD@vm$K5IEopUuBl^RI<}6Z8F+=NshTX*_iy|NaaA=JD?y{$0wybNQFH*yFQqO2FUD z!wUbd-i2D9IMJr-g#27=A>7b+c)ugwJA~u`Mcre8V&LYdSp7rgFN(!A}4-$}5xM!8l#_ z{Tvicawv)J;VI$6 zCa5h;c!BA2B{G%wb)zlF9((_tA$46KPX`G4_MQSkbPo~tH6?`aO9=ZA9+G)aiLl+i zAL6+B#1K06J9w=Yzit0i13VS$8KNobJpy={We8xkIp z@M#GjmasuWO=tH7M5s!_t0jEHFYx~+;RXp!5+3=iNDoT5K*F;=fp3@4A>kAWpRO0_ zJ_#2}IOrAlHza&k!mmguQW<+s%J(w~GlC*zri4c&UZX!D@y|;5xP-rz@IDEvB-|+B zN(oa#g43fiuJJ9EctygyB-GLyW!x{}qY^$N;mZ>KUc#9YrporYTEf{9R>^dY|2Cx;nwxoMV(&b1vAnA`w_%%tlQNpJrUT|ToO!9k3!XpwURSAkmC1hJf z+$-S;3C~J6yIQ1IOZXKDpOf$n2~%nW-SrY~lkkTUz9-@Gy9M1n622(m-CG6zkc5M^ zBHkonl7v|@U8G`hHA?@En^>cc5|>G+5t>HDHTq1^uQtxG@ArHIFQ^`uJLu`e=hOMgzQd(uj4jhlv3kaG3AwcI9 zAuzy$Jbs>lF!J&|ga?lhe!2$^xaw5lWh*?JSxJSTS$%%M9c(4aUKy%o*3cHV&g0|W zXN^0^R@ZN1#U3~1303%aP(D8ofDiJ!$t=O6hRg;$xb=e%F9*Cr0qMtxr>AbSN4j$c z-t`m%|3oyEH6lJ!|HY}P;9b2QANfFqtOfBl87Dg=IvE`T+&(ujW|kN|Akn1&E(9Jk z7gh>r0BlR3)A$jjy&dgIMj#JyvqWh8h;HL3y6ObFPvVz@eZSHCNFE>X+A|q}HpFQy zN)Pc1`YYU|>`KpOv>0PG-YPz_ZLjcqYv5!&et$jb+U>6i*0?Kpudc7EVoj^0eNPtf zgX;xkxdOIL70`T@fRiQOJYB@Emho%}ub0p#=}lJ)x``5>C1I+BY_&*#dy;?o)R>Bqu(or^4^4G}n3R>is*+RsQ-qbG^^w z4b;>*>ubEhRrp+L}tU#QW+4uoL*6eNLg*g|mm-huXz-tBlVVaF>MZ zy<5HY+r4H_gBv}ZIEbg|BJ`UpD$NzO_~~thAU3QWn=hyH`zv;sy`JsnT90>2P>g_| z6Z9<-Qfb<6x6=`R7@hjzwRo|HrDv}IfA_EXK`Hkr&PuVsJ0%>IEIKcc)Q7dl6h{AE zg?@ndVh{PM?u7JSgbj`hsfm}y8{0S7Da7U!UgDql_&}#UiFA2m@FY6osU)P0F1sE4 zNJi};AM3>1n2<+HBbbbLUqV_t!o7G4N)oAt!-@P8lDjz}y&d6R zycXHZ?ri1%3fq4E-+Hqa$WL}L96#D9#3c$yQ^E+tD7_-l1eLI?6-EV}4J^rsWjV+mo(L_D-7QBLBU_#~EV*$w}LO<9a}7k!A{nUG$d z5T;DTLwgd-CBBJIV!2t!(}r}6nHPOXkKGCBy$Fvkn=%tGjdx=C#6R)TXb4x0Jb4&1 zF&1C+Av;kL(yI}^m<+^(c#dUv0_|NA4^m3;M@2VBQ&#ssd;OY{RvVW)r^ zE;}!enU*m&AB6~91C$RL(ZS|o*%PeDUIcQ6@tA&V%I4Dr5F?9A9ZvkHq2B6m#+sI6KeVFcQb<8b{!qoUUmkj?*=dz?E{keIs$4t_8Rl7D3wI zUZHtH$~Y~a?I+pzoQHJ5|2|9hJ@?#GxtZ1lS*Q)KuRrL{wp)1_<`K9%co{i~xKcrv z2OQTO>2rV3k;OUp^g^Vo|E*mIUsiDn#JGH z(f-y9YY~4(M*K~gY`#_{wx8yix^x1|#b&{kx|`X=fwW#jPijYsFS$I)iQm5Dq90@4 z`76|w_r041$BBSIu_75?4@mu~0+8sOux;&WoGx%VW`QXKq`W~uRGA$Bq`bcYOaY9` zxMKz)c*+6MJnRKP;yo1?%v0AU%EOwL2kmR99gCZv6BHVpgOOvehlB|Sl2F?PgJ!bUX0z@nt z0&pF0NIrPSXfo@e|}9;eJhK8YUkVWcmo%=eg# z>&7Lsam$mPNqJbi>64f~IG$n6%~ni9O7z~*G;}_Q zbcXcNX}VlJ%dJG4FHB~sKCO%-hIKhp@J>X3odNk0o&!t;v|USBOnCvv z0e%&b%AzZL#BVDg^{dYT68)2aME4^=qSH-9ozWgs(JoU<$FZr<#nc6vy<~%vc%K?S zqEA($4wTl9@vLDE#x+?-9ZsE77bH0+8B>`Nyo}&uTrk0>(NCTW8^ZghMxS9|8KBER znHiwXSYYyvpO(s|Ei|%e{wci~zRSzCykA1T!g-AS8F>lvveqU!Wtn+8IOCKDfltyg zvP0c?9UH$H(2&9me(ufk#uLp<_&k0p!7gXTi? zB$iV;Mmd6Nct7K3;7j_Ty3*T?cn{vwcxjv%Ae%&Z!bZxeW7-Chrw*Gd?tqJSg-V>R z)t>G1=VH%j`wa^jz>1L9&G%jqrd=ez=d+<2Y&li#=qO5k_HHs}tx!34T8;M#$sxrg@rd^2tz{ml5ao-H+jpvPaj!{-UOu>W3HR9{y| z8|W|T3JVL?Y`)v$4qD{SzQwZMw%qDkZM7}mkT>6A!N~U}s7fwLWlkx?ANKTkE3K84 z;P0#V;=Y|_I5fn+#AdaYIc?Sr^A`~NYZCI6*00+EN$adt5O-Z@vs>d67@N^@Ync^{ zN!V|BYd7>W3qz$frrzF}RkBIrrvG-nj2 z;vL?KIt+e)9hbSIxY)gcV1?K1`RlmM8^xsrfBUxf6IfU;s7~G}4o-P2^9fAG(8+>P zT-JJg_5O=AI&Z5nc;t`bQCd^?S88M7Xb$zh`r7&}J3gT<7b=7;8pUM;{n^%<;3shz zqc)GH7S;r;ma%(_)3&@A7C&)(AswDB_f*yUJx-jKjj1skx?x$_9c;M2&aJGi#roii z!Y!Vl3&L&nRHBOY{>mCJ+3HXSyA?UneO;@O`_<1B20XzH6}2H!>e#fr4~?+*)vnc$ z^BJCZozLT`{Hqy|-+0DIl3EJ0*WtTitkO3~J;4!-mOhbwfrY$LVfN;VK#kia4iYWJ z>kG5p)fIjh4ybE_0ZXwZ5UjLVKqgQ9>ixihGe?s=A6`!K3|Tt71o;e`cMEO=k1QV= z8P&Fga8~a?`4zP*@HePE;4FmN;Bi~B^A@beKA~kHaMlesEF{;na0-<+WJUkOrF|u- zurN=!fdoxjt$!cQ%^F|0IaE_yDgEmh<&dS`IJL0Gi^E;+XoWqEK}i}KVQk3hpH}D> z{&`4W7v#K{|HzzC81hvvC+`n4yC9ntUBS9ROI~0>X}Xlc!ug{J*JDtkwC~KiaI{3z zg=*y~@_!l%D*}O35d+?lhE4homX=oN#v!=pLgM9CIRc{0c2HrU3k5w~FB>ZS zH5Hp{J;fe>%{COf%@Y_a|AjV6HvZgY!=tb^nqDOLCytxH)c6$Dd%c(*l-3J{jiEMP zYcZA&Xt`FvCDCvzb7QF|>oCTAz@}+#IE_yX*hHhOi^gpQ?kG6@^*)b3STmMV&;n2= z%hB@LVU1j+m&z#T5hKi!w3(-Ig3}1DL)VurIV|Z=XXCS)G2I5wN46<% zv}hdvKI~ZO5I=a!A$5}ug@VSmfl&?<>I;+q_tInww(PDKldiFCSGGu+bXOTZbok?M3C63jYn_XC#bQZQ+PaRE@%j#-1xfN)R#{6Ng zBgXy3Nf&x1D|*?EfCoDjltYUN`h^q6pmzsD6~TIcK+tP3o}pZSA$@6meXXG7Ax?L> zzOJE&53!8hh42c$2fH?pBXo-=Si;z55i0W3*1B%Y&tqQ`@zsodSA?8?b|XSX@c2Q` z>QEiroLe*}k+UBOs&)Jt5$7-g7)}6(#<8rnW^-Kwm*#)bMw89&F9~cZrTjKm3Z6;DOo=1|qerYj=5U5-8xfj#u9ZXTSV4R+<@=lYEEj71 zH(jA?xwUxBiWQ;ekkEOhC~4@9smonY^fNEOS9;JF z>UXeNG9`$;HPo@H-X&99wb*kbO`%NGkLt8`{pwPCiET)?iy3n8%dswB1+6JjL6;YI zZ^6ZjtpVJl#~vMIMW3w2<l8fh7$F zRacOfDWq4IUv4qFWJ;BG^aIU#8a!^SZL}nj>1xR054tb|pwW2>Vm!cbHZ-R$FlT#> zcMj6$;Qx0DVn1k3?fg0O=LnM=MN#dk@Fga*Z>6ja))rOxpgmfC@c(V-dxUe;*KZB^ zv_mPI7js3%{)19B)c7$ks$K6zv6YNHs$cIFCQ&(LQ!BU{8C$EzO#Y(D#w5&vib8%r zbcUI;+kG7j;!CSgf3upnq zU@%?C-CoHu60qn8=%YZg|0JwfuG5B^HGm3uD8#PNVTMf&Cx{(2oBjB?1r>9rO>CB#<*eLn|8K*l@^n4`abdF2U;^RO5!T*o1s6Aua->-OSllfHDYxpA} z`{ca`5G3K9XVxeMq;}F?!sM(Rc`tr$p;g;X99ooLjBbqe`}n1%N8l3tFEK~w(?HJdllWY6!hR7skk5t9Te(;g zQjcAZ%U&PYY*EC_MG(sG*z{scm?kz*pqQRsY+-N-9bSt6C6^qZAm`BRz+xG*oklH2 zIj6DY8Jd?Q=w~cC&X4bqVA|jk3)+M%BlP+|7BMk)E#8fob>{(o1FwR6XKjFVH!uTX zfUa`pl~Jyq~byemg{=AOp;Fk~ip@=C~p zFu}{O!u2?W@w+sZay57$oOtJr?!u)^N1cEtI1O(~nYdR&ch2aZE5WyM$HoDC9`3Z& z;w3%M{k9MBS`aqjUe|oQWeDRpQ_O{zXvzWG@RIxl-CVs>Wti?M^~o@skN>C;FX_A(uo7%JuEt%&z#Lr2@Ye_GtAcZ| zJ#kY-planW%}axR{BYck75As)mM`)lX8@~O z{N#BD=VQjRlYH$v8>^M)#o>`Uiq`n=g`by|yy z?W=DoE0cl$PMldVE~0Ifr<_lfKh^$J$5Y);^*nX@sotkzPYpaZ_!Qf3*l*gOwcory zXMf)Qg8i2L%6{kmvi;@ztM~i%H|%GvhE`K+R;#%+r!}v&pw-fJ19=Au4p4W z4wN6LKHxjhaG>!((}Cs#`wo~7<{T_IXgR1HtUl;F*l@7<;J$+`2RjaSA5^|s{>|!d zl2w+Y4y?t{Vrt21F}LKjX2c}sPRuce`-v8Ab{xn*BVOG{fzM@vsj tZ_7Xn`?~4t Date: Wed, 24 May 2023 16:31:12 -0500 Subject: [PATCH 34/72] Use persistent variables for speed --- .../inverseDynamicsMatlabParallel.m | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m index a8c48ecb5..ef2ac004d 100644 --- a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m +++ b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m @@ -2,30 +2,40 @@ % Load Library import org.opensim.modeling.*; -% Open a Model by name -osimModel = Model(modelFile); - -% Initialize the system and get the initial state -osimState = osimModel.initSystem(); - % Get the number of coords and markers numPts = size(time,1); numControls = size(AppliedLoads,2); -numCoords = osimState.getNQ; +numCoords = Model(modelFile).initSystem().getNQ; % Split time points into parallel problems numWorkers = gcp().NumWorkers; IDLoadsJobs = cell(1, numWorkers); AccelsTempVec = cell(1, numWorkers); -clear osimModel -clear osimState +for worker = 1:numWorkers + IDLoadsJobs{worker} = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec{worker},time,q,qp,qpp,IKLabels,AppliedLoads,worker); +end + +IDLoads = IDLoadsJobs{1}; +for job = 2 : numWorkers + IDLoads = cat(1, IDLoads, IDLoadsJobs{job}); +end + +end -parfor worker = 1:numWorkers +function IDLoadsJob = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec,time,q,qp,qpp,IKLabels,AppliedLoads,worker) + + import org.opensim.modeling.*; + persistent osimModel; + persistent osimState; + persistent idSolver; + if isempty(osimModel) + osimModel = Model(modelFile); + osimState = osimModel.initSystem(); + idSolver = InverseDynamicsSolver(osimModel); + end - osimModel = Model(modelFile); - osimState = osimModel.initSystem(); - idSolver = InverseDynamicsSolver(osimModel); + IDLoadsJob = []; for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) osimState.setTime(time(j,1)); @@ -45,13 +55,11 @@ for ii = 1:size(q,2) if abs(q(j,ii)-StateQ) <= 1e-6 - AccelsTempVec{worker}(j-indexOffset,i) = qpp(j,ii); + AccelsTempVec(j-indexOffset,i) = qpp(j,ii); end end end - tempMarkerGlobalPos=Vec3; - newControls = Vector(numControls,0); for i=0:numControls-1 @@ -65,22 +73,14 @@ AccelsVec = Vector(osimState.getNQ,0); for i=0:osimState.getNQ-1 - AccelsVec.set(i,AccelsTempVec{worker}(j-indexOffset,i+1)); + AccelsVec.set(i,AccelsTempVec(j-indexOffset,i+1)); end - IDLoadsVec = Vector; IDLoadsVec = idSolver.solve(osimState,AccelsVec); for i=0:numCoords-1 - IDLoadsJobs{worker}(j-indexOffset,i+1) = IDLoadsVec.get(i); + IDLoadsJob(j-indexOffset,i+1) = IDLoadsVec.get(i); end end end - -IDLoads = IDLoadsJobs{1}; -for job = 2 : numWorkers - IDLoads = cat(1, IDLoads, IDLoadsJobs{job}); -end - -end \ No newline at end of file From 91cd2a250a1704b07efad2fbf523d1817c2440c8 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 24 May 2023 18:50:07 -0500 Subject: [PATCH 35/72] Fix parallelized functions --- .../calcFootGroundReactions.m | 3 ++ .../calcTorqueBasedModeledValues.m | 11 ++-- .../inverseDynamicsMatlabParallel.m | 20 +++---- src/TrackingOptimization/pointKinematics.m | 52 +++++++++++-------- .../costTerms/isTrackingCostTerm.m | 4 +- .../setupGroundContact.m | 5 +- 6 files changed, 56 insertions(+), 39 deletions(-) diff --git a/src/TrackingOptimization/calcFootGroundReactions.m b/src/TrackingOptimization/calcFootGroundReactions.m index 60cfe09dc..7d90e3cf2 100644 --- a/src/TrackingOptimization/calcFootGroundReactions.m +++ b/src/TrackingOptimization/calcFootGroundReactions.m @@ -42,6 +42,9 @@ function [forces, moments] = calcGroundReactionForcesAndMoments(markerPositions, ... markerVelocities, springConstants, midfootSuperiorPosition, contactSurface) +markerPositions = reshape(markerPositions, [], 3, size(springConstants, 2)); +markerVelocities = reshape(markerVelocities, [], 3, size(springConstants, 2)); + for i = 1:size(markerPositions, 1) markerKinematics.xPosition = squeeze(markerPositions(i, 1, :))'; markerKinematics.height = squeeze(markerPositions(i, 2, :))'; diff --git a/src/TrackingOptimization/calcTorqueBasedModeledValues.m b/src/TrackingOptimization/calcTorqueBasedModeledValues.m index c17fd401e..8582af1e7 100644 --- a/src/TrackingOptimization/calcTorqueBasedModeledValues.m +++ b/src/TrackingOptimization/calcTorqueBasedModeledValues.m @@ -28,6 +28,7 @@ function phaseout = calcTorqueBasedModeledValues(values, params) appliedLoads = [zeros(length(values.time), params.numTotalMuscles)]; if ~isempty(params.contactSurfaces) + clear pointKinematics [springPositions, springVelocities] = getSpringLocations(values.time, .... values.statePositions, values.stateVelocities, params); phaseout.bodyLocations = getBodyLocations(values.time, .... @@ -50,13 +51,13 @@ for i = 1:length(params.contactSurfaces) [springPositions.parent{i}, springVelocities.parent{i}] = ... pointKinematics(time, statePositions, stateVelocities, ... - params.contactSurfaces{i}.parentSpringPointsOnBody', ... + params.contactSurfaces{i}.parentSpringPointsOnBody, ... params.contactSurfaces{i}.parentBody * ones(1, ... size(params.contactSurfaces{i}.parentSpringPointsOnBody, 1)), ... params.mexModel, params.coordinateNames); [springPositions.child{i}, springVelocities.child{i}] = ... pointKinematics(time, statePositions, stateVelocities, ... - params.contactSurfaces{i}.childSpringPointsOnBody', ... + params.contactSurfaces{i}.childSpringPointsOnBody, ... params.contactSurfaces{i}.childBody * ones(1, ... size(params.contactSurfaces{i}.childSpringPointsOnBody, 1)), ... params.mexModel, params.coordinateNames); @@ -67,14 +68,14 @@ for i = 1:length(params.contactSurfaces) bodyLocations.midfootSuperior{i} = pointKinematics(time, statePositions, ... - stateVelocities, params.contactSurfaces{i}.midfootSuperiorPointOnBody', ... + stateVelocities, params.contactSurfaces{i}.midfootSuperiorPointOnBody, ... params.contactSurfaces{i}.midfootSuperiorBody, params.mexModel, params.coordinateNames); bodyLocations.midfootSuperior{i}(:, 2) = 0; bodyLocations.parent{i} = pointKinematics(time, statePositions, ... - stateVelocities, [0 0 0]', params.contactSurfaces{i}.parentBody, ... + stateVelocities, [0 0 0], params.contactSurfaces{i}.parentBody, ... params.mexModel, params.coordinateNames); bodyLocations.child{i} = pointKinematics(time, statePositions, ... - stateVelocities, [0 0 0]', params.contactSurfaces{i}.childBody, ... + stateVelocities, [0 0 0], params.contactSurfaces{i}.childBody, ... params.mexModel, params.coordinateNames); end end diff --git a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m index ef2ac004d..09bd93453 100644 --- a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m +++ b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m @@ -1,18 +1,16 @@ -function IDLoads = inverseDynamics(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) -% Load Library -import org.opensim.modeling.*; +function IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) % Get the number of coords and markers numPts = size(time,1); numControls = size(AppliedLoads,2); -numCoords = Model(modelFile).initSystem().getNQ; +numCoords = length(IKLabels); % Split time points into parallel problems numWorkers = gcp().NumWorkers; IDLoadsJobs = cell(1, numWorkers); AccelsTempVec = cell(1, numWorkers); -for worker = 1:numWorkers +parfor worker = 1:numWorkers IDLoadsJobs{worker} = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec{worker},time,q,qp,qpp,IKLabels,AppliedLoads,worker); end @@ -37,11 +35,10 @@ IDLoadsJob = []; + indexOffset = (worker - 1) * ceil(numPts / numWorkers); + for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) osimState.setTime(time(j,1)); - - indexOffset = (worker - 1) * ceil(numPts / numWorkers); - for k=1:size(IKLabels,2) if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); @@ -72,8 +69,13 @@ AccelsVec = Vector(osimState.getNQ,0); + includedQIndex = 1; for i=0:osimState.getNQ-1 - AccelsVec.set(i,AccelsTempVec(j-indexOffset,i+1)); + currentCoordinate = osimModel.getCoordinateSet().get(i).getName().toCharArray'; + if any(cellfun(@isequal, IKLabels, repmat({currentCoordinate}, 1, length(IKLabels)))) + AccelsVec.set(i,AccelsTempVec(j-indexOffset,includedQIndex)); + includedQIndex = includedQIndex + 1; + end end IDLoadsVec = idSolver.solve(osimState,AccelsVec); diff --git a/src/TrackingOptimization/pointKinematics.m b/src/TrackingOptimization/pointKinematics.m index 2eebd2a7d..6df66c192 100644 --- a/src/TrackingOptimization/pointKinematics.m +++ b/src/TrackingOptimization/pointKinematics.m @@ -1,30 +1,47 @@ function [SpringPos, SpringVel] = pointKinematics(time,q,qp,SpringMat,SpringBodyMat,... modelFile,IKLabels) -% Load Library -import org.opensim.modeling.*; - -% Open a Model by name -osimModel = Model(modelFile); - -% Initialize the system and get the initial state -osimState = osimModel.initSystem(); - % Get the number of coords and markers numPts = size(time,1); numSprings = size(SpringMat,1); -refBodySet = osimModel.getBodySet; - % Split time points into parallel problems numWorkers = gcp().NumWorkers; SpringPosJobs = cell(1, numWorkers); SpringVelJobs = cell(1, numWorkers); parfor worker = 1:numWorkers + [SpringPosJobs{worker}, SpringVelJobs{worker}] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker); +end + +SpringPos = SpringPosJobs{1}; +SpringVel = SpringVelJobs{1}; +for job = 2 : numWorkers + SpringPos = cat(1, SpringPos, SpringPosJobs{job}); + SpringVel = cat(1, SpringVel, SpringVelJobs{job}); +end + +end + +function [SpringPosJob, SpringVelJob] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker) + + import org.opensim.modeling.*; + persistent osimModel; + persistent osimState; + persistent refBodySet; + if isempty(osimModel) + osimModel = Model(modelFile); + osimState = osimModel.initSystem(); + refBodySet = osimModel.getBodySet; + end + + SpringPosJob = []; + SpringVelJob = []; + indexOffset = (worker - 1) * ceil(numPts / numWorkers); for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) osimState.setTime(time(j,1)); + for k=1:size(IKLabels,2) if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); @@ -46,19 +63,10 @@ osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); for k=0:2 - SpringPosJobs{worker}(j,(i-1)*3+k+1)=tempGlobalPos.get(k); - SpringVelJobs{worker}(j,(i-1)*3+k+1)=tempGlobalVel.get(k); + SpringPosJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalPos.get(k); + SpringVelJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalVel.get(k); end end end end - -SpringPos = SpringPosJobs{1}; -SpringVel = SpringVelJobs{1}; -for job = 2 : numWorkers - SpringPos = cat(1, SpringPos, SpringPosJobs{job}); - SpringVel = cat(1, SpringVel, SpringVelJobs{job}); -end - -end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m index 27cee3f9a..219f34e9e 100644 --- a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m +++ b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m @@ -29,7 +29,9 @@ trackingCostTerms = [ ... "coordinate_tracking", ... "controller_tracking", ... - "inverse_dynamics_load_tracking" ... + "inverse_dynamics_load_tracking", ... + "external_force_tracking", ... + "external_moment_tracking" ... ]; output = false; for i = 1:length(trackingCostTerms) diff --git a/src/core/TreatmentOptimization/setupGroundContact.m b/src/core/TreatmentOptimization/setupGroundContact.m index a145119f6..06da6f396 100644 --- a/src/core/TreatmentOptimization/setupGroundContact.m +++ b/src/core/TreatmentOptimization/setupGroundContact.m @@ -29,8 +29,9 @@ for i = 1:length(inputs.contactSurfaces) midfootSuperiorLocation = pointKinematics(inputs.experimentalTime, ... inputs.experimentalJointAngles, inputs.experimentalJointVelocities, ... - inputs.contactSurfaces{i}.midfootSuperiorPointOnBody', ... - inputs.contactSurfaces{i}.midfootSuperiorBody, inputs.coordinateNames); + inputs.contactSurfaces{i}.midfootSuperiorPointOnBody, ... + inputs.contactSurfaces{i}.midfootSuperiorBody, inputs.mexModel, ... + inputs.coordinateNames); midfootSuperiorLocation(:, 2) = 0; inputs.contactSurfaces{i}.experimentalGroundReactionMoments = ... transferMoments(inputs.contactSurfaces{i}.electricalCenter, ... From fee56c0c13231d5bc69e2fdd37d9bc24f3b2bd38 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 24 May 2023 19:11:09 -0500 Subject: [PATCH 36/72] Fix coordinate constraint indexing --- .../PathTerms/calcTorqueActuatedMomentsPathConstraints.m | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m b/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m index dc640f3a1..698241494 100644 --- a/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m +++ b/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m @@ -28,9 +28,9 @@ function pathTerm = calcTorqueActuatedMomentsPathConstraints(params, ... phaseout, controlTorques, loadName) -indx1 = find(strcmp(convertCharsToStrings(params.inverseDynamicMomentLabels), ... - loadName)); -indx2 = find(strcmp(strcat(params.controlTorqueNames, ... - '_moment'), loadName)); +loadName = erase(loadName, '_moment'); +indx1 = find(cellfun(@isequal, params.coordinateNames, ... + repmat({loadName}, 1, length(params.coordinateNames)))); +indx2 = find(strcmp(params.controlTorqueNames, loadName)); pathTerm = phaseout.inverseDynamicMoments(:, indx1) - controlTorques(:, indx2); end \ No newline at end of file From 7580a30e580e32582183e013447fc7e4ec0a33ff Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 24 May 2023 19:33:58 -0500 Subject: [PATCH 37/72] Fix integrand for models with fewer IK columns than coordinates --- .../calcTrackingOptimizationIntegrand.m | 4 ++-- .../calcTrackingInverseDynamicLoadsIntegrand.m | 10 ++++++++-- .../calcTorqueActuatedMomentsPathConstraints.m | 1 + 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m index 7916c15ab..da2329145 100644 --- a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m +++ b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m @@ -42,12 +42,12 @@ calcTrackingInverseDynamicLoadsIntegrand(params, ... values.time, phaseout.inverseDynamicMoments, ... costTerm.load)); - case "external_force" + case "external_force_tracking" integrand = cat(2, integrand, ... calcTrackingExternalForcesIntegrand(params, ... phaseout.groundReactionsLab.forces, values.time, ... costTerm.force)); - case "external_moment" + case "external_moment_tracking" integrand = cat(2, integrand, ... calcTrackingExternalMomentsIntegrand(params, ... phaseout.groundReactionsLab.moments, values.time, ... diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m index 0a9fd79dd..925d12c9a 100644 --- a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m @@ -28,7 +28,9 @@ function cost = calcTrackingInverseDynamicLoadsIntegrand(params, time, ... inverseDynamicMoments, loadName) -indx = find(strcmp(convertCharsToStrings(params.inverseDynamicMomentLabels), ... +loadName = erase(loadName, '_moment'); +loadName = erase(loadName, '_force'); +indx = find(strcmp(convertCharsToStrings(params.coordinateNames), ... loadName)); if params.splineJointMoments.dim > 1 @@ -36,6 +38,10 @@ else experimentalJointMoments = fnval(params.splineJointMoments, time); end -cost = calcTrackingCostArrayTerm(experimentalJointMoments, ... + +momentLabelsNoSuffix = erase(params.inverseDynamicMomentLabels, '_moment'); +momentLabelsNoSuffix = erase(momentLabelsNoSuffix, '_force'); +includedJointMomentCols = ismember(momentLabelsNoSuffix, convertCharsToStrings(params.coordinateNames)); +cost = calcTrackingCostArrayTerm(experimentalJointMoments(:, includedJointMomentCols), ... inverseDynamicMoments, indx); end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m b/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m index 698241494..dbc14a437 100644 --- a/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m +++ b/src/core/TreatmentOptimization/PathTerms/calcTorqueActuatedMomentsPathConstraints.m @@ -29,6 +29,7 @@ phaseout, controlTorques, loadName) loadName = erase(loadName, '_moment'); +loadName = erase(loadName, '_force'); indx1 = find(cellfun(@isequal, params.coordinateNames, ... repmat({loadName}, 1, length(params.coordinateNames)))); indx2 = find(strcmp(params.controlTorqueNames, loadName)); From 19ca3478456d49cb27fb20a70b5af29a7c7c77d9 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 24 May 2023 19:37:59 -0500 Subject: [PATCH 38/72] Update terminal constraint type names --- .../calcTrackingOptimizationTerminalConstraint.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/TrackingOptimization/calcTrackingOptimizationTerminalConstraint.m b/src/TrackingOptimization/calcTrackingOptimizationTerminalConstraint.m index 3009ff0c9..0ab2bd362 100644 --- a/src/TrackingOptimization/calcTrackingOptimizationTerminalConstraint.m +++ b/src/TrackingOptimization/calcTrackingOptimizationTerminalConstraint.m @@ -57,13 +57,13 @@ modeledValues.inverseDynamicMoments, ... params.inverseDynamicMomentLabels, ... constraintTerm.load)); - case "external_force_periodicity" + case "external_force_tracking_periodicity" event = cat(2, event, ... calcExternalForcesPeriodicity(... modeledValues.groundReactionsLab.forces, ... params.contactSurfaces, ... constraintTerm.force)); - case "external_moment_periodicity" + case "external_moment_tracking_periodicity" event = cat(2, event, ... calcExternalMomentsPeriodicity(... modeledValues.groundReactionsLab.moments, ... From 7a1840ca537ffc3eb36f4ecf2e359bb69dafc51f Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 25 May 2023 17:17:40 -0500 Subject: [PATCH 39/72] Use mex pointKinematics and ID for Windows, Matlab parallel otherwise --- .../TrackingOptimization.m | 8 +- .../calcTorqueBasedModeledValues.m | 2 +- src/TrackingOptimization/inverseDynamics.m | 37 +++++++ .../inverseDynamicsMatlabParallel.m | 27 +++++ ...exw64 => inverseDynamicsMexWindows.mexw64} | Bin src/TrackingOptimization/pointKinematics.m | 101 ++++++------------ .../pointKinematicsMatlabParallel.m | 99 +++++++++++++++++ .../pointKinematicsMexWindows.mexw64 | Bin 0 -> 31744 bytes .../makeTreatmentOptimizationInputs.m | 6 +- 9 files changed, 205 insertions(+), 75 deletions(-) create mode 100644 src/TrackingOptimization/inverseDynamics.m rename src/TrackingOptimization/{inverseDynamics.mexw64 => inverseDynamicsMexWindows.mexw64} (100%) create mode 100644 src/TrackingOptimization/pointKinematicsMatlabParallel.m create mode 100644 src/TrackingOptimization/pointKinematicsMexWindows.mexw64 diff --git a/src/TrackingOptimization/TrackingOptimization.m b/src/TrackingOptimization/TrackingOptimization.m index 287d3094e..53c036fd4 100644 --- a/src/TrackingOptimization/TrackingOptimization.m +++ b/src/TrackingOptimization/TrackingOptimization.m @@ -27,9 +27,9 @@ function [output, inputs] = TrackingOptimization(inputs, params) inputs = makeTreatmentOptimizationInputs(inputs, params); -% pointKinematics(inputs.mexModel); -inverseDynamics(inputs.mexModel); -% inputs -gcp; +if isequal(mexext, 'mexw64') + pointKinematicsMexWindows(inputs.mexModel); + inverseDynamicsMexWindows(inputs.mexModel); +end output = computeTrackingOptimizationMainFunction(inputs, params); end diff --git a/src/TrackingOptimization/calcTorqueBasedModeledValues.m b/src/TrackingOptimization/calcTorqueBasedModeledValues.m index 8582af1e7..82b2dfbcf 100644 --- a/src/TrackingOptimization/calcTorqueBasedModeledValues.m +++ b/src/TrackingOptimization/calcTorqueBasedModeledValues.m @@ -41,7 +41,7 @@ appliedLoads = [appliedLoads groundReactionsBody]; end clear inverseDynamicsMatlabParallel -phaseout.inverseDynamicMoments = inverseDynamicsMatlabParallel(values.time, ... +phaseout.inverseDynamicMoments = inverseDynamics(values.time, ... values.statePositions, values.stateVelocities, ... values.stateAccelerations, params.coordinateNames, appliedLoads, params.mexModel); end diff --git a/src/TrackingOptimization/inverseDynamics.m b/src/TrackingOptimization/inverseDynamics.m new file mode 100644 index 000000000..8f2042343 --- /dev/null +++ b/src/TrackingOptimization/inverseDynamics.m @@ -0,0 +1,37 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function IDLoads = inverseDynamics(time,q,qp,qpp,IKLabels,AppliedLoads, ... + modelFile) +if isequal(mexext, 'mexw64') + IDLoads = inverseDynamicsMexWindows(time,q,qp,qpp,IKLabels, ... + AppliedLoads); +else + IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels, ... + AppliedLoads,modelFile); +end +end diff --git a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m index 09bd93453..af422fd26 100644 --- a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m +++ b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m @@ -1,3 +1,30 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + function IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) % Get the number of coords and markers diff --git a/src/TrackingOptimization/inverseDynamics.mexw64 b/src/TrackingOptimization/inverseDynamicsMexWindows.mexw64 similarity index 100% rename from src/TrackingOptimization/inverseDynamics.mexw64 rename to src/TrackingOptimization/inverseDynamicsMexWindows.mexw64 diff --git a/src/TrackingOptimization/pointKinematics.m b/src/TrackingOptimization/pointKinematics.m index 6df66c192..9255561a0 100644 --- a/src/TrackingOptimization/pointKinematics.m +++ b/src/TrackingOptimization/pointKinematics.m @@ -1,72 +1,37 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + function [SpringPos, SpringVel] = pointKinematics(time,q,qp,SpringMat,SpringBodyMat,... modelFile,IKLabels) - -% Get the number of coords and markers -numPts = size(time,1); -numSprings = size(SpringMat,1); - -% Split time points into parallel problems -numWorkers = gcp().NumWorkers; -SpringPosJobs = cell(1, numWorkers); -SpringVelJobs = cell(1, numWorkers); - -parfor worker = 1:numWorkers - [SpringPosJobs{worker}, SpringVelJobs{worker}] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker); +if isequal(mexext, 'mexw64') + [SpringPos, SpringVel] = pointKinematicsMexWindows(time,q,qp, ... + SpringMat,SpringBodyMat,IKLabels); +else + [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp, ... + SpringMat,SpringBodyMat,modelFile,IKLabels); end - -SpringPos = SpringPosJobs{1}; -SpringVel = SpringVelJobs{1}; -for job = 2 : numWorkers - SpringPos = cat(1, SpringPos, SpringPosJobs{job}); - SpringVel = cat(1, SpringVel, SpringVelJobs{job}); -end - -end - -function [SpringPosJob, SpringVelJob] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker) - - import org.opensim.modeling.*; - persistent osimModel; - persistent osimState; - persistent refBodySet; - if isempty(osimModel) - osimModel = Model(modelFile); - osimState = osimModel.initSystem(); - refBodySet = osimModel.getBodySet; - end - - SpringPosJob = []; - SpringVelJob = []; - indexOffset = (worker - 1) * ceil(numPts / numWorkers); - - for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) - osimState.setTime(time(j,1)); - - for k=1:size(IKLabels,2) - if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked - osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); - osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); - end - end - osimModel.realizeVelocity(osimState); - - for i=1:numSprings - tempRefParentBody = refBodySet.get(SpringBodyMat(i)); - tempLocalPos = Vec3(3,0); - tempGlobalPos = Vec3(3,0); - tempGlobalVel = Vec3(3,0); - tempLocalPos.set(0,SpringMat(i,1)); - tempLocalPos.set(1,SpringMat(i,2)); - tempLocalPos.set(2,SpringMat(i,3)); - - osimModel.getSimbodyEngine.getPosition(osimState,tempRefParentBody,tempLocalPos,tempGlobalPos); - osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); - - for k=0:2 - SpringPosJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalPos.get(k); - SpringVelJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalVel.get(k); - end - end - end - end diff --git a/src/TrackingOptimization/pointKinematicsMatlabParallel.m b/src/TrackingOptimization/pointKinematicsMatlabParallel.m new file mode 100644 index 000000000..063b961df --- /dev/null +++ b/src/TrackingOptimization/pointKinematicsMatlabParallel.m @@ -0,0 +1,99 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp,SpringMat,SpringBodyMat,... + modelFile,IKLabels) + +% Get the number of coords and markers +numPts = size(time,1); +numSprings = size(SpringMat,1); + +% Split time points into parallel problems +numWorkers = gcp().NumWorkers; +SpringPosJobs = cell(1, numWorkers); +SpringVelJobs = cell(1, numWorkers); + +parfor worker = 1:numWorkers + [SpringPosJobs{worker}, SpringVelJobs{worker}] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker); +end + +SpringPos = SpringPosJobs{1}; +SpringVel = SpringVelJobs{1}; +for job = 2 : numWorkers + SpringPos = cat(1, SpringPos, SpringPosJobs{job}); + SpringVel = cat(1, SpringVel, SpringVelJobs{job}); +end + +end + +function [SpringPosJob, SpringVelJob] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker) + + import org.opensim.modeling.*; + persistent osimModel; + persistent osimState; + persistent refBodySet; + if isempty(osimModel) + osimModel = Model(modelFile); + osimState = osimModel.initSystem(); + refBodySet = osimModel.getBodySet; + end + + SpringPosJob = []; + SpringVelJob = []; + indexOffset = (worker - 1) * ceil(numPts / numWorkers); + + for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) + osimState.setTime(time(j,1)); + + for k=1:size(IKLabels,2) + if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked + osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); + osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); + end + end + osimModel.realizeVelocity(osimState); + + for i=1:numSprings + tempRefParentBody = refBodySet.get(SpringBodyMat(i)); + tempLocalPos = Vec3(3,0); + tempGlobalPos = Vec3(3,0); + tempGlobalVel = Vec3(3,0); + tempLocalPos.set(0,SpringMat(i,1)); + tempLocalPos.set(1,SpringMat(i,2)); + tempLocalPos.set(2,SpringMat(i,3)); + + osimModel.getSimbodyEngine.getPosition(osimState,tempRefParentBody,tempLocalPos,tempGlobalPos); + osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); + + for k=0:2 + SpringPosJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalPos.get(k); + SpringVelJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalVel.get(k); + end + end + end + +end diff --git a/src/TrackingOptimization/pointKinematicsMexWindows.mexw64 b/src/TrackingOptimization/pointKinematicsMexWindows.mexw64 new file mode 100644 index 0000000000000000000000000000000000000000..b8ca6b73ff4ebabcec6ebb4571420156aaf6c7dd GIT binary patch literal 31744 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 tZ_7Xn`?~4t Date: Thu, 25 May 2023 17:20:42 -0500 Subject: [PATCH 40/72] Move pointKinematics, ID to core treatment optimization folder --- .../TreatmentOptimization}/inverseDynamics.m | 0 .../TreatmentOptimization}/inverseDynamicsMatlab.m | 0 .../inverseDynamicsMatlabParallel.m | 0 .../inverseDynamicsMexWindows.mexw64 | Bin .../TreatmentOptimization}/pointKinematics.m | 0 .../pointKinematicsMatlabParallel.m | 0 .../pointKinematicsMexWindows.mexw64 | Bin 7 files changed, 0 insertions(+), 0 deletions(-) rename src/{TrackingOptimization => core/TreatmentOptimization}/inverseDynamics.m (100%) rename src/{TrackingOptimization => core/TreatmentOptimization}/inverseDynamicsMatlab.m (100%) rename src/{TrackingOptimization => core/TreatmentOptimization}/inverseDynamicsMatlabParallel.m (100%) rename src/{TrackingOptimization => core/TreatmentOptimization}/inverseDynamicsMexWindows.mexw64 (100%) rename src/{TrackingOptimization => core/TreatmentOptimization}/pointKinematics.m (100%) rename src/{TrackingOptimization => core/TreatmentOptimization}/pointKinematicsMatlabParallel.m (100%) rename src/{TrackingOptimization => core/TreatmentOptimization}/pointKinematicsMexWindows.mexw64 (100%) diff --git a/src/TrackingOptimization/inverseDynamics.m b/src/core/TreatmentOptimization/inverseDynamics.m similarity index 100% rename from src/TrackingOptimization/inverseDynamics.m rename to src/core/TreatmentOptimization/inverseDynamics.m diff --git a/src/TrackingOptimization/inverseDynamicsMatlab.m b/src/core/TreatmentOptimization/inverseDynamicsMatlab.m similarity index 100% rename from src/TrackingOptimization/inverseDynamicsMatlab.m rename to src/core/TreatmentOptimization/inverseDynamicsMatlab.m diff --git a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m b/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m similarity index 100% rename from src/TrackingOptimization/inverseDynamicsMatlabParallel.m rename to src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m diff --git a/src/TrackingOptimization/inverseDynamicsMexWindows.mexw64 b/src/core/TreatmentOptimization/inverseDynamicsMexWindows.mexw64 similarity index 100% rename from src/TrackingOptimization/inverseDynamicsMexWindows.mexw64 rename to src/core/TreatmentOptimization/inverseDynamicsMexWindows.mexw64 diff --git a/src/TrackingOptimization/pointKinematics.m b/src/core/TreatmentOptimization/pointKinematics.m similarity index 100% rename from src/TrackingOptimization/pointKinematics.m rename to src/core/TreatmentOptimization/pointKinematics.m diff --git a/src/TrackingOptimization/pointKinematicsMatlabParallel.m b/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m similarity index 100% rename from src/TrackingOptimization/pointKinematicsMatlabParallel.m rename to src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m diff --git a/src/TrackingOptimization/pointKinematicsMexWindows.mexw64 b/src/core/TreatmentOptimization/pointKinematicsMexWindows.mexw64 similarity index 100% rename from src/TrackingOptimization/pointKinematicsMexWindows.mexw64 rename to src/core/TreatmentOptimization/pointKinematicsMexWindows.mexw64 From 810f5f9ee086be2a42922ab53a975ca365182012 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 25 May 2023 17:30:42 -0500 Subject: [PATCH 41/72] Preallocate Matlab implementation outputs for speed --- .../inverseDynamicsMatlabParallel.m | 115 ++++++++++++++++++ .../pointKinematicsMatlabParallel.m | 99 +++++++++++++++ 2 files changed, 214 insertions(+) create mode 100644 src/TrackingOptimization/inverseDynamicsMatlabParallel.m create mode 100644 src/TrackingOptimization/pointKinematicsMatlabParallel.m diff --git a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m new file mode 100644 index 000000000..8ced4df1a --- /dev/null +++ b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m @@ -0,0 +1,115 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) + +% Get the number of coords and markers +numPts = size(time,1); +numControls = size(AppliedLoads,2); +numCoords = length(IKLabels); + +% Split time points into parallel problems +numWorkers = gcp().NumWorkers; +IDLoadsJobs = cell(1, numWorkers); +AccelsTempVec = cell(1, numWorkers); + +parfor worker = 1:numWorkers + IDLoadsJobs{worker} = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec{worker},time,q,qp,qpp,IKLabels,AppliedLoads,worker); +end + +IDLoads = IDLoadsJobs{1}; +for job = 2 : numWorkers + IDLoads = cat(1, IDLoads, IDLoadsJobs{job}); +end + +end + +function IDLoadsJob = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec,time,q,qp,qpp,IKLabels,AppliedLoads,worker) + + import org.opensim.modeling.*; + persistent osimModel; + persistent osimState; + persistent idSolver; + if isempty(osimModel) + osimModel = Model(modelFile); + osimState = osimModel.initSystem(); + idSolver = InverseDynamicsSolver(osimModel); + end + + IDLoadsJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numCoords); + + indexOffset = (worker - 1) * ceil(numPts / numWorkers); + + for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) + osimState.setTime(time(j,1)); + for k=1:size(IKLabels,2) + if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked + osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); + osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); + end + end + osimModel.realizeVelocity(osimState); + + for i=1:osimState.getNQ + StateQ = osimState.getQ.get(i-1); + + for ii = 1:size(q,2) + if abs(q(j,ii)-StateQ) <= 1e-6 + AccelsTempVec(j-indexOffset,i) = qpp(j,ii); + end + end + end + + newControls = Vector(numControls,0); + + for i=0:numControls-1 + newControls.set(i,AppliedLoads(j,i+1)); + end + + osimModel.setControls(osimState,newControls); + osimModel.markControlsAsValid(osimState); + osimModel.realizeDynamics(osimState); + + AccelsVec = Vector(osimState.getNQ,0); + + includedQIndex = 1; + for i=0:osimState.getNQ-1 + currentCoordinate = osimModel.getCoordinateSet().get(i).getName().toCharArray'; + if any(cellfun(@isequal, IKLabels, repmat({currentCoordinate}, 1, length(IKLabels)))) + AccelsVec.set(i,AccelsTempVec(j-indexOffset,includedQIndex)); + includedQIndex = includedQIndex + 1; + end + end + + IDLoadsVec = idSolver.solve(osimState,AccelsVec); + + for i=0:numCoords-1 + IDLoadsJob(j-indexOffset,i+1) = IDLoadsVec.get(i); + end + end + +end diff --git a/src/TrackingOptimization/pointKinematicsMatlabParallel.m b/src/TrackingOptimization/pointKinematicsMatlabParallel.m new file mode 100644 index 000000000..1a33263ab --- /dev/null +++ b/src/TrackingOptimization/pointKinematicsMatlabParallel.m @@ -0,0 +1,99 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp,SpringMat,SpringBodyMat,... + modelFile,IKLabels) + +% Get the number of coords and markers +numPts = size(time,1); +numSprings = size(SpringMat,1); + +% Split time points into parallel problems +numWorkers = gcp().NumWorkers; +SpringPosJobs = cell(1, numWorkers); +SpringVelJobs = cell(1, numWorkers); + +parfor worker = 1:numWorkers + [SpringPosJobs{worker}, SpringVelJobs{worker}] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker); +end + +SpringPos = SpringPosJobs{1}; +SpringVel = SpringVelJobs{1}; +for job = 2 : numWorkers + SpringPos = cat(1, SpringPos, SpringPosJobs{job}); + SpringVel = cat(1, SpringVel, SpringVelJobs{job}); +end + +end + +function [SpringPosJob, SpringVelJob] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker) + + import org.opensim.modeling.*; + persistent osimModel; + persistent osimState; + persistent refBodySet; + if isempty(osimModel) + osimModel = Model(modelFile); + osimState = osimModel.initSystem(); + refBodySet = osimModel.getBodySet; + end + + SpringPosJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); + SpringVelJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); + indexOffset = (worker - 1) * ceil(numPts / numWorkers); + + for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) + osimState.setTime(time(j,1)); + + for k=1:size(IKLabels,2) + if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked + osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); + osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); + end + end + osimModel.realizeVelocity(osimState); + + for i=1:numSprings + tempRefParentBody = refBodySet.get(SpringBodyMat(i)); + tempLocalPos = Vec3(3,0); + tempGlobalPos = Vec3(3,0); + tempGlobalVel = Vec3(3,0); + tempLocalPos.set(0,SpringMat(i,1)); + tempLocalPos.set(1,SpringMat(i,2)); + tempLocalPos.set(2,SpringMat(i,3)); + + osimModel.getSimbodyEngine.getPosition(osimState,tempRefParentBody,tempLocalPos,tempGlobalPos); + osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); + + for k=0:2 + SpringPosJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalPos.get(k); + SpringVelJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalVel.get(k); + end + end + end + +end From 47dff56c18d63e2fcc3fb9e773bc1ca003ad1e63 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 25 May 2023 18:01:46 -0500 Subject: [PATCH 42/72] Account for mex/Matlab switch in integrand --- .../inverseDynamicsMatlabParallel.m | 115 ------------------ .../pointKinematicsMatlabParallel.m | 99 --------------- ...calcTrackingInverseDynamicLoadsIntegrand.m | 5 +- .../TreatmentOptimization/pointKinematics.m | 2 +- 4 files changed, 5 insertions(+), 216 deletions(-) delete mode 100644 src/TrackingOptimization/inverseDynamicsMatlabParallel.m delete mode 100644 src/TrackingOptimization/pointKinematicsMatlabParallel.m diff --git a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m b/src/TrackingOptimization/inverseDynamicsMatlabParallel.m deleted file mode 100644 index 8ced4df1a..000000000 --- a/src/TrackingOptimization/inverseDynamicsMatlabParallel.m +++ /dev/null @@ -1,115 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Spencer Williams % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) - -% Get the number of coords and markers -numPts = size(time,1); -numControls = size(AppliedLoads,2); -numCoords = length(IKLabels); - -% Split time points into parallel problems -numWorkers = gcp().NumWorkers; -IDLoadsJobs = cell(1, numWorkers); -AccelsTempVec = cell(1, numWorkers); - -parfor worker = 1:numWorkers - IDLoadsJobs{worker} = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec{worker},time,q,qp,qpp,IKLabels,AppliedLoads,worker); -end - -IDLoads = IDLoadsJobs{1}; -for job = 2 : numWorkers - IDLoads = cat(1, IDLoads, IDLoadsJobs{job}); -end - -end - -function IDLoadsJob = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec,time,q,qp,qpp,IKLabels,AppliedLoads,worker) - - import org.opensim.modeling.*; - persistent osimModel; - persistent osimState; - persistent idSolver; - if isempty(osimModel) - osimModel = Model(modelFile); - osimState = osimModel.initSystem(); - idSolver = InverseDynamicsSolver(osimModel); - end - - IDLoadsJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numCoords); - - indexOffset = (worker - 1) * ceil(numPts / numWorkers); - - for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) - osimState.setTime(time(j,1)); - for k=1:size(IKLabels,2) - if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked - osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); - osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); - end - end - osimModel.realizeVelocity(osimState); - - for i=1:osimState.getNQ - StateQ = osimState.getQ.get(i-1); - - for ii = 1:size(q,2) - if abs(q(j,ii)-StateQ) <= 1e-6 - AccelsTempVec(j-indexOffset,i) = qpp(j,ii); - end - end - end - - newControls = Vector(numControls,0); - - for i=0:numControls-1 - newControls.set(i,AppliedLoads(j,i+1)); - end - - osimModel.setControls(osimState,newControls); - osimModel.markControlsAsValid(osimState); - osimModel.realizeDynamics(osimState); - - AccelsVec = Vector(osimState.getNQ,0); - - includedQIndex = 1; - for i=0:osimState.getNQ-1 - currentCoordinate = osimModel.getCoordinateSet().get(i).getName().toCharArray'; - if any(cellfun(@isequal, IKLabels, repmat({currentCoordinate}, 1, length(IKLabels)))) - AccelsVec.set(i,AccelsTempVec(j-indexOffset,includedQIndex)); - includedQIndex = includedQIndex + 1; - end - end - - IDLoadsVec = idSolver.solve(osimState,AccelsVec); - - for i=0:numCoords-1 - IDLoadsJob(j-indexOffset,i+1) = IDLoadsVec.get(i); - end - end - -end diff --git a/src/TrackingOptimization/pointKinematicsMatlabParallel.m b/src/TrackingOptimization/pointKinematicsMatlabParallel.m deleted file mode 100644 index 1a33263ab..000000000 --- a/src/TrackingOptimization/pointKinematicsMatlabParallel.m +++ /dev/null @@ -1,99 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Spencer Williams % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp,SpringMat,SpringBodyMat,... - modelFile,IKLabels) - -% Get the number of coords and markers -numPts = size(time,1); -numSprings = size(SpringMat,1); - -% Split time points into parallel problems -numWorkers = gcp().NumWorkers; -SpringPosJobs = cell(1, numWorkers); -SpringVelJobs = cell(1, numWorkers); - -parfor worker = 1:numWorkers - [SpringPosJobs{worker}, SpringVelJobs{worker}] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker); -end - -SpringPos = SpringPosJobs{1}; -SpringVel = SpringVelJobs{1}; -for job = 2 : numWorkers - SpringPos = cat(1, SpringPos, SpringPosJobs{job}); - SpringVel = cat(1, SpringVel, SpringVelJobs{job}); -end - -end - -function [SpringPosJob, SpringVelJob] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker) - - import org.opensim.modeling.*; - persistent osimModel; - persistent osimState; - persistent refBodySet; - if isempty(osimModel) - osimModel = Model(modelFile); - osimState = osimModel.initSystem(); - refBodySet = osimModel.getBodySet; - end - - SpringPosJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); - SpringVelJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); - indexOffset = (worker - 1) * ceil(numPts / numWorkers); - - for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) - osimState.setTime(time(j,1)); - - for k=1:size(IKLabels,2) - if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked - osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); - osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); - end - end - osimModel.realizeVelocity(osimState); - - for i=1:numSprings - tempRefParentBody = refBodySet.get(SpringBodyMat(i)); - tempLocalPos = Vec3(3,0); - tempGlobalPos = Vec3(3,0); - tempGlobalVel = Vec3(3,0); - tempLocalPos.set(0,SpringMat(i,1)); - tempLocalPos.set(1,SpringMat(i,2)); - tempLocalPos.set(2,SpringMat(i,3)); - - osimModel.getSimbodyEngine.getPosition(osimState,tempRefParentBody,tempLocalPos,tempGlobalPos); - osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); - - for k=0:2 - SpringPosJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalPos.get(k); - SpringVelJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalVel.get(k); - end - end - end - -end diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m index 925d12c9a..24a90028d 100644 --- a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingInverseDynamicLoadsIntegrand.m @@ -42,6 +42,9 @@ momentLabelsNoSuffix = erase(params.inverseDynamicMomentLabels, '_moment'); momentLabelsNoSuffix = erase(momentLabelsNoSuffix, '_force'); includedJointMomentCols = ismember(momentLabelsNoSuffix, convertCharsToStrings(params.coordinateNames)); -cost = calcTrackingCostArrayTerm(experimentalJointMoments(:, includedJointMomentCols), ... +if ~isequal(mexext, 'mexw64') + experimentalJointMoments = experimentalJointMoments(:, includedJointMomentCols); +end +cost = calcTrackingCostArrayTerm(experimentalJointMoments, ... inverseDynamicMoments, indx); end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/pointKinematics.m b/src/core/TreatmentOptimization/pointKinematics.m index 9255561a0..848b77450 100644 --- a/src/core/TreatmentOptimization/pointKinematics.m +++ b/src/core/TreatmentOptimization/pointKinematics.m @@ -29,7 +29,7 @@ modelFile,IKLabels) if isequal(mexext, 'mexw64') [SpringPos, SpringVel] = pointKinematicsMexWindows(time,q,qp, ... - SpringMat,SpringBodyMat,IKLabels); + SpringMat',SpringBodyMat,IKLabels); else [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp, ... SpringMat,SpringBodyMat,modelFile,IKLabels); From d99dd2081b593dca25000e44c4d659061e307b05 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Fri, 26 May 2023 12:45:33 -0500 Subject: [PATCH 43/72] Move ID/point kinematics persistent clear for speed --- src/TrackingOptimization/TrackingOptimization.m | 2 ++ src/TrackingOptimization/calcTorqueBasedModeledValues.m | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/TrackingOptimization/TrackingOptimization.m b/src/TrackingOptimization/TrackingOptimization.m index 53c036fd4..714d496cc 100644 --- a/src/TrackingOptimization/TrackingOptimization.m +++ b/src/TrackingOptimization/TrackingOptimization.m @@ -31,5 +31,7 @@ pointKinematicsMexWindows(inputs.mexModel); inverseDynamicsMexWindows(inputs.mexModel); end +clear inverseDynamicsMatlabParallel +clear pointKinematicsMatlabParallel output = computeTrackingOptimizationMainFunction(inputs, params); end diff --git a/src/TrackingOptimization/calcTorqueBasedModeledValues.m b/src/TrackingOptimization/calcTorqueBasedModeledValues.m index 82b2dfbcf..1c7d9548e 100644 --- a/src/TrackingOptimization/calcTorqueBasedModeledValues.m +++ b/src/TrackingOptimization/calcTorqueBasedModeledValues.m @@ -40,7 +40,6 @@ phaseout.groundReactionsLab = calcGroundReactionsLab(groundReactions); appliedLoads = [appliedLoads groundReactionsBody]; end -clear inverseDynamicsMatlabParallel phaseout.inverseDynamicMoments = inverseDynamics(values.time, ... values.statePositions, values.stateVelocities, ... values.stateAccelerations, params.coordinateNames, appliedLoads, params.mexModel); From 299baf9ca5feb395c47e3d9a1886b71f1e6a3a2b Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Fri, 26 May 2023 12:58:47 -0500 Subject: [PATCH 44/72] Initialize mex and Matlab parallel for TO, VO, DO --- src/DesignOptimization/DesignOptimization.m | 1 + .../TrackingOptimization.m | 7 +--- .../VerificationOptimization.m | 1 + .../initializeMexOrMatlabParallelFunctions.m | 35 +++++++++++++++++++ 4 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 src/core/TreatmentOptimization/initializeMexOrMatlabParallelFunctions.m diff --git a/src/DesignOptimization/DesignOptimization.m b/src/DesignOptimization/DesignOptimization.m index 23ebb3d64..82357d72c 100644 --- a/src/DesignOptimization/DesignOptimization.m +++ b/src/DesignOptimization/DesignOptimization.m @@ -27,6 +27,7 @@ function [output, inputs] = DesignOptimization(inputs, params) inputs = makeTreatmentOptimizationInputs(inputs, params); +initializeMexOrMatlabParallelFunctions(inputs.mexModel); if strcmp(inputs.controllerType, 'synergy_driven') inputs = setupMuscleSynergies(inputs); end diff --git a/src/TrackingOptimization/TrackingOptimization.m b/src/TrackingOptimization/TrackingOptimization.m index 714d496cc..46f06dea7 100644 --- a/src/TrackingOptimization/TrackingOptimization.m +++ b/src/TrackingOptimization/TrackingOptimization.m @@ -27,11 +27,6 @@ function [output, inputs] = TrackingOptimization(inputs, params) inputs = makeTreatmentOptimizationInputs(inputs, params); -if isequal(mexext, 'mexw64') - pointKinematicsMexWindows(inputs.mexModel); - inverseDynamicsMexWindows(inputs.mexModel); -end -clear inverseDynamicsMatlabParallel -clear pointKinematicsMatlabParallel +initializeMexOrMatlabParallelFunctions(inputs.mexModel); output = computeTrackingOptimizationMainFunction(inputs, params); end diff --git a/src/VerificationOptimization/VerificationOptimization.m b/src/VerificationOptimization/VerificationOptimization.m index f88f39073..f8510cc61 100644 --- a/src/VerificationOptimization/VerificationOptimization.m +++ b/src/VerificationOptimization/VerificationOptimization.m @@ -27,6 +27,7 @@ function [output, inputs] = VerificationOptimization(inputs, params) inputs = makeTreatmentOptimizationInputs(inputs, params); +initializeMexOrMatlabParallelFunctions(inputs.mexModel); if strcmp(inputs.controllerType, 'synergy_driven') inputs = setupMuscleSynergies(inputs); end diff --git a/src/core/TreatmentOptimization/initializeMexOrMatlabParallelFunctions.m b/src/core/TreatmentOptimization/initializeMexOrMatlabParallelFunctions.m new file mode 100644 index 000000000..5fc71ad85 --- /dev/null +++ b/src/core/TreatmentOptimization/initializeMexOrMatlabParallelFunctions.m @@ -0,0 +1,35 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Spencer Williams % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function initializeMexOrMatlabParallelFunctions(modelFile) +if isequal(mexext, 'mexw64') + pointKinematicsMexWindows(modelFile); + inverseDynamicsMexWindows(modelFile); +end +clear inverseDynamicsMatlabParallel +clear pointKinematicsMatlabParallel +end From 8048b7a2b2a136d04b49728cebe2e652191329c1 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Fri, 26 May 2023 13:18:18 -0500 Subject: [PATCH 45/72] Preallocation for speed --- .../TreatmentOptimization/inverseDynamicsMatlabParallel.m | 2 +- .../TreatmentOptimization/pointKinematicsMatlabParallel.m | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m b/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m index af422fd26..8ced4df1a 100644 --- a/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m +++ b/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m @@ -60,7 +60,7 @@ idSolver = InverseDynamicsSolver(osimModel); end - IDLoadsJob = []; + IDLoadsJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numCoords); indexOffset = (worker - 1) * ceil(numPts / numWorkers); diff --git a/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m b/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m index 063b961df..1a33263ab 100644 --- a/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m +++ b/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m @@ -62,8 +62,8 @@ refBodySet = osimModel.getBodySet; end - SpringPosJob = []; - SpringVelJob = []; + SpringPosJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); + SpringVelJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); indexOffset = (worker - 1) * ceil(numPts / numWorkers); for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) From 63694b17b49186973fd794a46b8056f012c94337 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Mon, 29 May 2023 23:45:23 -0500 Subject: [PATCH 46/72] updated VO and TO cost terms --- .../calcDesignOptimizationDiscreteObjective.m | 35 +++---------- .../calcDesignOptimizationIntegrand.m | 19 +------ ...omputeDesignOptimizationEndpointFunction.m | 5 +- .../parseDesignOptimizationSettingsTree.m | 1 + .../calcTrackingOptimizationIntegrand.m | 50 +++--------------- ...teTrackingOptimizationContinuousFunction.m | 3 +- .../parseTrackingOptimizationSettingsTree.m | 1 + .../calcVerificationOptimizationIntegrand.m | 34 +++--------- ...rificationOptimizationContinuousFunction.m | 4 +- ...arseVerificationOptimizationSettingsTree.m | 1 + .../SetupBounds/getIntegralBounds.m | 4 +- ...Term.m => calcTreatmentOptimizationCost.m} | 30 +++++++---- .../costTerms/isTrackingCostTerm.m | 42 --------------- .../generateCostTermStruct.m | 52 ++++++++++++++++--- 14 files changed, 99 insertions(+), 182 deletions(-) rename src/core/TreatmentOptimization/{costTerms/isMinimizationCostTerm.m => calcTreatmentOptimizationCost.m} (67%) delete mode 100644 src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m diff --git a/src/DesignOptimization/calcDesignOptimizationDiscreteObjective.m b/src/DesignOptimization/calcDesignOptimizationDiscreteObjective.m index 1e3a2be7c..36d38ae1c 100644 --- a/src/DesignOptimization/calcDesignOptimizationDiscreteObjective.m +++ b/src/DesignOptimization/calcDesignOptimizationDiscreteObjective.m @@ -25,34 +25,11 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function discrete = calcDesignOptimizationDiscreteObjective(values, params) +function discrete = calcDesignOptimizationDiscreteObjective(values, ... + modeledValues, auxdata) -discrete = []; -if isfield(params, 'discrete') - for i = 1:length(params.discrete.tracking) - costTerm = params.discrete.tracking{i}; - if costTerm.isEnabled - switch costTerm.type - case "synergy_vectors" - discrete = cat(2, discrete, ... - calcTrackingCoordinateIntegrand(params, ... - values.time, values.statePositions, ... - costTerm.coordinate)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end - end - for i = 1:length(params.discrete.minimizing) - costTerm = params.discrete.minimizing{i}; - if costTerm.isEnabled - switch costTerm.type - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end - end -end +[costTermCalculations, allowedTypes] = ... + generateCostTermStruct("discrete", "DesignOptimization"); +discrete = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata); end \ No newline at end of file diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index 58a2c1010..fb9de4dcc 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -27,25 +27,10 @@ function integrand = calcDesignOptimizationIntegrand(values, ... modeledValues, auxdata) -integrand = []; [costTermCalculations, allowedTypes] = ... generateCostTermStruct("continuous", "DesignOptimization"); - -for i = 1:length(auxdata.costTerms) - costTerm = auxdata.costTerms{i}; - if costTerm.isEnabled - if isfield(costTermCalculations, costTerm.type) && ... - any(ismember(allowedTypes, costTerm.type)) - fn = costTermCalculations.(costTerm.type); - integrand = cat(2, ... - integrand, ... - fn(values, modeledValues, auxdata, costTerm)); - else - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end +integrand = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata); integrand = scaleToBounds(integrand, auxdata.maxIntegral, auxdata.minIntegral); integrand = integrand .^ 2; end diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index 58fecf64d..4eb27a30b 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -43,8 +43,9 @@ output.eventgroup.event = calcDesignOptimizationTerminalConstraint( ... values, modeledValues, inputs.auxdata); end -% discrete = calcDesignOptimizationDiscreteObjective(values, inputs.auxdata); -discrete = computeStaticParameterCost(inputs); +discrete = calcDesignOptimizationDiscreteObjective(values, ... + modeledValues, inputs.auxdata); +% discrete = computeStaticParameterCost(inputs); output.objective = calcDesignOptimizationObjective(discrete, ... inputs.phase.integral); end diff --git a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m index e8b10c705..7ad17c1a9 100644 --- a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m +++ b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m @@ -88,6 +88,7 @@ inputs.maxControlTorquesMultiple = getDoubleFromField(maxControlTorques); end end +inputs.toolName = "DesignOptimization"; end function params = getParams(tree) diff --git a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m index 7916c15ab..66efa6ece 100644 --- a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m +++ b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m @@ -25,48 +25,12 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function integrand = calcTrackingOptimizationIntegrand(values, params, ... - phaseout) -integrand = []; -for i = 1:length(params.costTerms) - costTerm = params.costTerms{i}; - if costTerm.isEnabled - switch costTerm.type - case "coordinate_tracking" - integrand = cat(2, integrand, ... - calcTrackingCoordinateIntegrand(params, ... - values.time, values.statePositions, ... - costTerm.coordinate)); - case "inverse_dynamics_load_tracking" - integrand = cat(2, integrand, ... - calcTrackingInverseDynamicLoadsIntegrand(params, ... - values.time, phaseout.inverseDynamicMoments, ... - costTerm.load)); - case "external_force" - integrand = cat(2, integrand, ... - calcTrackingExternalForcesIntegrand(params, ... - phaseout.groundReactionsLab.forces, values.time, ... - costTerm.force)); - case "external_moment" - integrand = cat(2, integrand, ... - calcTrackingExternalMomentsIntegrand(params, ... - phaseout.groundReactionsLab.moments, values.time, ... - costTerm.moment)); - case "muscle_activation" - integrand = cat(2, integrand, ... - calcTrackingMuscleActivationIntegrand( ... - phaseout.muscleActivations, ... - values.time, params, costTerm.muscle)); - case "joint_jerk_minimization" - integrand = cat(2, integrand, ... - calcMinimizingJointJerkIntegrand(values.controlJerks, ... - params, costTerm.coordinate)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end -integrand = integrand ./ (params.maxIntegral - params.minIntegral); +function integrand = calcTrackingOptimizationIntegrand(values, ... + modeledValues, auxdata) +[costTermCalculations, allowedTypes] = ... + generateCostTermStruct("continuous", "TrackingOptimization"); +integrand = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata); +integrand = integrand ./ (auxdata.maxIntegral - auxdata.minIntegral); integrand = integrand .^ 2; end \ No newline at end of file diff --git a/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m b/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m index 8a7098684..6eaacbbef 100644 --- a/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m +++ b/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m @@ -31,6 +31,5 @@ phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); phaseout.dynamics = calcTrackingOptimizationDynamicsConstraint(values, inputs.auxdata); phaseout.path = calcTrackingOptimizationPathConstraint(values, phaseout, inputs.auxdata); -phaseout.integrand = calcTrackingOptimizationIntegrand(values, inputs.auxdata, ... - phaseout); +phaseout.integrand = calcTrackingOptimizationIntegrand(values, phaseout, inputs.auxdata); end \ No newline at end of file diff --git a/src/TrackingOptimization/parseTrackingOptimizationSettingsTree.m b/src/TrackingOptimization/parseTrackingOptimizationSettingsTree.m index fa0683977..af01d9007 100644 --- a/src/TrackingOptimization/parseTrackingOptimizationSettingsTree.m +++ b/src/TrackingOptimization/parseTrackingOptimizationSettingsTree.m @@ -77,6 +77,7 @@ inputs.maxControlTorquesMultiple = getDoubleFromField(maxControlTorques); end end +inputs.toolName = "TrackingOptimization"; end function params = getParams(tree) diff --git a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m index 30adf244b..7ead230de 100644 --- a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m +++ b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m @@ -25,32 +25,12 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function integrand = calcVerificationOptimizationIntegrand(values, params, ... - phaseout) -integrand = []; -for i = 1:length(params.costTerms) - costTerm = params.costTerms{i}; - if costTerm.isEnabled - switch costTerm.type - case "coordinate_tracking" - integrand = cat(2, integrand, ... - calcTrackingCoordinateIntegrand(params, ... - values.time, values.statePositions, ... - costTerm.coordinate)); - case "controller_tracking" - integrand = cat(2, integrand, ... - calcTrackingControllerIntegrand(params, values, ... - costTerm.controller)); - case "joint_jerk_minimization" - integrand = cat(2, integrand, ... - calcMinimizingJointJerkIntegrand(values.controlJerks, ... - params, costTerm.coordinate)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end -integrand = scaleToBounds(integrand, params.maxIntegral, params.minIntegral); +function integrand = calcVerificationOptimizationIntegrand(values, ... + modeledValues, auxdata) +[costTermCalculations, allowedTypes] = ... + generateCostTermStruct("continuous", "VerificationOptimization"); +integrand = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata); +integrand = scaleToBounds(integrand, auxdata.maxIntegral, auxdata.minIntegral); integrand = integrand .^ 2; end \ No newline at end of file diff --git a/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m b/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m index e53110044..5b8979aaf 100644 --- a/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m +++ b/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m @@ -32,6 +32,6 @@ phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); phaseout.dynamics = calcVerificationOptimizationDynamicsConstraint(values, inputs.auxdata); phaseout.path = calcVerificationOptimizationPathConstraint(values, phaseout, inputs.auxdata); -phaseout.integrand = calcVerificationOptimizationIntegrand(values, inputs.auxdata, ... - phaseout); +phaseout.integrand = calcVerificationOptimizationIntegrand(values, ... + phaseout, inputs.auxdata); end \ No newline at end of file diff --git a/src/VerificationOptimization/parseVerificationOptimizationSettingsTree.m b/src/VerificationOptimization/parseVerificationOptimizationSettingsTree.m index 22654f714..c28e9a8c1 100644 --- a/src/VerificationOptimization/parseVerificationOptimizationSettingsTree.m +++ b/src/VerificationOptimization/parseVerificationOptimizationSettingsTree.m @@ -80,6 +80,7 @@ inputs.maxControlTorquesMultiple = getDoubleFromField(maxControlTorques); end end +inputs.toolName = "VerificationOptimization"; end function params = getParams(tree) diff --git a/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m b/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m index bbdfc3313..37747ede6 100644 --- a/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m +++ b/src/core/TreatmentOptimization/SetupBounds/getIntegralBounds.m @@ -26,12 +26,14 @@ % ----------------------------------------------------------------------- % function inputs = getIntegralBounds(inputs) +[~, allowedTypes] = generateCostTermStruct("continuous", inputs.toolName); inputs.maxIntegral = []; inputs.minIntegral = []; for i = 1:length(inputs.costTerms) costTerm = inputs.costTerms{i}; if costTerm.isEnabled - if isTrackingCostTerm(costTerm) || isMinimizationCostTerm(costTerm) + if any(ismember(costTerm.type, allowedTypes)) && ... + ~strcmp(costTerm.type, "user_defined") inputs.maxIntegral = cat(2, inputs.maxIntegral, ... costTerm.maxAllowableError); elseif strcmp(costTerm.type, "user_defined") diff --git a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m b/src/core/TreatmentOptimization/calcTreatmentOptimizationCost.m similarity index 67% rename from src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m rename to src/core/TreatmentOptimization/calcTreatmentOptimizationCost.m index 1a6690922..c7d20ff9e 100644 --- a/src/core/TreatmentOptimization/costTerms/isMinimizationCostTerm.m +++ b/src/core/TreatmentOptimization/calcTreatmentOptimizationCost.m @@ -1,7 +1,7 @@ % This function is part of the NMSM Pipeline, see file for full license. % % () -> () -% +% % ----------------------------------------------------------------------- % % The NMSM Pipeline is a toolkit for model personalization and treatment % @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond % +% Author(s): Marleny Vega % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -25,15 +25,23 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function output = isMinimizationCostTerm(costTerm) -minimizationCostTerms = [ ... - "joint_jerk_minimization" ... - ]; -output = false; -for i = 1:length(minimizationCostTerms) - if strcmp(costTerm.type, minimizationCostTerms(i)) - output = true; - return +function cost = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata) +cost = []; +for i = 1:length(auxdata.costTerms) + costTerm = auxdata.costTerms{i}; + if costTerm.isEnabled + if isfield(costTermCalculations, costTerm.type) && ... + any(ismember(allowedTypes, costTerm.type)) + fn = costTermCalculations.(costTerm.type); + cost = cat(2, ... + cost, ... + fn(values, modeledValues, auxdata, costTerm)); +% else +% costTerm +% throw(MException('', ['Cost term type ' costTerm.type ... +% ' does not exist for this tool.'])) + end end end end diff --git a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m deleted file mode 100644 index 27cee3f9a..000000000 --- a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m +++ /dev/null @@ -1,42 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function output = isTrackingCostTerm(costTerm) -trackingCostTerms = [ ... - "coordinate_tracking", ... - "controller_tracking", ... - "inverse_dynamics_load_tracking" ... - ]; -output = false; -for i = 1:length(trackingCostTerms) - if strcmp(costTerm.type, trackingCostTerms(i)) - output = true; - return - end -end -end - diff --git a/src/core/TreatmentOptimization/generateCostTermStruct.m b/src/core/TreatmentOptimization/generateCostTermStruct.m index 2d3185395..3c310f01b 100644 --- a/src/core/TreatmentOptimization/generateCostTermStruct.m +++ b/src/core/TreatmentOptimization/generateCostTermStruct.m @@ -45,11 +45,18 @@ switch toolName case "TrackingOptimization" allowedTypes = [ ... - - ]; + "coordinate_tracking", ... + "inverse_dynamics_load_tracking", ... + "external_force_tracking", ... + "external_moment_tracking", ... + "muscle_activation_tracking", ... + "joint_jerk_minimization", ... + ]; case "VerificationOptimization" allowedTypes = [ ... - + "coordinate_tracking", ... + "controller_tracking", ... + "joint_jerk_minimization", ... ]; case "DesignOptimization" allowedTypes = [ ... @@ -73,8 +80,9 @@ ]; case "DesignOptimization" allowedTypes = [ ... - - ]; + "synergy_vectors", ... + "user_defined", ... + ]; otherwise throw(MException('', ['Tool name' toolName 'is not valid'])) end @@ -111,6 +119,37 @@ costTermCalculations.metabolic_cost = @(values, modeledValues, auxdata, costTerm) ... calcMinimizingMetabolicCost(modeledValues.metabolicCost); +costTermCalculations.synergy_vectors = @(values, modeledValues, auxdata, costTerm) ... + calcTrackingCoordinateIntegrand(... + auxdata, ... + values.time, ... + values.statePositions, ... + costTerm.coordinate ... + ); + +costTermCalculations.inverse_dynamics_load_tracking = @(values, modeledValues, auxdata, costTerm) ... + calcTrackingInverseDynamicLoadsIntegrand( ... + auxdata, ... + values.time, ... + modeledValues.inverseDynamicMoments, ... + costTerm.load ... + ); + +costTermCalculations.external_force_tracking = @(values, modeledValues, auxdata, costTerm) ... + calcTrackingExternalForcesIntegrand(auxdata, ... + modeledValues.groundReactionsLab.forces, values.time, ... + costTerm.force); + +costTermCalculations.external_moment_tracking = @(values, modeledValues, auxdata, costTerm) ... + calcTrackingExternalMomentsIntegrand(auxdata, ... + modeledValues.groundReactionsLab.moments, values.time, ... + costTerm.moment); + +costTermCalculations.muscle_activation_tracking = @(values, modeledValues, auxdata, costTerm) ... + calcTrackingMuscleActivationIntegrand( ... + modeledValues.muscleActivations, ... + values.time, auxdata, costTerm.muscle); + costTermCalculations.user_defined = @(values, modeledValues, auxdata, costTerm) ... userDefinedFunction(values, modeledValues, auxdata, costTerm, costTermType); @@ -120,6 +159,7 @@ userDefinedFunction(values, modeledValues, auxdata, costTerm, costTermType) output = []; if strcmp(costTerm.cost_term_type, costTermType) - output = costTerm.function_name(values, modeledValues, auxdata, costTerm); + fn = str2func(costTerm.function_name); + output = fn(values, modeledValues, auxdata, costTerm); end end From 33f6ee74254492141dc85e447e046290fac62ab3 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 30 May 2023 13:29:12 -0500 Subject: [PATCH 47/72] Fixed calcMetabolicCost function --- src/core/MuscleCalculations/calcMetabolicCost.m | 1 + 1 file changed, 1 insertion(+) diff --git a/src/core/MuscleCalculations/calcMetabolicCost.m b/src/core/MuscleCalculations/calcMetabolicCost.m index 3992d47eb..8059ffb40 100644 --- a/src/core/MuscleCalculations/calcMetabolicCost.m +++ b/src/core/MuscleCalculations/calcMetabolicCost.m @@ -28,6 +28,7 @@ function metabolicCost = calcMetabolicCost(time, statePositions, ... muscleActivations, params) +metabolicCost = []; for indx = 1 : numel(params.integral.minimizing) if strcmpi(params.integral.minimizing{indx}.type, 'metabolic_cost') import org.opensim.modeling.* From 44db0b97cacf3a09182266adb8fbdceceae56df2 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 31 May 2023 13:34:28 -0500 Subject: [PATCH 48/72] Fix EMG processing --- src/Preprocessing/preprocessing.m | 6 +++--- src/core/emg/processEmg.m | 13 ++++++++----- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/Preprocessing/preprocessing.m b/src/Preprocessing/preprocessing.m index 3fd5739f6..be5d0c662 100644 --- a/src/Preprocessing/preprocessing.m +++ b/src/Preprocessing/preprocessing.m @@ -13,8 +13,8 @@ % All values required rawEmgFileName = "input_data\Patient3_NormalGait_1pt0_02_Right_01_EMG.mot"; filterOrder = 4; -highPassCutoff = 10; -lowPassCutoff = 50; +highPassCutoff = 40; +lowPassCutoff = 10; processedEmgFileName = "Patient3_NormalGait_1pt0_02_Right_01_processedEmg.sto"; processRawEmgFile(rawEmgFileName, filterOrder, highPassCutoff, ... @@ -39,7 +39,7 @@ ]; % Required: Associated .osim model file -inputSettings.model = "UF_Patient3_correctHeight_JMP_MTPGroups.osim"; +inputSettings.model = "UF_Patient3_correctHeight_JMP_MTPGroups_NCPGroups.osim"; % All values optional: files and directories of data to be split inputSettings.ikFileName = "input_data\Patient3_NormalGait_1pt0_02_Right_01_IK.mot"; diff --git a/src/core/emg/processEmg.m b/src/core/emg/processEmg.m index d9f84a91c..a431d20b2 100644 --- a/src/core/emg/processEmg.m +++ b/src/core/emg/processEmg.m @@ -40,9 +40,9 @@ % High pass filter the data order = valueOrAlternate(params, "filterOrder", 4); -highPassCutoff = valueOrAlternate(params, "highPassCutoff", 10); +highPassCutoff = valueOrAlternate(params, "highPassCutoff", 40); [b,a] = butter(order, 2 * highPassCutoff/sampleRate, 'high'); -emgData = filtfilt(b, a, emgData); +emgData = filtfilt(b, a, emgData')'; % Demean emgData = emgData-ones(size(emgData, 1), 1) * mean(emgData); @@ -51,13 +51,16 @@ emgData = abs(emgData); % Low pass filter -lowPassCutoff = valueOrAlternate(params, "lowPassCutoff", 40); -[b,a] = butter(order, 2 * lowPassCutoff / sampleRate); -emgData = filtfilt(b, a, emgData); +lowPassCutoff = valueOrAlternate(params, "lowPassCutoff", 10); +[b,a] = butter(order, 2 * lowPassCutoff / sampleRate, 'low'); +emgData = filtfilt(b, a, emgData')'; % Remove any negative EMG values that may still exist emgData(emgData < 0) = 0; +% Normalize by maximum value for each channel +emgData = emgData ./ max(emgData, [], 2); + processedEmgData = emgData'; end From f0b64e10f7b87acbf8fa9bb087138e996545675c Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 1 Jun 2023 13:04:48 -0500 Subject: [PATCH 49/72] New linear inequality constraints for MTP SynX --- .../MuscleTendonPersonalizationTool.m | 1 + .../calcMuscleExcitationsSynX.m | 6 ++ .../getLinearInequalityConstraints.m | 61 +++++++++++++------ .../SynergyExtrapolation/getSynergyCommands.m | 2 +- 4 files changed, 51 insertions(+), 19 deletions(-) diff --git a/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m b/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m index 39810ca8b..26bf1cf4b 100644 --- a/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m +++ b/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m @@ -52,6 +52,7 @@ function MuscleTendonPersonalizationTool(settingsFileName) finalValues.maxIsometricForce = inputs.maxIsometricForce; end results = calcMtpSynXModeledValues(finalValues, inputs, params); + results.time = inputs.emgTime(:, inputs.numPaddingFrames + 1 : ... end - inputs.numPaddingFrames); saveMuscleTendonPersonalizationResults(inputs.model, ... diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m index e1f15c59c..2cf57d5e5 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m @@ -84,6 +84,7 @@ emgSplines, timeDelay); % muscleExcitations = permute(muscleExcitations,[1 3 2]); end + function UnmeasuredExcitations = getUnmeasuredMuscleExcitations(params, ... emgData, extrapolationCommands, extrapolationWeights) @@ -121,6 +122,7 @@ end UnmeasuredExcitations = permute(UnmeasuredExcitations, [3 2 1]); end + function residualExcitations = getResidualMuscleExcitation(params, ... residualCommands, residualWeights) @@ -149,6 +151,7 @@ end residualExcitations = concatResidualExcitations; end + function emgData = updateEmgSignals(missingEmgChannelGroups, emgData, ... unmeasuredEmgSignals) @@ -159,6 +162,7 @@ end end end + function emgData = distributeResidualExcitations(emgData, ... currentEmgChannelGroups, residualExcitations) @@ -175,6 +179,7 @@ size(emgData, 3), size(emgData, 1), size(emgData, 2)); emgData = emgData + permute(residualExcitationsExpanded, [2 3 1]); end + function emgSplines = createEmgSignals(emgData, emgTime, timeDelay) emgSplines = cell(size(emgData, 1), size(emgData, 2)); @@ -192,6 +197,7 @@ end end end + function emgData = evaluateEmgSplines(emgTime, emgSplines, timeDelay) if size(timeDelay, 2) == 1 diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m index 817d40bfc..ea6192c0f 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m @@ -49,26 +49,48 @@ function [A,b] = getLinearInequalityConstraints(params, ... numOtherDesignVariables, synergyCommands, emgData) -if strcmp(params.matrixFactorizationMethod, 'PCA') - matrixFactorizationFactor = 1; -elseif strcmp(params.matrixFactorizationMethod, 'NMF') - matrixFactorizationFactor = 0; +% if strcmp(params.matrixFactorizationMethod, 'PCA') +% matrixFactorizationFactor = 1; +% elseif strcmp(params.matrixFactorizationMethod, 'NMF') +% matrixFactorizationFactor = 0; +% end +% % Construct the linear constraint, SynX and Residual A Matrix +% aMatrixSynergy = getSynxAMatrix(params, synergyCommands, ... +% emgData, matrixFactorizationFactor); +% aMatrixResidual = getResidualAMatrix(emgData, params.numberOfSynergies, ... +% params.missingEmgChannelGroups, params.residualCategorizationOfTrials, ... +% matrixFactorizationFactor); +% % Concatenate non-SynX design variables with SynX and Residual Matrices +% A = [zeros(2 * size(params.missingEmgChannelGroups, 2) * size(emgData, 1) * ... +% size(emgData, 2), numOtherDesignVariables) [-aMatrixSynergy ... +% aMatrixResidual; aMatrixSynergy aMatrixResidual]]; +% % b (between 0 and 1) +% b = [zeros(size(emgData, 1) * size(emgData, 2) * size( ... +% params.missingEmgChannelGroups, 2), 1); ones(size(emgData, 1) * ... +% size(emgData, 2) * size(params.missingEmgChannelGroups, 2), 1)]; + +fullCommands = synergyCommands{1}; +for i = 2:length(synergyCommands) + fullCommands = cat(1, fullCommands, synergyCommands{i}); end -% Construct the linear constraint, SynX and Residual A Matrix -aMatrixSynergy = getSynxAMatrix(params, synergyCommands, ... - emgData, matrixFactorizationFactor); -aMatrixResidual = getResidualAMatrix(emgData, params.numberOfSynergies, ... - params.missingEmgChannelGroups, params.residualCategorizationOfTrials, ... - matrixFactorizationFactor); -% Concatenate non-SynX design variables with SynX and Residual Matrices -A = [zeros(2 * size(params.missingEmgChannelGroups, 2) * size(emgData, 1) * ... - size(emgData, 2), numOtherDesignVariables) [-aMatrixSynergy ... - aMatrixResidual; aMatrixSynergy aMatrixResidual]]; -% b (between 0 and 1) -b = [zeros(size(emgData, 1) * size(emgData, 2) * size( ... - params.missingEmgChannelGroups, 2), 1); ones(size(emgData, 1) * ... - size(emgData, 2) * size(params.missingEmgChannelGroups, 2), 1)]; +fullCommands(:, end+1) = ones(size(fullCommands, 1), 1); + +totalGroups = length(params.missingEmgChannelGroups) + ... + length(params.currentEmgChannelGroups); +A = zeros(totalGroups * size(fullCommands, 1), numOtherDesignVariables + totalGroups * size(fullCommands, 2)); +for i = 1:totalGroups + A(size(fullCommands, 1) * (i - 1) + 1 : size(fullCommands, 1) * i, ... + numOtherDesignVariables + size(fullCommands, 2) * (i - 1) + 1 : ... + numOtherDesignVariables + size(fullCommands, 2) * i) = fullCommands; end +b = ones(totalGroups * size(fullCommands, 1), 1); + +end + + + + + function [aMatrix, aMatrixSynergy] = allocateSynxMatrixAMemory(emgData, ... numberOfSynergies, missingEmgChannelGroups, thirdMatrixDimension, ... matrixFactorizationFactor) @@ -79,6 +101,7 @@ size(missingEmgChannelGroups, 2), (numberOfSynergies + ... matrixFactorizationFactor) * thirdMatrixDimension * numberOfSynergies); end + function aMatrixSynergy = updateHalfMatrixA(emgData, aMatrix,... numberOfSynergies, missingEmgChannelGroups, thirdMatrixDimension, ... matrixFactorizationFactor) @@ -91,6 +114,7 @@ matrixFactorizationFactor) * thirdMatrixDimension) = aMatrix; end end + function aMatrixSynergy = getSynxAMatrix(params, synergyCommands, ... emgData, matrixFactorizationFactor) @@ -118,6 +142,7 @@ params.numberOfSynergies, params.missingEmgChannelGroups, ... length(params.synergyCategorizationOfTrials), matrixFactorizationFactor); end + function aMatrixResidual = getResidualAMatrix(emgData, ... numberOfSynergies, missingEmgChannelGroups, residualCategorization, ... matrixFactorizationFactor) diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/getSynergyCommands.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/getSynergyCommands.m index 12aefafe6..48ecdb469 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/getSynergyCommands.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/getSynergyCommands.m @@ -53,7 +53,7 @@ maxEmgOverAllTrials = max(max(emgData, [], 3), [], 1); normalizedEMG = permute(emgData ./ maxEmgOverAllTrials, [3 2 1]); %--Extract synergy excitations from measured muscle excitations -if strcmpi(matrixFactorizationMethod, 'PCA') + if strcmpi(matrixFactorizationMethod, 'PCA') extrapolationCommands = getPcaCommands(normalizedEMG, numberOfSynergies, ... synergyCategorizationOfTrials); residualCommands = getPcaCommands(normalizedEMG, numberOfSynergies, ... From 02debeabe4dcf067369befc35035308aac10c2b9 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Thu, 1 Jun 2023 16:39:34 -0500 Subject: [PATCH 50/72] Updated formulation for free final time --- .../parseDesignOptimizationSettingsTree.m | 2 ++ .../calcVerificationOptimizationIntegrand.m | 2 +- .../IntegrandTerms/calcTrackingControllerIntegrand.m | 6 +++--- src/core/TreatmentOptimization/generateCostTermStruct.m | 3 ++- 4 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m index e8b10c705..1a001ac00 100644 --- a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m +++ b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m @@ -35,6 +35,8 @@ function inputs = getInputs(tree) import org.opensim.modeling.Storage inputs = getTreatmentOptimizationInputs(tree); +inputs.experimentalTime = inputs.experimentalTime / ... + inputs.experimentalTime(end); inputs = getDesignVariableBounds(tree, inputs); if strcmpi(inputs.controllerType, 'synergy_driven') inputs.synergyWeights = parseTreatmentOptimizationStandard(... diff --git a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m index 30adf244b..690da8702 100644 --- a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m +++ b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m @@ -40,7 +40,7 @@ case "controller_tracking" integrand = cat(2, integrand, ... calcTrackingControllerIntegrand(params, values, ... - costTerm.controller)); + values.time, costTerm.controller)); case "joint_jerk_minimization" integrand = cat(2, integrand, ... calcMinimizingJointJerkIntegrand(values.controlJerks, ... diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m index b4ecbf8cd..782441ca4 100644 --- a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m @@ -25,20 +25,20 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function cost = calcTrackingControllerIntegrand(auxdata, values, ... +function cost = calcTrackingControllerIntegrand(auxdata, values, time, ... controllerName) switch auxdata.controllerType case 'synergy_driven' indx = find(strcmp(convertCharsToStrings( ... auxdata.synergyLabels), controllerName)); - synergyActivations = fnval(auxdata.splineSynergyActivations, values.time)'; + synergyActivations = fnval(auxdata.splineSynergyActivations, time)'; cost = calcTrackingCostArrayTerm(synergyActivations, values.controlSynergyActivations, indx); case 'torque_driven' indx1 = find(strcmp(convertCharsToStrings( ... auxdata.inverseDynamicMomentLabels), controllerName)); indx2 = find(strcmp(convertCharsToStrings( ... strcat(auxdata.controlTorqueNames, '_moment')), controllerName)); - experimentalJointMoments = fnval(auxdata.splineJointMoments, values.time)'; + experimentalJointMoments = fnval(auxdata.splineJointMoments, time)'; cost = experimentalJointMoments(:, indx1) - values.controlTorques(:, indx2); end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/generateCostTermStruct.m b/src/core/TreatmentOptimization/generateCostTermStruct.m index 2d3185395..7bc7a1e9b 100644 --- a/src/core/TreatmentOptimization/generateCostTermStruct.m +++ b/src/core/TreatmentOptimization/generateCostTermStruct.m @@ -89,7 +89,7 @@ costTermCalculations.coordinate_tracking = @(values, modeledValues, auxdata, costTerm) ... calcTrackingCoordinateIntegrand( ... auxdata, ... - values.time, ... + values.time/values.time(end), ... values.statePositions, ... costTerm.coordinate ... ); @@ -98,6 +98,7 @@ calcTrackingControllerIntegrand( ... auxdata, ... values, ... + values.time/values.time(end), ... costTerm.controller ... ); From 0b7f1ff747a5fe66bef56983c70ff240919887a4 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Thu, 1 Jun 2023 20:25:34 -0500 Subject: [PATCH 51/72] Parse NCP symmetry as toggle --- .../parseNeuralControlPersonalizationSettingsTree.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m index 2d3501a82..6aa280996 100644 --- a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m +++ b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m @@ -112,6 +112,11 @@ params.normalizedFiberLengthGroupNames, model); params.costTerms = parseRcnlCostTermSet( ... getFieldByNameOrError(tree, 'RCNLCostTermSet').objects.RCNLCostTerm); +if strcmpi('true', getTextFromField(getFieldByName(tree, ... + 'enforce_bilateral_symmetry'))) + params.costTerms{end+1} = struct('type', 'bilateral_symmetry', ... + 'isEnabled', true, 'maxAllowableError', 1e-6, 'errorCenter', 0); +end params.diffMinChange = str2double(getTextFromField(... getFieldByNameOrAlternate(tree, 'diff_min_change', '1e-6'))); params.stepTolerance = str2double(getTextFromField(... From 3a7f7194874ef1117ad34f59a839a86e2299f4c0 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Fri, 2 Jun 2023 11:27:19 -0500 Subject: [PATCH 52/72] Finished up changes for free final time formulation --- src/DesignOptimization/calcDesignOptimizationObjective.m | 6 +++++- .../computeDesignOptimizationEndpointFunction.m | 2 +- .../computeDesignOptimizationMainFunction.m | 4 ++++ .../parseDesignOptimizationSettingsTree.m | 5 +++++ .../SetupBounds/getDesignVariableInputBounds.m | 6 +++++- 5 files changed, 20 insertions(+), 3 deletions(-) diff --git a/src/DesignOptimization/calcDesignOptimizationObjective.m b/src/DesignOptimization/calcDesignOptimizationObjective.m index 5856be4c7..55d0c8883 100644 --- a/src/DesignOptimization/calcDesignOptimizationObjective.m +++ b/src/DesignOptimization/calcDesignOptimizationObjective.m @@ -25,8 +25,12 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function objective = calcDesignOptimizationObjective(discrete, continuous) +function objective = calcDesignOptimizationObjective(discrete, ... + continuous, finalTime, inputs) continuousObjective = sum(continuous) / length(continuous); +if isfield(inputs, "finalTimeRange") + continuousObjective = continuousObjective / finalTime; +end discreteObjective = sum(discrete) / length(discrete); if isnan(discreteObjective); discreteObjective = 0; end objective = continuousObjective + discreteObjective; diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index 58fecf64d..3610cc439 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -46,7 +46,7 @@ % discrete = calcDesignOptimizationDiscreteObjective(values, inputs.auxdata); discrete = computeStaticParameterCost(inputs); output.objective = calcDesignOptimizationObjective(discrete, ... - inputs.phase.integral); + inputs.phase.integral, inputs.phase.finaltime, inputs.auxdata); end function cost = computeStaticParameterCost(inputs) diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index b2b3a8ba5..5cbdf2e82 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -63,6 +63,10 @@ 0.5]; end end +if isfield(inputs, "finalTimeRange") + bounds.phase.finaltime.lower = -0.5; + bounds.phase.finaltime.upper = 0.5; +end end function guess = addUserDefinedTermsToGuess(guess, inputs) diff --git a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m index 1a001ac00..029175bb9 100644 --- a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m +++ b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m @@ -89,6 +89,11 @@ if(isstruct(maxControlTorques)) inputs.maxControlTorquesMultiple = getDoubleFromField(maxControlTorques); end +finalTimeRange = getFieldByName(designVariableTree, ... + 'final_time_range'); +if(isstruct(finalTimeRange)) + inputs.finalTimeRange = getDoubleFromField(finalTimeRange); +end end end diff --git a/src/core/TreatmentOptimization/SetupBounds/getDesignVariableInputBounds.m b/src/core/TreatmentOptimization/SetupBounds/getDesignVariableInputBounds.m index 764772061..dd407e304 100644 --- a/src/core/TreatmentOptimization/SetupBounds/getDesignVariableInputBounds.m +++ b/src/core/TreatmentOptimization/SetupBounds/getDesignVariableInputBounds.m @@ -26,7 +26,11 @@ % ----------------------------------------------------------------------- % function inputs = getDesignVariableInputBounds(inputs) -inputs.maxTime = max(inputs.experimentalTime); +if isfield(inputs, "finalTimeRange") + inputs.maxTime = max(inputs.experimentalTime) + inputs.finalTimeRange; +else + inputs.maxTime = max(inputs.experimentalTime); +end inputs.minTime = min(inputs.experimentalTime); maxStatePositions = max(inputs.experimentalJointAngles) + ... From ee590644872b21a403fb83ba1c84c078455f53a1 Mon Sep 17 00:00:00 2001 From: Spencer Williams <54556034+SpencerTWilliams@users.noreply.github.com> Date: Fri, 2 Jun 2023 16:44:38 -0500 Subject: [PATCH 53/72] Add lower inequality constraint to MTP SynX --- .../SynergyExtrapolation/getLinearInequalityConstraints.m | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m index ea6192c0f..a73c6776d 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m @@ -83,7 +83,8 @@ numOtherDesignVariables + size(fullCommands, 2) * (i - 1) + 1 : ... numOtherDesignVariables + size(fullCommands, 2) * i) = fullCommands; end -b = ones(totalGroups * size(fullCommands, 1), 1); +A = cat(1, A, -A); +b = ones(totalGroups * size(fullCommands, 1) * 2, 1); end From 3660531816199a8d502e4c710e19169c5157af6a Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Mon, 5 Jun 2023 12:16:53 -0500 Subject: [PATCH 54/72] Fixed minor syntax errors --- .../getDesignOptimizationValueStruct.m | 7 +++++++ .../calcSynergyBasedModeledValues.m | 18 +++++++++--------- .../calcTrackingOptimizationPathConstraint.m | 8 ++++---- ...uteTrackingOptimizationContinuousFunction.m | 12 ++++++------ .../getTrackingOptimizationValueStruct.m | 4 ++-- ...alcVerificationOptimizationPathConstraint.m | 8 ++++---- ...erificationOptimizationContinuousFunction.m | 14 +++++++------- .../getVerificationOptimizationValueStruct.m | 2 +- .../MuscleCalculations/calcMetabolicCost.m | 6 +++--- 9 files changed, 43 insertions(+), 36 deletions(-) diff --git a/src/DesignOptimization/getDesignOptimizationValueStruct.m b/src/DesignOptimization/getDesignOptimizationValueStruct.m index 23782a4a6..ada0f9704 100644 --- a/src/DesignOptimization/getDesignOptimizationValueStruct.m +++ b/src/DesignOptimization/getDesignOptimizationValueStruct.m @@ -37,6 +37,13 @@ values.synergyWeights = getSynergyWeightsFromGroups(... params.synergyWeightsGuess, params); end + if params.splineJointMoments.dim > 1 + values.controlSynergyActivations = ... + fnval(params.splineSynergyActivations, values.time)'; + else + values.controlSynergyActivations = ... + fnval(params.splineSynergyActivations, values.time); + end end for i = 1:length(params.userDefinedVariables) values.(params.userDefinedVariables{i}.type) = scaleToOriginal( ... diff --git a/src/TrackingOptimization/calcSynergyBasedModeledValues.m b/src/TrackingOptimization/calcSynergyBasedModeledValues.m index d275dc0ad..c31902e6d 100644 --- a/src/TrackingOptimization/calcSynergyBasedModeledValues.m +++ b/src/TrackingOptimization/calcSynergyBasedModeledValues.m @@ -25,23 +25,23 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function phaseout = calcSynergyBasedModeledValues(values, params, phaseout) +function modeledValues = calcSynergyBasedModeledValues(values, params, modeledValues) if strcmp(params.controllerType, 'synergy_driven') [jointAngles, jointVelocities] = getMuscleActuatedDOFs(values, params); [params.muscleTendonLength, params.momentArms, ... params.muscleTendonVelocity] = calcSurrogateModel(params, ... jointAngles, jointVelocities); - [phaseout.normalizedFiberLength, phaseout.normalizedFiberVelocity] = ... + [modeledValues.normalizedFiberLength, modeledValues.normalizedFiberVelocity] = ... calcNormalizedMuscleFiberLengthsAndVelocities(params, ... ones(1, params.numMuscles), ones(1, params.numMuscles)); - phaseout.muscleActivations = calcMuscleActivationFromSynergies(values); - phaseout.metabolicCost = calcMetabolicCost(values.time, ... - values.statePositions, phaseout.muscleActivations, params); + modeledValues.muscleActivations = calcMuscleActivationFromSynergies(values); + modeledValues.metabolicCost = calcMetabolicCost(values.time, ... + values.statePositions, modeledValues.muscleActivations, params); muscleJointMoments = calcMuscleJointMoments(params, ... - phaseout.muscleActivations, phaseout.normalizedFiberLength, ... - phaseout.normalizedFiberVelocity); - phaseout.muscleJointMoments = muscleJointMoments(:, params.surrogateModelIndex); - phaseout.muscleJointMoments = phaseout.muscleJointMoments(:, params.dofsActuatedIndex); + modeledValues.muscleActivations, modeledValues.normalizedFiberLength, ... + modeledValues.normalizedFiberVelocity); + modeledValues.muscleJointMoments = muscleJointMoments(:, params.surrogateModelIndex); + modeledValues.muscleJointMoments = modeledValues.muscleJointMoments(:, params.dofsActuatedIndex); end end diff --git a/src/TrackingOptimization/calcTrackingOptimizationPathConstraint.m b/src/TrackingOptimization/calcTrackingOptimizationPathConstraint.m index d724658f1..87bf1163f 100644 --- a/src/TrackingOptimization/calcTrackingOptimizationPathConstraint.m +++ b/src/TrackingOptimization/calcTrackingOptimizationPathConstraint.m @@ -25,7 +25,7 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function path = calcTrackingOptimizationPathConstraint(values, phaseout, ... +function path = calcTrackingOptimizationPathConstraint(values, modeledValues, ... params) path = []; for i = 1:length(params.path) @@ -37,15 +37,15 @@ calcRootSegmentResidualsPathConstraints(... constraintTerm.load, ... params.inverseDynamicMomentLabels, ... - phaseout.inverseDynamicMoments)); + modeledValues.inverseDynamicMoments)); case "muscle_model_moment_consistency" path = cat(2, path, ... calcMuscleActuatedMomentsPathConstraints(params, ... - phaseout, constraintTerm.load)); + modeledValues, constraintTerm.load)); case "torque_model_moment_consistency" path = cat(2, path, ... calcTorqueActuatedMomentsPathConstraints(params, ... - phaseout, values.controlTorques, constraintTerm.load)); + modeledValues, values.controlTorques, constraintTerm.load)); otherwise throw(MException('', ['Constraint term type ' ... constraintTerm.type ' does not exist for this tool.'])) diff --git a/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m b/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m index 6eaacbbef..9c34e4b56 100644 --- a/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m +++ b/src/TrackingOptimization/computeTrackingOptimizationContinuousFunction.m @@ -25,11 +25,11 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function phaseout = computeTrackingOptimizationContinuousFunction(inputs) +function modeledValues = computeTrackingOptimizationContinuousFunction(inputs) values = getTrackingOptimizationValueStruct(inputs.phase, inputs.auxdata); -phaseout = calcTorqueBasedModeledValues(values, inputs.auxdata); -phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); -phaseout.dynamics = calcTrackingOptimizationDynamicsConstraint(values, inputs.auxdata); -phaseout.path = calcTrackingOptimizationPathConstraint(values, phaseout, inputs.auxdata); -phaseout.integrand = calcTrackingOptimizationIntegrand(values, phaseout, inputs.auxdata); +modeledValues = calcTorqueBasedModeledValues(values, inputs.auxdata); +modeledValues = calcSynergyBasedModeledValues(values, inputs.auxdata, modeledValues); +modeledValues.dynamics = calcTrackingOptimizationDynamicsConstraint(values, inputs.auxdata); +modeledValues.path = calcTrackingOptimizationPathConstraint(values, modeledValues, inputs.auxdata); +modeledValues.integrand = calcTrackingOptimizationIntegrand(values, modeledValues, inputs.auxdata); end \ No newline at end of file diff --git a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m index 00741a496..520f586fe 100644 --- a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m +++ b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m @@ -37,7 +37,7 @@ values.synergyWeights = getSynergyWeightsFromGroups(... params.synergyWeightsGuess, params); end - values.controlSynergyActivations = control(:, params.numCoordinates + 1 : ... - params.numCoordinates + params.numSynergies); + values.controlSynergyActivations = inputs.control(:, ... + params.numCoordinates + 1 : params.numCoordinates + params.numSynergies); end end diff --git a/src/VerificationOptimization/calcVerificationOptimizationPathConstraint.m b/src/VerificationOptimization/calcVerificationOptimizationPathConstraint.m index d0bb058b2..be8055847 100644 --- a/src/VerificationOptimization/calcVerificationOptimizationPathConstraint.m +++ b/src/VerificationOptimization/calcVerificationOptimizationPathConstraint.m @@ -25,7 +25,7 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function path = calcVerificationOptimizationPathConstraint(values, phaseout, ... +function path = calcVerificationOptimizationPathConstraint(values, modeledValues, ... params) path = []; for i = 1:length(params.path) @@ -37,15 +37,15 @@ calcRootSegmentResidualsPathConstraints(... constraintTerm.load, ... params.inverseDynamicMomentLabels, ... - phaseout.inverseDynamicMoments)); + modeledValues.inverseDynamicMoments)); case "muscle_model_moment_consistency" path = cat(2, path, ... calcMuscleActuatedMomentsPathConstraints(params, ... - phaseout, constraintTerm.load)); + modeledValues, constraintTerm.load)); case "torque_model_moment_consistency" path = cat(2, path, ... calcTorqueActuatedMomentsPathConstraints(params, ... - phaseout, values.controlTorques, constraintTerm.load)); + modeledValues, values.controlTorques, constraintTerm.load)); otherwise throw(MException('', ['Constraint term type ' ... constraintTerm.type ' does not exist for this tool.'])) diff --git a/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m b/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m index 5b8979aaf..8d7494d44 100644 --- a/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m +++ b/src/VerificationOptimization/computeVerificationOptimizationContinuousFunction.m @@ -25,13 +25,13 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function phaseout = computeVerificationOptimizationContinuousFunction(inputs) +function modeledValues = computeVerificationOptimizationContinuousFunction(inputs) values = getVerificationOptimizationValueStruct(inputs.phase, inputs.auxdata); -phaseout = calcTorqueBasedModeledValues(values, inputs.auxdata); -phaseout = calcSynergyBasedModeledValues(values, inputs.auxdata, phaseout); -phaseout.dynamics = calcVerificationOptimizationDynamicsConstraint(values, inputs.auxdata); -phaseout.path = calcVerificationOptimizationPathConstraint(values, phaseout, inputs.auxdata); -phaseout.integrand = calcVerificationOptimizationIntegrand(values, ... - phaseout, inputs.auxdata); +modeledValues = calcTorqueBasedModeledValues(values, inputs.auxdata); +modeledValues = calcSynergyBasedModeledValues(values, inputs.auxdata, modeledValues); +modeledValues.dynamics = calcVerificationOptimizationDynamicsConstraint(values, inputs.auxdata); +modeledValues.path = calcVerificationOptimizationPathConstraint(values, modeledValues, inputs.auxdata); +modeledValues.integrand = calcVerificationOptimizationIntegrand(values, ... + modeledValues, inputs.auxdata); end \ No newline at end of file diff --git a/src/VerificationOptimization/getVerificationOptimizationValueStruct.m b/src/VerificationOptimization/getVerificationOptimizationValueStruct.m index f123b38ce..c3e1ada73 100644 --- a/src/VerificationOptimization/getVerificationOptimizationValueStruct.m +++ b/src/VerificationOptimization/getVerificationOptimizationValueStruct.m @@ -28,7 +28,7 @@ function values = getVerificationOptimizationValueStruct(inputs, params) values = getTreatmentOptimizationValueStruct(inputs, params); if strcmp(params.controllerType, 'synergy_driven') - values.controlSynergyActivations = control(:, params.numCoordinates + 1 : ... + values.controlSynergyActivations = inputs.control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numSynergies); values.synergyWeights = getSynergyWeightsFromGroups(... params.synergyWeightsGuess, params); diff --git a/src/core/MuscleCalculations/calcMetabolicCost.m b/src/core/MuscleCalculations/calcMetabolicCost.m index 3992d47eb..5a458f3f0 100644 --- a/src/core/MuscleCalculations/calcMetabolicCost.m +++ b/src/core/MuscleCalculations/calcMetabolicCost.m @@ -27,9 +27,9 @@ function metabolicCost = calcMetabolicCost(time, statePositions, ... muscleActivations, params) - -for indx = 1 : numel(params.integral.minimizing) - if strcmpi(params.integral.minimizing{indx}.type, 'metabolic_cost') +metabolicCost = []; +for indx = 1 : numel(params.costTerms) + if strcmpi(params.costTerms{indx}.type, 'metabolic_cost') import org.opensim.modeling.* model = Model(params.model); for i = 1 : params.numMuscles From 03d4af763a2b0c0d00c20ef1d4125e9da08d8909 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Mon, 5 Jun 2023 14:29:22 -0500 Subject: [PATCH 55/72] Updated integrand calculation --- src/DesignOptimization/calcDesignOptimizationIntegrand.m | 2 +- .../calcVerificationOptimizationIntegrand.m | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DesignOptimization/calcDesignOptimizationIntegrand.m b/src/DesignOptimization/calcDesignOptimizationIntegrand.m index 58a2c1010..adb92fc6b 100644 --- a/src/DesignOptimization/calcDesignOptimizationIntegrand.m +++ b/src/DesignOptimization/calcDesignOptimizationIntegrand.m @@ -46,7 +46,7 @@ end end end -integrand = scaleToBounds(integrand, auxdata.maxIntegral, auxdata.minIntegral); +integrand = integrand ./ (auxdata.maxIntegral - auxdata.minIntegral); integrand = integrand .^ 2; end diff --git a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m index 690da8702..7f162844f 100644 --- a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m +++ b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m @@ -51,6 +51,6 @@ end end end -integrand = scaleToBounds(integrand, params.maxIntegral, params.minIntegral); +integrand = integrand ./ (params.maxIntegral - params.minIntegral); integrand = integrand .^ 2; end \ No newline at end of file From d4ad6029a9bad1e0e0462e75d40a3a6dbbc3e4e7 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 5 Jun 2023 21:03:54 -0500 Subject: [PATCH 56/72] Fix merge conflicts --- .../calcTrackingOptimizationIntegrand.m | 48 +++---------------- .../costTerms/isTrackingCostTerm.m | 44 ----------------- 2 files changed, 6 insertions(+), 86 deletions(-) delete mode 100644 src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m diff --git a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m index da2329145..5b8339a70 100644 --- a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m +++ b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m @@ -25,48 +25,12 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function integrand = calcTrackingOptimizationIntegrand(values, params, ... - phaseout) -integrand = []; -for i = 1:length(params.costTerms) - costTerm = params.costTerms{i}; - if costTerm.isEnabled - switch costTerm.type - case "coordinate_tracking" - integrand = cat(2, integrand, ... - calcTrackingCoordinateIntegrand(params, ... - values.time, values.statePositions, ... - costTerm.coordinate)); - case "inverse_dynamics_load_tracking" - integrand = cat(2, integrand, ... - calcTrackingInverseDynamicLoadsIntegrand(params, ... - values.time, phaseout.inverseDynamicMoments, ... - costTerm.load)); - case "external_force_tracking" - integrand = cat(2, integrand, ... - calcTrackingExternalForcesIntegrand(params, ... - phaseout.groundReactionsLab.forces, values.time, ... - costTerm.force)); - case "external_moment_tracking" - integrand = cat(2, integrand, ... - calcTrackingExternalMomentsIntegrand(params, ... - phaseout.groundReactionsLab.moments, values.time, ... - costTerm.moment)); - case "muscle_activation" - integrand = cat(2, integrand, ... - calcTrackingMuscleActivationIntegrand( ... - phaseout.muscleActivations, ... - values.time, params, costTerm.muscle)); - case "joint_jerk_minimization" - integrand = cat(2, integrand, ... - calcMinimizingJointJerkIntegrand(values.controlJerks, ... - params, costTerm.coordinate)); - otherwise - throw(MException('', ['Cost term type ' costTerm.type ... - ' does not exist for this tool.'])) - end - end -end +function integrand = calcTrackingOptimizationIntegrand(values, ... + modeledValues, auxdata) +[costTermCalculations, allowedTypes] = ... + generateCostTermStruct("continuous", "TrackingOptimization"); +integrand = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata); integrand = integrand ./ (params.maxIntegral - params.minIntegral); integrand = integrand .^ 2; end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m b/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m deleted file mode 100644 index 219f34e9e..000000000 --- a/src/core/TreatmentOptimization/costTerms/isTrackingCostTerm.m +++ /dev/null @@ -1,44 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Claire V. Hammond % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function output = isTrackingCostTerm(costTerm) -trackingCostTerms = [ ... - "coordinate_tracking", ... - "controller_tracking", ... - "inverse_dynamics_load_tracking", ... - "external_force_tracking", ... - "external_moment_tracking" ... - ]; -output = false; -for i = 1:length(trackingCostTerms) - if strcmp(costTerm.type, trackingCostTerms(i)) - output = true; - return - end -end -end - From 1aa237657e219d3cedbdfc80837c8f5020e162e1 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 5 Jun 2023 21:04:55 -0500 Subject: [PATCH 57/72] Fix integrand merge conflict --- .../calcTrackingOptimizationIntegrand.m | 36 ------------------- 1 file changed, 36 deletions(-) delete mode 100644 src/TrackingOptimization/calcTrackingOptimizationIntegrand.m diff --git a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m deleted file mode 100644 index 5b8339a70..000000000 --- a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m +++ /dev/null @@ -1,36 +0,0 @@ -% This function is part of the NMSM Pipeline, see file for full license. -% -% () -> () -% - -% ----------------------------------------------------------------------- % -% The NMSM Pipeline is a toolkit for model personalization and treatment % -% optimization of neuromusculoskeletal models through OpenSim. See % -% nmsm.rice.edu and the NOTICE file for more information. The % -% NMSM Pipeline is developed at Rice University and supported by the US % -% National Institutes of Health (R01 EB030520). % -% % -% Copyright (c) 2021 Rice University and the Authors % -% Author(s): Marleny Vega % -% % -% 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. % -% ----------------------------------------------------------------------- % - -function integrand = calcTrackingOptimizationIntegrand(values, ... - modeledValues, auxdata) -[costTermCalculations, allowedTypes] = ... - generateCostTermStruct("continuous", "TrackingOptimization"); -integrand = calcTreatmentOptimizationCost( ... - costTermCalculations, allowedTypes, values, modeledValues, auxdata); -integrand = integrand ./ (params.maxIntegral - params.minIntegral); -integrand = integrand .^ 2; -end \ No newline at end of file From 6c44c21f544512b0922d14c851f1a9efa19d5c8f Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 5 Jun 2023 21:06:18 -0500 Subject: [PATCH 58/72] Restore TO integrand --- .../calcTrackingOptimizationIntegrand.m | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/TrackingOptimization/calcTrackingOptimizationIntegrand.m diff --git a/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m new file mode 100644 index 000000000..5b8339a70 --- /dev/null +++ b/src/TrackingOptimization/calcTrackingOptimizationIntegrand.m @@ -0,0 +1,36 @@ +% This function is part of the NMSM Pipeline, see file for full license. +% +% () -> () +% + +% ----------------------------------------------------------------------- % +% The NMSM Pipeline is a toolkit for model personalization and treatment % +% optimization of neuromusculoskeletal models through OpenSim. See % +% nmsm.rice.edu and the NOTICE file for more information. The % +% NMSM Pipeline is developed at Rice University and supported by the US % +% National Institutes of Health (R01 EB030520). % +% % +% Copyright (c) 2021 Rice University and the Authors % +% Author(s): Marleny Vega % +% % +% 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. % +% ----------------------------------------------------------------------- % + +function integrand = calcTrackingOptimizationIntegrand(values, ... + modeledValues, auxdata) +[costTermCalculations, allowedTypes] = ... + generateCostTermStruct("continuous", "TrackingOptimization"); +integrand = calcTreatmentOptimizationCost( ... + costTermCalculations, allowedTypes, values, modeledValues, auxdata); +integrand = integrand ./ (params.maxIntegral - params.minIntegral); +integrand = integrand .^ 2; +end \ No newline at end of file From 989c808dc345d54d046f34139cad3543e26a7d73 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 5 Jun 2023 21:14:45 -0500 Subject: [PATCH 59/72] Fix residual SynX activation allowable error --- src/MuscleTendonPersonalization/Optimization/calcMtpCost.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m b/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m index 575b4c9b1..7ec645d8d 100644 --- a/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m +++ b/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m @@ -73,7 +73,7 @@ synxModeledValues, experimentalData, costTerm); case "residual_muscle_activation" cost = calcResidualMuscleActivationCost( ... - synxModeledValues, modeledValues, experimentalData, params); + synxModeledValues, modeledValues, experimentalData, costTerm); case "muscle_excitation_penalty" cost = calcMuscleExcitationPenaltyCost( ... synxModeledValues, experimentalData, costTerm); From 46f1d9554d740381dfff3e5076887b6e0235a6a9 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 6 Jun 2023 16:44:59 -0500 Subject: [PATCH 60/72] Fixed free final time formulation --- .../computeDesignOptimizationEndpointFunction.m | 2 +- .../computeDesignOptimizationMainFunction.m | 8 ++++---- .../IntegrandTerms/calcTrackingControllerIntegrand.m | 12 ++++++++++-- .../reportTreatmentOptimizationResults.m | 9 ++++++--- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m index 3610cc439..c1570015b 100644 --- a/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationEndpointFunction.m @@ -46,7 +46,7 @@ % discrete = calcDesignOptimizationDiscreteObjective(values, inputs.auxdata); discrete = computeStaticParameterCost(inputs); output.objective = calcDesignOptimizationObjective(discrete, ... - inputs.phase.integral, inputs.phase.finaltime, inputs.auxdata); + inputs.phase.integral, values.time(end), inputs.auxdata); end function cost = computeStaticParameterCost(inputs) diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index 5cbdf2e82..8d14b3432 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -26,8 +26,8 @@ % ----------------------------------------------------------------------- % function output = computeDesignOptimizationMainFunction(inputs, params) -bounds = setupProblemBounds(inputs); guess = setupCommonOptimalControlInitialGuess(inputs); +bounds = setupProblemBounds(inputs, guess); guess = addUserDefinedTermsToGuess(guess, inputs); setup = setupCommonOptimalControlSolverSettings(inputs, ... bounds, guess, params, ... @@ -41,7 +41,7 @@ output.solution = solution; end -function bounds = setupProblemBounds(inputs) +function bounds = setupProblemBounds(inputs, guess) bounds = setupCommonOptimalControlBounds(inputs); % setup parameter bounds if strcmp(inputs.controllerType, 'synergy_driven') @@ -64,7 +64,7 @@ end end if isfield(inputs, "finalTimeRange") - bounds.phase.finaltime.lower = -0.5; + bounds.phase.finaltime.lower = (0.5 - guess.phase.time(end)) + 0.5; bounds.phase.finaltime.upper = 0.5; end end @@ -82,4 +82,4 @@ variable.upper_bounds, ... variable.lower_bounds)]; end -end +end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m index 782441ca4..fda1c9d5d 100644 --- a/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m +++ b/src/core/TreatmentOptimization/IntegrandTerms/calcTrackingControllerIntegrand.m @@ -32,13 +32,21 @@ case 'synergy_driven' indx = find(strcmp(convertCharsToStrings( ... auxdata.synergyLabels), controllerName)); - synergyActivations = fnval(auxdata.splineSynergyActivations, time)'; + if auxdata.splineJointMoments.dim > 1 + synergyActivations = fnval(auxdata.splineSynergyActivations, time)'; + else + synergyActivations = fnval(auxdata.splineSynergyActivations, time); + end cost = calcTrackingCostArrayTerm(synergyActivations, values.controlSynergyActivations, indx); case 'torque_driven' indx1 = find(strcmp(convertCharsToStrings( ... auxdata.inverseDynamicMomentLabels), controllerName)); indx2 = find(strcmp(convertCharsToStrings( ... strcat(auxdata.controlTorqueNames, '_moment')), controllerName)); - experimentalJointMoments = fnval(auxdata.splineJointMoments, time)'; + if auxdata.splineJointMoments.dim > 1 + experimentalJointMoments = fnval(auxdata.splineJointMoments, time)'; + else + experimentalJointMoments = fnval(auxdata.splineJointMoments, time); + end cost = experimentalJointMoments(:, indx1) - values.controlTorques(:, indx2); end \ No newline at end of file diff --git a/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m b/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m index 50782ad8b..c5146273a 100644 --- a/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m +++ b/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m @@ -27,10 +27,13 @@ function reportTreatmentOptimizationResults(solution, inputs) -if isfield(solution.solution, "parameter") - parameterResults = solution.solution.parameter +for i = 1:length(inputs.userDefinedVariables) + parameterResults = scaleToOriginal( ... + solution.solution.phase.parameter(i, 1), ... + inputs.userDefinedVariables{i}.upper_bounds, ... + inputs.userDefinedVariables{i}.lower_bounds) end -values = getTrackingOptimizationValueStruct(solution.solution.phase, inputs); +values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); if strcmp(inputs.controllerType, 'synergy_driven') % plot Muscle Activations plotMuscleActivations(solution.muscleActivations, values.time, ... From 7d38f2447427da00a8db362709e1166348842d91 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Fri, 9 Jun 2023 19:40:02 -0500 Subject: [PATCH 61/72] Updated variable names in mex/matlab functions --- .../TrackingOptimizationTool.m | 6 - .../TreatmentOptimization/inverseDynamics.m | 17 ++- .../inverseDynamicsMatlabParallel.m | 138 +++++++++--------- .../TreatmentOptimization/pointKinematics.m | 17 ++- .../pointKinematicsMatlabParallel.m | 120 +++++++-------- 5 files changed, 148 insertions(+), 150 deletions(-) diff --git a/src/TrackingOptimization/TrackingOptimizationTool.m b/src/TrackingOptimization/TrackingOptimizationTool.m index cd6f928d3..d8d9592f1 100644 --- a/src/TrackingOptimization/TrackingOptimizationTool.m +++ b/src/TrackingOptimization/TrackingOptimizationTool.m @@ -32,13 +32,7 @@ function TrackingOptimizationTool(settingsFileName) settingsTree = xml2struct(settingsFileName); [inputs, params] = parseTrackingOptimizationSettingsTree(settingsTree); - -tic - [outputs, inputs] = TrackingOptimization(inputs, params); - -toc - reportTreatmentOptimizationResults(outputs, inputs); saveTrackingOptimizationResults(outputs, inputs); end diff --git a/src/core/TreatmentOptimization/inverseDynamics.m b/src/core/TreatmentOptimization/inverseDynamics.m index 8f2042343..beafa074e 100644 --- a/src/core/TreatmentOptimization/inverseDynamics.m +++ b/src/core/TreatmentOptimization/inverseDynamics.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Spencer Williams % +% Author(s): Spencer Williams, Marleny Vega % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -25,13 +25,16 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function IDLoads = inverseDynamics(time,q,qp,qpp,IKLabels,AppliedLoads, ... - modelFile) +function inverseDynamicMoments = inverseDynamics(time, jointAngles, ... + jointVelocities, jointAccelerations, coordinateLabels, appliedLoads, ... + modelName) if isequal(mexext, 'mexw64') - IDLoads = inverseDynamicsMexWindows(time,q,qp,qpp,IKLabels, ... - AppliedLoads); + inverseDynamicMoments = inverseDynamicsMexWindows(time, jointAngles, ... + jointVelocities, jointAccelerations, coordinateLabels, ... + appliedLoads); else - IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels, ... - AppliedLoads,modelFile); + inverseDynamicMoments = inverseDynamicsMatlabParallel(time, ... + jointAngles, jointVelocities, jointAccelerations, coordinateLabels, ... + appliedLoads, modelName); end end diff --git a/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m b/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m index 8ced4df1a..eab94a3fd 100644 --- a/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m +++ b/src/core/TreatmentOptimization/inverseDynamicsMatlabParallel.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Spencer Williams % +% Author(s): Spencer Williams, Marleny Vega % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -25,91 +25,85 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function IDLoads = inverseDynamicsMatlabParallel(time,q,qp,qpp,IKLabels,AppliedLoads,modelFile) +function inverseDynamicMoments = inverseDynamicsMatlabParallel(time, ... + jointAngles, jointVelocities, jointAccelerations, coordinateLabels, ... + appliedLoads, modelName) % Get the number of coords and markers numPts = size(time,1); -numControls = size(AppliedLoads,2); -numCoords = length(IKLabels); - +numControls = size(appliedLoads,2); +numCoords = length(coordinateLabels); % Split time points into parallel problems numWorkers = gcp().NumWorkers; -IDLoadsJobs = cell(1, numWorkers); -AccelsTempVec = cell(1, numWorkers); - +inverseDynamicJobs = cell(1, numWorkers); +accelsTempVec = cell(1, numWorkers); parfor worker = 1:numWorkers - IDLoadsJobs{worker} = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec{worker},time,q,qp,qpp,IKLabels,AppliedLoads,worker); + inverseDynamicJobs{worker} = idWorkerHelper(modelName, numPts, ... + numControls, numCoords, numWorkers, accelsTempVec{worker}, ... + time, jointAngles, jointVelocities, jointAccelerations, ... + coordinateLabels,appliedLoads,worker); end - -IDLoads = IDLoadsJobs{1}; +inverseDynamicMoments = inverseDynamicJobs{1}; for job = 2 : numWorkers - IDLoads = cat(1, IDLoads, IDLoadsJobs{job}); + inverseDynamicMoments = cat(1, inverseDynamicMoments, ... + inverseDynamicJobs{job}); end - end - -function IDLoadsJob = idWorkerHelper(modelFile, numPts, numControls, numCoords, numWorkers, AccelsTempVec,time,q,qp,qpp,IKLabels,AppliedLoads,worker) +function inverseDynamicJobs = idWorkerHelper(modelName, numPts, ... + numControls, numCoords, numWorkers, accelsTempVec, time, jointAngles, ... + jointVelocities, jointAccelerations, coordinateLabels, appliedLoads, ... + worker) - import org.opensim.modeling.*; - persistent osimModel; - persistent osimState; - persistent idSolver; - if isempty(osimModel) - osimModel = Model(modelFile); - osimState = osimModel.initSystem(); - idSolver = InverseDynamicsSolver(osimModel); - end - - IDLoadsJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numCoords); - - indexOffset = (worker - 1) * ceil(numPts / numWorkers); - - for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) - osimState.setTime(time(j,1)); - for k=1:size(IKLabels,2) - if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked - osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); - osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); - end - end - osimModel.realizeVelocity(osimState); - - for i=1:osimState.getNQ - StateQ = osimState.getQ.get(i-1); - - for ii = 1:size(q,2) - if abs(q(j,ii)-StateQ) <= 1e-6 - AccelsTempVec(j-indexOffset,i) = qpp(j,ii); - end - end - end - - newControls = Vector(numControls,0); - - for i=0:numControls-1 - newControls.set(i,AppliedLoads(j,i+1)); +import org.opensim.modeling.*; +persistent osimModel; +persistent osimState; +persistent inverseDynamicsSolver; +if isempty(osimModel) + osimModel = Model(modelName); + osimState = osimModel.initSystem(); + inverseDynamicsSolver = InverseDynamicsSolver(osimModel); +end +inverseDynamicJobs = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : ... + min(worker * ceil(numPts / numWorkers), numPts)), numCoords); +indexOffset = (worker - 1) * ceil(numPts / numWorkers); +for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) + osimState.setTime(time(j,1)); + for k=1:size(coordinateLabels,2) + if ~osimModel.getCoordinateSet.get(coordinateLabels{k}).get_locked + osimModel.getCoordinateSet.get(coordinateLabels{k}). ... + setValue(osimState,jointAngles(j, k)); + osimModel.getCoordinateSet.get(coordinateLabels{k}). ... + setSpeedValue(osimState,jointVelocities(j, k)); end - - osimModel.setControls(osimState,newControls); - osimModel.markControlsAsValid(osimState); - osimModel.realizeDynamics(osimState); - - AccelsVec = Vector(osimState.getNQ,0); - - includedQIndex = 1; - for i=0:osimState.getNQ-1 - currentCoordinate = osimModel.getCoordinateSet().get(i).getName().toCharArray'; - if any(cellfun(@isequal, IKLabels, repmat({currentCoordinate}, 1, length(IKLabels)))) - AccelsVec.set(i,AccelsTempVec(j-indexOffset,includedQIndex)); - includedQIndex = includedQIndex + 1; + end + osimModel.realizeVelocity(osimState); + for i=1:osimState.getNQ + statePositions = osimState.getQ.get(i-1); + for ii = 1:size(jointAngles,2) + if abs(jointAngles(j, ii) - statePositions) <= 1e-6 + accelsTempVec(j - indexOffset, i) = jointAccelerations(j, ii); end end - - IDLoadsVec = idSolver.solve(osimState,AccelsVec); - - for i=0:numCoords-1 - IDLoadsJob(j-indexOffset,i+1) = IDLoadsVec.get(i); + end + newControls = Vector(numControls,0); + for i=0 : numControls - 1 + newControls.set(i, appliedLoads(j, i + 1)); + end + osimModel.setControls(osimState, newControls); + osimModel.markControlsAsValid(osimState); + osimModel.realizeDynamics(osimState); + accelsVec = Vector(osimState.getNQ, 0); + includedQIndex = 1; + for i=0:osimState.getNQ-1 + currentCoordinate = osimModel.getCoordinateSet().get(i).getName().toCharArray'; + if any(cellfun(@isequal, coordinateLabels, repmat({currentCoordinate}, 1, length(coordinateLabels)))) + accelsVec.set(i, accelsTempVec(j - indexOffset, includedQIndex)); + includedQIndex = includedQIndex + 1; end end - + IDLoadsVec = inverseDynamicsSolver.solve(osimState,accelsVec); + for i=0 : numCoords - 1 + inverseDynamicJobs(j - indexOffset, i + 1) = IDLoadsVec.get(i); + end +end end diff --git a/src/core/TreatmentOptimization/pointKinematics.m b/src/core/TreatmentOptimization/pointKinematics.m index 848b77450..e0c77bd21 100644 --- a/src/core/TreatmentOptimization/pointKinematics.m +++ b/src/core/TreatmentOptimization/pointKinematics.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Spencer Williams % +% Author(s): Spencer Williams, Marleny Vega % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -25,13 +25,16 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function [SpringPos, SpringVel] = pointKinematics(time,q,qp,SpringMat,SpringBodyMat,... - modelFile,IKLabels) +function [pointPositions, pointVelocities] = pointKinematics(time, ... + jointAngles,jointVelocities, pointLocationOnBody, body, modelName, ... + coordinateLabels) if isequal(mexext, 'mexw64') - [SpringPos, SpringVel] = pointKinematicsMexWindows(time,q,qp, ... - SpringMat',SpringBodyMat,IKLabels); + [pointPositions, pointVelocities] = pointKinematicsMexWindows(time, ... + jointAngles, jointVelocities, pointLocationOnBody', body, ... + coordinateLabels); else - [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp, ... - SpringMat,SpringBodyMat,modelFile,IKLabels); + [pointPositions, pointVelocities] = pointKinematicsMatlabParallel(time, ... + jointAngles, jointVelocities, pointLocationOnBody, body, modelName, ... + coordinateLabels); end end diff --git a/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m b/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m index 1a33263ab..737f13c33 100644 --- a/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m +++ b/src/core/TreatmentOptimization/pointKinematicsMatlabParallel.m @@ -11,7 +11,7 @@ % National Institutes of Health (R01 EB030520). % % % % Copyright (c) 2021 Rice University and the Authors % -% Author(s): Spencer Williams % +% Author(s): Spencer Williams, Marleny Vega % % % % Licensed under the Apache License, Version 2.0 (the "License"); % % you may not use this file except in compliance with the License. % @@ -25,75 +25,79 @@ % permissions and limitations under the License. % % ----------------------------------------------------------------------- % -function [SpringPos, SpringVel] = pointKinematicsMatlabParallel(time,q,qp,SpringMat,SpringBodyMat,... - modelFile,IKLabels) +function [pointPositions, pointVelocities] = ... + pointKinematicsMatlabParallel(time, jointAngles, jointVelocities, ... + pointLocationOnBody, body, modelName, coordinateNames) % Get the number of coords and markers numPts = size(time,1); -numSprings = size(SpringMat,1); - +numSprings = size(pointLocationOnBody,1); % Split time points into parallel problems numWorkers = gcp().NumWorkers; -SpringPosJobs = cell(1, numWorkers); -SpringVelJobs = cell(1, numWorkers); - +pointPositionsJobs = cell(1, numWorkers); +pointVelocitiesJobs = cell(1, numWorkers); parfor worker = 1:numWorkers - [SpringPosJobs{worker}, SpringVelJobs{worker}] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker); + [pointPositionsJobs{worker}, pointVelocitiesJobs{worker}] = ... + pointKinematicsWorkerHelper(modelName, numPts, numSprings, ... + numWorkers, time, jointAngles, jointVelocities, ... + pointLocationOnBody, body, coordinateNames, worker); end - -SpringPos = SpringPosJobs{1}; -SpringVel = SpringVelJobs{1}; +pointPositions = pointPositionsJobs{1}; +pointVelocities = pointVelocitiesJobs{1}; for job = 2 : numWorkers - SpringPos = cat(1, SpringPos, SpringPosJobs{job}); - SpringVel = cat(1, SpringVel, SpringVelJobs{job}); + pointPositions = cat(1, pointPositions, pointPositionsJobs{job}); + pointVelocities = cat(1, pointVelocities, pointVelocitiesJobs{job}); end - end -function [SpringPosJob, SpringVelJob] = pointKinematicsWorkerHelper(modelFile, numPts, numSprings, numWorkers, time,q,qp,SpringMat,SpringBodyMat,IKLabels,worker) - - import org.opensim.modeling.*; - persistent osimModel; - persistent osimState; - persistent refBodySet; - if isempty(osimModel) - osimModel = Model(modelFile); - osimState = osimModel.initSystem(); - refBodySet = osimModel.getBodySet; - end - - SpringPosJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); - SpringVelJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); - indexOffset = (worker - 1) * ceil(numPts / numWorkers); - - for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) - osimState.setTime(time(j,1)); - - for k=1:size(IKLabels,2) - if ~osimModel.getCoordinateSet.get(IKLabels{k}).get_locked - osimModel.getCoordinateSet.get(IKLabels{k}).setValue(osimState,q(j,k)); - osimModel.getCoordinateSet.get(IKLabels{k}).setSpeedValue(osimState,qp(j,k)); - end +function [SpringPosJob, SpringVelJob] = ... + pointKinematicsWorkerHelper(modelFile, numPts, numSprings, ... + numWorkers, time, jointAngles, jointVelocities, pointLocationOnBody, ... + body, coordinateNames, worker) + +import org.opensim.modeling.*; +persistent osimModel; +persistent osimState; +persistent refBodySet; +if isempty(osimModel) + osimModel = Model(modelFile); + osimState = osimModel.initSystem(); + refBodySet = osimModel.getBodySet; +end +SpringPosJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : ... + min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); +SpringVelJob = zeros(length(1 + (worker - 1) * ceil(numPts / numWorkers) : ... + min(worker * ceil(numPts / numWorkers), numPts)), numSprings * 3); +indexOffset = (worker - 1) * ceil(numPts / numWorkers); +for j = 1 + (worker - 1) * ceil(numPts / numWorkers) : min(worker * ceil(numPts / numWorkers), numPts) + osimState.setTime(time(j,1)); + for k=1:size(coordinateNames,2) + if ~osimModel.getCoordinateSet.get(coordinateNames{k}).get_locked + osimModel.getCoordinateSet.get(coordinateNames{k}). ... + setValue(osimState,jointAngles(j,k)); + osimModel.getCoordinateSet.get(coordinateNames{k}). ... + setSpeedValue(osimState,jointVelocities(j,k)); end - osimModel.realizeVelocity(osimState); - - for i=1:numSprings - tempRefParentBody = refBodySet.get(SpringBodyMat(i)); - tempLocalPos = Vec3(3,0); - tempGlobalPos = Vec3(3,0); - tempGlobalVel = Vec3(3,0); - tempLocalPos.set(0,SpringMat(i,1)); - tempLocalPos.set(1,SpringMat(i,2)); - tempLocalPos.set(2,SpringMat(i,3)); - - osimModel.getSimbodyEngine.getPosition(osimState,tempRefParentBody,tempLocalPos,tempGlobalPos); - osimModel.getSimbodyEngine.getVelocity(osimState,tempRefParentBody,tempLocalPos,tempGlobalVel); - - for k=0:2 - SpringPosJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalPos.get(k); - SpringVelJob(j-indexOffset,(i-1)*3+k+1)=tempGlobalVel.get(k); - end + end + osimModel.realizeVelocity(osimState); + for i=1:numSprings + tempRefParentBody = refBodySet.get(body(i)); + tempLocalPos = Vec3(3, 0); + tempGlobalPos = Vec3(3, 0); + tempGlobalVel = Vec3(3, 0); + tempLocalPos.set(0,pointLocationOnBody(i, 1)); + tempLocalPos.set(1,pointLocationOnBody(i, 2)); + tempLocalPos.set(2,pointLocationOnBody(i, 3)); + osimModel.getSimbodyEngine.getPosition(osimState, ... + tempRefParentBody, tempLocalPos, tempGlobalPos); + osimModel.getSimbodyEngine.getVelocity(osimState, ... + tempRefParentBody, tempLocalPos, tempGlobalVel); + for k=0:2 + SpringPosJob(j - indexOffset, (i - 1) * 3 + k + 1) = ... + tempGlobalPos.get(k); + SpringVelJob(j - indexOffset, (i - 1) * 3 + k + 1) = ... + tempGlobalVel.get(k); end end - +end end From a2111ac26700b95cbf44fef4b297a1b44f7c7987 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 12 Jun 2023 11:57:04 -0500 Subject: [PATCH 62/72] Update SynX bounds --- .../calcMuscleExcitationsSynX.m | 90 +++++++++---------- .../getLinearInequalityConstraints.m | 33 +++++-- 2 files changed, 69 insertions(+), 54 deletions(-) diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m index 2cf57d5e5..eae6b2404 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/calcMuscleExcitationsSynX.m @@ -7,11 +7,11 @@ % data: % time - timestamp vectors % timeDelay - electromechanical delay -% emgScalingFactor - EMG scaling factors +% emgScalingFactor - EMG scaling factors % synergyExtrapolation - vector for SynX (including both SynX weights and residual weights) % experimentalData.extrapolationCommands - synergy excitations extracted from measured muscle % excitations for SynX -% experimentalData.residualCommands - synergy excitations extracted from measured muscle excitations +% experimentalData.residualCommands - synergy excitations extracted from measured muscle excitations % for residual muscle excitations % params.missingEmgChannelGroups - Index for expanding SynX EMG to unmeasured muscles % MeasuredMuscIndexExpand - Index for expanding residual EMG to measured muscles @@ -22,9 +22,9 @@ % size(params.taskNames,2) - number of tasks - double % experimentalData.emgData - number of time frames for each trial - double % params.matrixFactorizationMethod - matrix factorization algorithm - 'PCA' or 'NMF' -% params.synergyExtrapolationCategorization - variability of synergy vector weights across trials for +% params.synergyExtrapolationCategorization - variability of synergy vector weights across trials for % SynX reconstruction -% params.residualCategorization - variability of synergy vector weights across trials for +% params.residualCategorization - variability of synergy vector weights across trials for % residual excitation reconstruction % TrialIndex - trial index for each task - cell array (nTask cells) % nOtherDesignVariables - number of other design variables - double @@ -56,7 +56,7 @@ function [muscleExcitations, muscleExcitationsNoTDelay] = ... calcMuscleExcitationsSynX(experimentalData, timeDelay, ... - emgScalingFactor, synergyExtrapolationVariables, params) + emgScalingFactor, synergyExtrapolationVariables, params) experimentalData.emgDataExpanded = round(experimentalData.emgDataExpanded, 4); @@ -79,10 +79,10 @@ % Create EMG splines emgSplines = createEmgSignals(muscleExcitationsNoTDelay, ... experimentalData.emgTime, timeDelay); -% Interpolate Emg +% Interpolate Emg muscleExcitations = evaluateEmgSplines(experimentalData.emgTime, ... emgSplines, timeDelay); -% muscleExcitations = permute(muscleExcitations,[1 3 2]); +% muscleExcitations = permute(muscleExcitations,[1 3 2]); end function UnmeasuredExcitations = getUnmeasuredMuscleExcitations(params, ... @@ -93,33 +93,33 @@ size(params.missingEmgChannelGroups,2), ... length(params.synergyCategorizationOfTrials)); for j = 1:size(params.missingEmgChannelGroups,2) -if strcmpi(params.matrixFactorizationMethod,'PCA') - SynxVars = reshape(extrapolationWeights',(params.numberOfSynergies + 1) * ... - length(params.synergyCategorizationOfTrials), ... - size(params.missingEmgChannelGroups, 2))'; - for i = 1 : length(params.synergyCategorizationOfTrials) - UnmeasuredExcitations(:, j, i) = extrapolationCommands{i} * SynxVars(j, ... - (i - 1) * (params.numberOfSynergies + 1) + 1 : (i - 1) * ... - (params.numberOfSynergies + 1) + params.numberOfSynergies)' + ... - repmat(SynxVars(j, i * (params.numberOfSynergies + 1)), ... - size(emgData, 3) * ... - size(params.synergyCategorizationOfTrials{i}, 2), 1); - end -elseif strcmp(params.matrixFactorizationMethod,'NMF') - SynxVars = reshape(synxVars', params.numberOfSynergies * ... - length(params.synergyCategorizationOfTrials), ... - size(params.missingEmgChannelGroups, 2))'; - for i = 1 : length(params.synergyCategorizationOfTrials) - UnmeasuredExcitations = zeros(emgData * size( ... - params.synergyCategorizationOfTrials{i}, 2), ... - size(params.missingEmgChannelGroups, ... - 2), length(params.synergyCategorizationOfTrials)); - UnmeasuredExcitations(:, j, i) = extrapolationCommands{i} * ... - SynxVars(j, (i - 1) * params.numberOfSynergies + 1 : ... - i * params.numberOfSynergies, :)'; + if strcmpi(params.matrixFactorizationMethod,'PCA') + SynxVars = reshape(extrapolationWeights',(params.numberOfSynergies + 1) * ... + length(params.synergyCategorizationOfTrials), ... + size(params.missingEmgChannelGroups, 2))'; + for i = 1 : length(params.synergyCategorizationOfTrials) + UnmeasuredExcitations(:, j, i) = extrapolationCommands{i} * SynxVars(j, ... + (i - 1) * (params.numberOfSynergies + 1) + 1 : (i - 1) * ... + (params.numberOfSynergies + 1) + params.numberOfSynergies)' + ... + repmat(SynxVars(j, i * (params.numberOfSynergies + 1)), ... + size(emgData, 3) * ... + size(params.synergyCategorizationOfTrials{i}, 2), 1); + end + elseif strcmp(params.matrixFactorizationMethod,'NMF') + SynxVars = reshape(synxVars', params.numberOfSynergies * ... + length(params.synergyCategorizationOfTrials), ... + size(params.missingEmgChannelGroups, 2))'; + for i = 1 : length(params.synergyCategorizationOfTrials) + UnmeasuredExcitations = zeros(emgData * size( ... + params.synergyCategorizationOfTrials{i}, 2), ... + size(params.missingEmgChannelGroups, ... + 2), length(params.synergyCategorizationOfTrials)); + UnmeasuredExcitations(:, j, i) = extrapolationCommands{i} * ... + SynxVars(j, (i - 1) * params.numberOfSynergies + 1 : ... + i * params.numberOfSynergies, :)'; + end end end -end UnmeasuredExcitations = permute(UnmeasuredExcitations, [3 2 1]); end @@ -146,7 +146,7 @@ end concatResidualExcitations = []; for i = 1:size(params.residualCategorizationOfTrials, 2) - concatResidualExcitations = [concatResidualExcitations; + concatResidualExcitations = [concatResidualExcitations; residualExcitations{i}]; end residualExcitations = concatResidualExcitations; @@ -155,11 +155,11 @@ function emgData = updateEmgSignals(missingEmgChannelGroups, emgData, ... unmeasuredEmgSignals) -for i = 1 : size(missingEmgChannelGroups, 2) -for j = 1 : size(missingEmgChannelGroups{i}, 2) - emgData(:, missingEmgChannelGroups{i}(1, j), :) = ... - unmeasuredEmgSignals(:, i, :); -end +for i = 1 : size(missingEmgChannelGroups, 2) + for j = 1 : size(missingEmgChannelGroups{i}, 2) + emgData(:, missingEmgChannelGroups{i}(1, j), :) = ... + unmeasuredEmgSignals(:, i, :); + end end end @@ -168,12 +168,12 @@ %Distribute residual muscle excitations to measured muscles residualExcitationsExpanded = zeros(size(emgData, 3) * ... -size(emgData, 1), size(emgData, 2)); + size(emgData, 1), size(emgData, 2)); for i = 1 : size(currentEmgChannelGroups, 2) -for j = 1 : size(currentEmgChannelGroups{i}, 2) - residualExcitationsExpanded(:, currentEmgChannelGroups{i}(1, j)) = ... - residualExcitations(:, i); -end + for j = 1 : size(currentEmgChannelGroups{i}, 2) + residualExcitationsExpanded(:, currentEmgChannelGroups{i}(1, j)) = ... + residualExcitations(:, i); + end end residualExcitationsExpanded = reshape(residualExcitationsExpanded, ... size(emgData, 3), size(emgData, 1), size(emgData, 2)); @@ -186,13 +186,13 @@ if size(timeDelay, 2) <= 2 for j = 1 : size(emgData, 1) emgSplines{j} = spline(emgTime(j, 1 : 4 : end), ... - emgData(j, :, 1 : 4 : end)'); + emgData(j, :, 1 : 4 : end)'); end else for i = 1 : size(emgData, 2) for j = 1:size(emgData, 1) emgSplines{j, i} = spline(emgTime(j, 1 : 4 : end), ... - emgData(j, i, 1 : 4 : end)); + emgData(j, i, 1 : 4 : end)); end end end diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m index a73c6776d..1814bb712 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m @@ -74,17 +74,32 @@ fullCommands = cat(1, fullCommands, synergyCommands{i}); end fullCommands(:, end+1) = ones(size(fullCommands, 1), 1); +fullEmg = reshape(emgData, size(emgData, 1) * size(emgData, 2), ... + size(emgData, 3)); -totalGroups = length(params.missingEmgChannelGroups) + ... - length(params.currentEmgChannelGroups); -A = zeros(totalGroups * size(fullCommands, 1), numOtherDesignVariables + totalGroups * size(fullCommands, 2)); -for i = 1:totalGroups - A(size(fullCommands, 1) * (i - 1) + 1 : size(fullCommands, 1) * i, ... - numOtherDesignVariables + size(fullCommands, 2) * (i - 1) + 1 : ... - numOtherDesignVariables + size(fullCommands, 2) * i) = fullCommands; +if strcmp(params.matrixFactorizationMethod, 'PCA') + totalGroups = length(params.missingEmgChannelGroups) + ... + length(params.currentEmgChannelGroups); + A = zeros(totalGroups * size(fullCommands, 1), ... + numOtherDesignVariables + totalGroups * size(fullCommands, 2)); + for i = 1:totalGroups + A(size(fullCommands, 1) * (i - 1) + 1 : size(fullCommands, 1) * i, ... + numOtherDesignVariables + size(fullCommands, 2) * (i - 1) + 1 : ... + numOtherDesignVariables + size(fullCommands, 2) * i) = fullCommands; + end + A = cat(1, A, -A); + + b = ones(length(params.missingEmgChannelGroups) * ... + size(fullCommands, 1), 1); + for i = 1:length(params.currentEmgChannelGroups) + b = [b; 1 - fullEmg(:, i)]; + end + b = [b; zeros(length(params.missingEmgChannelGroups) * ... + size(fullCommands, 1), 1)]; + for i = 1:length(params.currentEmgChannelGroups) + b = [b; fullEmg(:, i)]; + end end -A = cat(1, A, -A); -b = ones(totalGroups * size(fullCommands, 1) * 2, 1); end From e18e260ec4315c07e07bff789120db599688a90e Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Mon, 12 Jun 2023 17:43:56 -0500 Subject: [PATCH 63/72] Fix constraints for multiple trials --- .../SynergyExtrapolation/getLinearInequalityConstraints.m | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m index 1814bb712..9b8227b99 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m @@ -78,7 +78,7 @@ size(emgData, 3)); if strcmp(params.matrixFactorizationMethod, 'PCA') - totalGroups = length(params.missingEmgChannelGroups) + ... + totalGroups = length(params.missingEmgChannelGroups) * size(emgData, 2) + ... length(params.currentEmgChannelGroups); A = zeros(totalGroups * size(fullCommands, 1), ... numOtherDesignVariables + totalGroups * size(fullCommands, 2)); @@ -90,12 +90,12 @@ A = cat(1, A, -A); b = ones(length(params.missingEmgChannelGroups) * ... - size(fullCommands, 1), 1); + size(emgData, 2) * size(fullCommands, 1), 1); for i = 1:length(params.currentEmgChannelGroups) b = [b; 1 - fullEmg(:, i)]; end b = [b; zeros(length(params.missingEmgChannelGroups) * ... - size(fullCommands, 1), 1)]; + size(emgData, 2) * size(fullCommands, 1), 1)]; for i = 1:length(params.currentEmgChannelGroups) b = [b; fullEmg(:, i)]; end From cb392aa0b7cbe2603f9a0d993012c1da2ecea58b Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 13 Jun 2023 12:26:10 -0500 Subject: [PATCH 64/72] Fixed ValueStruct --- src/TrackingOptimization/getTrackingOptimizationValueStruct.m | 2 -- .../getTreatmentOptimizationValueStruct.m | 4 +++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m index 520f586fe..80d2cb2f4 100644 --- a/src/TrackingOptimization/getTrackingOptimizationValueStruct.m +++ b/src/TrackingOptimization/getTrackingOptimizationValueStruct.m @@ -37,7 +37,5 @@ values.synergyWeights = getSynergyWeightsFromGroups(... params.synergyWeightsGuess, params); end - values.controlSynergyActivations = inputs.control(:, ... - params.numCoordinates + 1 : params.numCoordinates + params.numSynergies); end end diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m b/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m index 1a601ed5d..254ee91a0 100644 --- a/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationValueStruct.m @@ -41,6 +41,8 @@ if ~strcmp(params.controllerType, 'synergy_driven') values.controlTorques = control(:, params.numCoordinates + 1 : ... params.numCoordinates + params.numTorqueControls); +else + values.controlSynergyActivations = control(:, ... + params.numCoordinates + 1 : params.numCoordinates + params.numSynergies); end - end From dc16caa725b70f55d5b594542f11dc6d18eeb11c Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Tue, 13 Jun 2023 14:39:20 -0500 Subject: [PATCH 65/72] Fix saving MTP without precal --- .../MuscleTendonPersonalizationTool.m | 2 + .../getLinearInequalityConstraints.m | 98 +++++++++---------- 2 files changed, 51 insertions(+), 49 deletions(-) diff --git a/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m b/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m index 26bf1cf4b..a17db3f4f 100644 --- a/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m +++ b/src/MuscleTendonPersonalization/MuscleTendonPersonalizationTool.m @@ -38,6 +38,8 @@ function MuscleTendonPersonalizationTool(settingsFileName) optimizedInitialGuess = MuscleTendonLengthInitialization(precalInputs); inputs = updateMtpInitialGuess(inputs, precalInputs, ... optimizedInitialGuess); +else + precalInputs = struct('optimizeIsometricMaxForce', false); end optimizedParams = MuscleTendonPersonalization(inputs, params); diff --git a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m index 9b8227b99..67f50956e 100644 --- a/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m +++ b/src/MuscleTendonPersonalization/SynergyExtrapolation/getLinearInequalityConstraints.m @@ -49,57 +49,57 @@ function [A,b] = getLinearInequalityConstraints(params, ... numOtherDesignVariables, synergyCommands, emgData) -% if strcmp(params.matrixFactorizationMethod, 'PCA') -% matrixFactorizationFactor = 1; -% elseif strcmp(params.matrixFactorizationMethod, 'NMF') -% matrixFactorizationFactor = 0; -% end -% % Construct the linear constraint, SynX and Residual A Matrix -% aMatrixSynergy = getSynxAMatrix(params, synergyCommands, ... -% emgData, matrixFactorizationFactor); -% aMatrixResidual = getResidualAMatrix(emgData, params.numberOfSynergies, ... -% params.missingEmgChannelGroups, params.residualCategorizationOfTrials, ... -% matrixFactorizationFactor); -% % Concatenate non-SynX design variables with SynX and Residual Matrices -% A = [zeros(2 * size(params.missingEmgChannelGroups, 2) * size(emgData, 1) * ... -% size(emgData, 2), numOtherDesignVariables) [-aMatrixSynergy ... -% aMatrixResidual; aMatrixSynergy aMatrixResidual]]; -% % b (between 0 and 1) -% b = [zeros(size(emgData, 1) * size(emgData, 2) * size( ... -% params.missingEmgChannelGroups, 2), 1); ones(size(emgData, 1) * ... -% size(emgData, 2) * size(params.missingEmgChannelGroups, 2), 1)]; - -fullCommands = synergyCommands{1}; -for i = 2:length(synergyCommands) - fullCommands = cat(1, fullCommands, synergyCommands{i}); -end -fullCommands(:, end+1) = ones(size(fullCommands, 1), 1); -fullEmg = reshape(emgData, size(emgData, 1) * size(emgData, 2), ... - size(emgData, 3)); - if strcmp(params.matrixFactorizationMethod, 'PCA') - totalGroups = length(params.missingEmgChannelGroups) * size(emgData, 2) + ... - length(params.currentEmgChannelGroups); - A = zeros(totalGroups * size(fullCommands, 1), ... - numOtherDesignVariables + totalGroups * size(fullCommands, 2)); - for i = 1:totalGroups - A(size(fullCommands, 1) * (i - 1) + 1 : size(fullCommands, 1) * i, ... - numOtherDesignVariables + size(fullCommands, 2) * (i - 1) + 1 : ... - numOtherDesignVariables + size(fullCommands, 2) * i) = fullCommands; - end - A = cat(1, A, -A); - - b = ones(length(params.missingEmgChannelGroups) * ... - size(emgData, 2) * size(fullCommands, 1), 1); - for i = 1:length(params.currentEmgChannelGroups) - b = [b; 1 - fullEmg(:, i)]; - end - b = [b; zeros(length(params.missingEmgChannelGroups) * ... - size(emgData, 2) * size(fullCommands, 1), 1)]; - for i = 1:length(params.currentEmgChannelGroups) - b = [b; fullEmg(:, i)]; - end + matrixFactorizationFactor = 1; +elseif strcmp(params.matrixFactorizationMethod, 'NMF') + matrixFactorizationFactor = 0; end +% Construct the linear constraint, SynX and Residual A Matrix +aMatrixSynergy = getSynxAMatrix(params, synergyCommands, ... + emgData, matrixFactorizationFactor); +aMatrixResidual = getResidualAMatrix(emgData, params.numberOfSynergies, ... + params.missingEmgChannelGroups, params.residualCategorizationOfTrials, ... + matrixFactorizationFactor); +% Concatenate non-SynX design variables with SynX and Residual Matrices +A = [zeros(2 * size(params.missingEmgChannelGroups, 2) * size(emgData, 1) * ... + size(emgData, 2), numOtherDesignVariables) [-aMatrixSynergy ... + aMatrixResidual; aMatrixSynergy aMatrixResidual]]; +% b (between 0 and 1) +b = [zeros(size(emgData, 1) * size(emgData, 2) * size( ... + params.missingEmgChannelGroups, 2), 1); ones(size(emgData, 1) * ... + size(emgData, 2) * size(params.missingEmgChannelGroups, 2), 1)]; + +% fullCommands = synergyCommands{1}; +% for i = 2:length(synergyCommands) +% fullCommands = cat(1, fullCommands, synergyCommands{i}); +% end +% fullCommands(:, end+1) = ones(size(fullCommands, 1), 1); +% fullEmg = reshape(emgData, size(emgData, 1) * size(emgData, 2), ... +% size(emgData, 3)); +% +% if strcmp(params.matrixFactorizationMethod, 'PCA') +% totalGroups = length(params.missingEmgChannelGroups) * size(emgData, 2) + ... +% length(params.currentEmgChannelGroups); +% A = zeros(totalGroups * size(fullCommands, 1), ... +% numOtherDesignVariables + totalGroups * size(fullCommands, 2)); +% for i = 1:totalGroups +% A(size(fullCommands, 1) * (i - 1) + 1 : size(fullCommands, 1) * i, ... +% numOtherDesignVariables + size(fullCommands, 2) * (i - 1) + 1 : ... +% numOtherDesignVariables + size(fullCommands, 2) * i) = fullCommands; +% end +% A = cat(1, A, -A); +% +% b = ones(length(params.missingEmgChannelGroups) * ... +% size(emgData, 2) * size(fullCommands, 1), 1); +% for i = 1:length(params.currentEmgChannelGroups) +% b = [b; 1 - fullEmg(:, i)]; +% end +% b = [b; zeros(length(params.missingEmgChannelGroups) * ... +% size(emgData, 2) * size(fullCommands, 1), 1)]; +% for i = 1:length(params.currentEmgChannelGroups) +% b = [b; fullEmg(:, i)]; +% end +% end end From 89844cef532845e461d8348f60ea6af65550e90d Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Tue, 13 Jun 2023 14:50:53 -0500 Subject: [PATCH 66/72] Use excitation penalty --- src/MuscleTendonPersonalization/Optimization/calcMtpCost.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m b/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m index 7ec645d8d..a1783a618 100644 --- a/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m +++ b/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m @@ -76,7 +76,7 @@ synxModeledValues, modeledValues, experimentalData, costTerm); case "muscle_excitation_penalty" cost = calcMuscleExcitationPenaltyCost( ... - synxModeledValues, experimentalData, costTerm); + synxModeledValues, experimentalData);%, costTerm); otherwise throw(MException('', 'Cost term %s is not valid for MTP', ... costTerm.type)) From fd378e698a4cb56f7790ecaacfc877dfa0f19db0 Mon Sep 17 00:00:00 2001 From: "Claire V. Hammond" <61138239+cvhammond@users.noreply.github.com> Date: Tue, 13 Jun 2023 15:57:13 -0500 Subject: [PATCH 67/72] fixed exception on NCP parse --- .../parseNeuralControlPersonalizationSettingsTree.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m index 6aa280996..6efe6a877 100644 --- a/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m +++ b/src/NeuralControlPersonalization/parseNeuralControlPersonalizationSettingsTree.m @@ -68,7 +68,7 @@ parseMtpStandard(findFileListFromPrefixList( ... fullfile(mtpResultsDirectory, "muscleActivations"), inputs.prefixes)); osimxFileName = getFieldByName(tree, "input_osimx_file"); -if ~isstruct(osimxFileName) +if ~isstruct(osimxFileName) || isempty(osimxFileName.Text) throw(MException('', 'An input .osimx file is required if using data from MTP.')) end inputs.mtpMuscleData = parseOsimxFile(osimxFileName.Text); From c9c8943a12c6ef9b5008bfbc3aab687fc8484247 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 13 Jun 2023 16:31:10 -0500 Subject: [PATCH 68/72] Quick fix to free final time range setup --- src/DesignOptimization/computeDesignOptimizationMainFunction.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DesignOptimization/computeDesignOptimizationMainFunction.m b/src/DesignOptimization/computeDesignOptimizationMainFunction.m index 8d14b3432..990a459f9 100644 --- a/src/DesignOptimization/computeDesignOptimizationMainFunction.m +++ b/src/DesignOptimization/computeDesignOptimizationMainFunction.m @@ -64,7 +64,7 @@ end end if isfield(inputs, "finalTimeRange") - bounds.phase.finaltime.lower = (0.5 - guess.phase.time(end)) + 0.5; + bounds.phase.finaltime.lower = guess.phase.time(end) - (0.5 - guess.phase.time(end)); bounds.phase.finaltime.upper = 0.5; end end From 07a56946959cd37102cc72b99d3f169e24edfb04 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 13 Jun 2023 23:20:21 -0500 Subject: [PATCH 69/72] Fixed reporting of userDefinedVariables --- .../getDesignOptimizationValueStruct.m | 12 +++++++----- .../reportTreatmentOptimizationResults.m | 13 +++++++------ 2 files changed, 14 insertions(+), 11 deletions(-) diff --git a/src/DesignOptimization/getDesignOptimizationValueStruct.m b/src/DesignOptimization/getDesignOptimizationValueStruct.m index ada0f9704..783581645 100644 --- a/src/DesignOptimization/getDesignOptimizationValueStruct.m +++ b/src/DesignOptimization/getDesignOptimizationValueStruct.m @@ -45,10 +45,12 @@ fnval(params.splineSynergyActivations, values.time); end end -for i = 1:length(params.userDefinedVariables) - values.(params.userDefinedVariables{i}.type) = scaleToOriginal( ... - inputs.parameter(i, 1), ... - params.userDefinedVariables{i}.upper_bounds, ... - params.userDefinedVariables{i}.lower_bounds); +if isfield(params, 'userDefinedVariables') + for i = 1:length(params.userDefinedVariables) + values.(params.userDefinedVariables{i}.type) = scaleToOriginal( ... + inputs.parameter(i, 1), ... + params.userDefinedVariables{i}.upper_bounds, ... + params.userDefinedVariables{i}.lower_bounds); + end end end diff --git a/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m b/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m index c5146273a..ca02c6ac3 100644 --- a/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m +++ b/src/core/TreatmentOptimization/reportTreatmentOptimizationResults.m @@ -26,12 +26,13 @@ % ----------------------------------------------------------------------- % function reportTreatmentOptimizationResults(solution, inputs) - -for i = 1:length(inputs.userDefinedVariables) - parameterResults = scaleToOriginal( ... - solution.solution.phase.parameter(i, 1), ... - inputs.userDefinedVariables{i}.upper_bounds, ... - inputs.userDefinedVariables{i}.lower_bounds) +if isfield(inputs, 'userDefinedVariables') + for i = 1:length(inputs.userDefinedVariables) + parameterResults = scaleToOriginal( ... + solution.solution.phase.parameter(i, 1), ... + inputs.userDefinedVariables{i}.upper_bounds, ... + inputs.userDefinedVariables{i}.lower_bounds) + end end values = getDesignOptimizationValueStruct(solution.solution.phase, inputs); if strcmp(inputs.controllerType, 'synergy_driven') From 8c8c5156b6c935861016cf0db4230de2737a0df9 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 13 Jun 2023 23:20:36 -0500 Subject: [PATCH 70/72] Fixed experimentalTime --- src/DesignOptimization/parseDesignOptimizationSettingsTree.m | 2 -- src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m index 672766fb8..84cce51f7 100644 --- a/src/DesignOptimization/parseDesignOptimizationSettingsTree.m +++ b/src/DesignOptimization/parseDesignOptimizationSettingsTree.m @@ -35,8 +35,6 @@ function inputs = getInputs(tree) import org.opensim.modeling.Storage inputs = getTreatmentOptimizationInputs(tree); -inputs.experimentalTime = inputs.experimentalTime / ... - inputs.experimentalTime(end); inputs = getDesignVariableBounds(tree, inputs); if strcmpi(inputs.controllerType, 'synergy_driven') inputs.synergyWeights = parseTreatmentOptimizationStandard(... diff --git a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m index ba90bab8f..5d422a4c8 100644 --- a/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m +++ b/src/core/TreatmentOptimization/getTreatmentOptimizationInputs.m @@ -60,6 +60,8 @@ parseElementTextByNameOrAlternate(tree, "optimizeSynergyVectors", 0)); inputs = parseTreatmentOptimizationDataDirectory(tree, inputs); inputs.initialGuess = getGpopsInitialGuess(tree); +inputs.experimentalTime = inputs.experimentalTime / ... + inputs.experimentalTime(end); inputs.costTerms = parseRcnlCostTermSet( ... getFieldByNameOrError(tree, 'RCNLCostTermSet').RCNLCostTerm); inputs.path = getPathConstraintTerms(tree); From ab77936c763066b8507e67982995b0fd5459d5d8 Mon Sep 17 00:00:00 2001 From: Marleny Vega Date: Tue, 13 Jun 2023 23:21:07 -0500 Subject: [PATCH 71/72] Fixed maxIntegral in VerificationOptimization --- .../calcVerificationOptimizationIntegrand.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m index b2c41c35a..4853284b3 100644 --- a/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m +++ b/src/VerificationOptimization/calcVerificationOptimizationIntegrand.m @@ -31,6 +31,6 @@ generateCostTermStruct("continuous", "VerificationOptimization"); integrand = calcTreatmentOptimizationCost( ... costTermCalculations, allowedTypes, values, modeledValues, auxdata); -integrand = integrand ./ (params.maxIntegral - params.minIntegral); +integrand = integrand ./ (auxdata.maxIntegral - auxdata.minIntegral); integrand = integrand .^ 2; end \ No newline at end of file From 5cac3876cd482683cd42c2fc1a27858ce580af00 Mon Sep 17 00:00:00 2001 From: stwilliams333 Date: Wed, 14 Jun 2023 16:45:15 -0500 Subject: [PATCH 72/72] Excitation cost allowable error and error center from xml --- .../CostTerms/calcMuscleExcitationPenaltyCost.m | 6 ++++-- src/MuscleTendonPersonalization/Optimization/calcMtpCost.m | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/MuscleTendonPersonalization/Optimization/CostTerms/calcMuscleExcitationPenaltyCost.m b/src/MuscleTendonPersonalization/Optimization/CostTerms/calcMuscleExcitationPenaltyCost.m index 5de8a3e2f..505251aee 100644 --- a/src/MuscleTendonPersonalization/Optimization/CostTerms/calcMuscleExcitationPenaltyCost.m +++ b/src/MuscleTendonPersonalization/Optimization/CostTerms/calcMuscleExcitationPenaltyCost.m @@ -27,13 +27,15 @@ function cost = calcMuscleExcitationPenaltyCost(modeledValues, ... experimentalData, costTerm) +errorCenter = valueOrAlternate(costTerm, "errorCenter", 0.5); +maximumAllowableError = valueOrAlternate(costTerm, "maxAllowableError", 0.25); muscleExcitationsConstraint = modeledValues.muscleExcitationsNoTDelay(: , ... setdiff(1 : size(modeledValues.muscleExcitationsNoTDelay, 2), ... [experimentalData.synergyExtrapolation.missingEmgChannelGroups{:}]), ... experimentalData.numPaddingFrames + 1 : ... size(modeledValues.muscleExcitationsNoTDelay, 3) - ... experimentalData.numPaddingFrames); -cost = 120 * (muscleExcitationsConstraint - 0.5) .^ 8; +cost = 30 / maximumAllowableError * (muscleExcitationsConstraint - errorCenter) .^ 8; cost(isnan(cost))=0; -cost = sum([sqrt(0.1) .* cost ].^ 2, 'all'); +cost = sum((sqrt(0.1) .* cost).^ 2, 'all'); end \ No newline at end of file diff --git a/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m b/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m index a1783a618..7ec645d8d 100644 --- a/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m +++ b/src/MuscleTendonPersonalization/Optimization/calcMtpCost.m @@ -76,7 +76,7 @@ synxModeledValues, modeledValues, experimentalData, costTerm); case "muscle_excitation_penalty" cost = calcMuscleExcitationPenaltyCost( ... - synxModeledValues, experimentalData);%, costTerm); + synxModeledValues, experimentalData, costTerm); otherwise throw(MException('', 'Cost term %s is not valid for MTP', ... costTerm.type))