Skip to content

Commit 63d6f9d

Browse files
committed
Merge remote-tracking branch 'remotes/origin/master' into opensim-copy-dependencies-with-wrapping
2 parents 278be94 + d76e5d4 commit 63d6f9d

12 files changed

+1154
-82
lines changed

Bindings/Python/CMakeLists.txt

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -270,15 +270,26 @@ add_test(NAME python_tests
270270
--start-directory "${CMAKE_CURRENT_SOURCE_DIR}/tests"
271271
--verbose
272272
)
273+
274+
# List the examples we want to test. Some examples might run too long for
275+
# testing, or might require additional libraries.
276+
# Leave off the .py extension; here, we are listing module names.
277+
set(PYTHON_EXAMPLES_UNITTEST_ARGS
278+
build_simple_arm_model
279+
extend_OpenSim_Vec3_class
280+
posthoc_StatesTrajectory_example
281+
wiring_inputs_and_outputs_with_TableReporter
282+
)
273283
# Similar as above, but for the example files. These files aren't named as
274284
# test_*.py, so we must specify a more general search pattern.
275285
add_test(NAME python_examples
276286
WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/$<CONFIG>"
277-
COMMAND "${PYTHON_EXECUTABLE}" -m unittest discover
278-
--start-directory "${CMAKE_CURRENT_SOURCE_DIR}/examples"
279-
--pattern *.py
287+
COMMAND "${PYTHON_EXECUTABLE}" -m unittest
288+
${PYTHON_EXAMPLES_UNITTEST_ARGS}
280289
--verbose
281290
)
291+
set_tests_properties(python_examples PROPERTIES
292+
ENVIRONMENT PYTHONPATH=${CMAKE_CURRENT_SOURCE_DIR}/examples)
282293

283294
if(WIN32)
284295
# On Windows, CMake cannot use RPATH to hard code the location of libraries
@@ -288,7 +299,7 @@ if(WIN32)
288299
# want to accidentally use a different OpenSim build/installation somewhere
289300
# on the machine.
290301
foreach(folder tests examples)
291-
set_tests_properties(python_${folder} PROPERTIES ENVIRONMENT
302+
set_property(TEST python_${folder} APPEND PROPERTY ENVIRONMENT
292303
"PATH=${CMAKE_BINARY_DIR}/$<CONFIG>")
293304
endforeach()
294305
endif()

Bindings/Python/examples/dynamic_walker_example_model.osim

Lines changed: 786 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
# ----------------------------------------------------------------------- #
2+
# The OpenSim API is a toolkit for musculoskeletal modeling and #
3+
# simulation. See http://opensim.stanford.edu and the NOTICE file #
4+
# for more information. OpenSim is developed at Stanford University #
5+
# and supported by the US National Institutes of Health (U54 GM072970, #
6+
# R24 HD065690) and by DARPA through the Warrior Web program. #
7+
# #
8+
# Copyright (c) 2005-2019 Stanford University and the Authors #
9+
# Author(s): Thomas Uchida, Akshaykumar Patel, James Dunne #
10+
# Contributor(s): Andrew Miller, Jeff Reinbolt, Ajay Seth, Dan Jacobs, #
11+
# Chris Dembia, Ayman Habib, Carmichael Ong #
12+
# #
13+
# Licensed under the Apache License, Version 2.0 (the "License"); #
14+
# you may not use this file except in compliance with the License. #
15+
# You may obtain a copy of the License at #
16+
# http://www.apache.org/licenses/LICENSE-2.0. #
17+
# #
18+
# Unless required by applicable law or agreed to in writing, software #
19+
# distributed under the License is distributed on an "AS IS" BASIS, #
20+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or #
21+
# implied. See the License for the specific language governing #
22+
# permissions and limitations under the License. #
23+
# ----------------------------------------------------------------------- #
24+
25+
# This example demonstrates how to run an optimization in Python using the
26+
# cma package (see References, below). The model is a dynamic walker (see
27+
# the analogous Matlab example files for details); the optimizer adjusts the
28+
# initial forward velocity of the pelvis and the initial angles and angular
29+
# velocities of the knees and hips to maximize travel distance in 10 seconds.
30+
# Note that the OpenSim model 'dynamic_walker_example_model.osim' must be in
31+
# the same folder as this script and the cma package must have been installed.
32+
# As configured, the optimizer takes about 3 minutes to run on a standard
33+
# laptop (Intel i7, Windows 10, Python 2.7). For demonstration purposes only.
34+
#
35+
# References for cma package:
36+
# [1] Project page -- https://pypi.org/project/cma/
37+
# [2] Implementation -- https://github.com/CMA-ES/pycma
38+
# [3] Documentation -- http://cma.gforge.inria.fr/apidocs-pycma/cma.html
39+
40+
import opensim as osim
41+
import time
42+
import cma
43+
44+
45+
# OBJECTIVE FUNCTION
46+
# Runs a forward simulation using the initial conditions specified in the
47+
# candidate solution vector (candsol) and computes the corresponding
48+
# objective function value (i.e. the final location of the pelvis).
49+
def walker_simulation_objective_function(candsol):
50+
global model, initial_state, all_distances, all_candsols
51+
52+
# Set the initial hip and knee angles.
53+
initial_state.updQ()[3] = candsol[0] # left hip
54+
initial_state.updQ()[4] = candsol[1] # right hip
55+
initial_state.updQ()[5] = candsol[2] # left knee
56+
initial_state.updQ()[6] = candsol[3] # right knee
57+
58+
# Set the initial forward velocity of the pelvis.
59+
vx0 = candsol[4] # needed to compute the objective function, below
60+
initial_state.updU()[1] = vx0
61+
62+
# Set the initial hip and knee angular velocities.
63+
initial_state.updU()[3] = candsol[5] # left hip
64+
initial_state.updU()[4] = candsol[6] # right hip
65+
initial_state.updU()[5] = candsol[7] # left knee
66+
initial_state.updU()[6] = candsol[8] # right knee
67+
68+
# Simulate.
69+
manager = osim.Manager(model)
70+
manager.initialize(initial_state)
71+
manager.integrate(10.0)
72+
73+
# Get the final location of the pelvis in the X direction.
74+
st = manager.getStatesTable()
75+
dc = st.getDependentColumn('/jointset/PelvisToPlatform/Pelvis_tx/value')
76+
x = dc[ dc.nrow()-1 ]
77+
# Store the candidate solution and the distance traveled.
78+
all_candsols.append(candsol)
79+
all_distances.append(x)
80+
print('Distance traveled: %f meters' % (x))
81+
82+
# To maximize distance, minimize its negative. Also penalize candidate
83+
# solutions that increase the initial pelvis velocity beyond 2 m/s (to
84+
# avoid simply launching the model forward). This could also be done by
85+
# adding a hard constraint.
86+
k = 10.0
87+
penalty1 = k*(vx0**2.0) if vx0 < 0 else 0.0 # lower bound: 0 m/s
88+
penalty2 = k*((vx0-2.0)**2.0) if vx0 > 2 else 0.0 # upper bound: 2 m/s
89+
J = -x + penalty1 + penalty2
90+
return (J)
91+
92+
# MAIN
93+
# Perform an optimization using cma with the above objective function. The
94+
# final model will be saved as 'dynamic_walker_example_model_optimized.osim'.
95+
global model, initial_state, all_distances, all_candsols
96+
all_distances = []
97+
all_candsols = []
98+
99+
# Load OpenSim model.
100+
model = osim.Model('dynamic_walker_example_model.osim')
101+
102+
# Create the underlying computational system. Note that we reuse the State
103+
# in the objective function to avoid the high computational cost of calling
104+
# initSystem() before every simulation.
105+
initial_state = model.initSystem()
106+
107+
# Create candidate solution vector. The (arbitrary) initial guess for the
108+
# initial forward velocity of the pelvis is 0.1. If the optimizer is working
109+
# correctly, it should increase this value to 2.0 (the upper bound); see the
110+
# penalty calculation in the objective function.
111+
candsol = []
112+
candsol.append(model.getCoordinateSet().get('LHip_rz').getDefaultValue())
113+
candsol.append(model.getCoordinateSet().get('RHip_rz').getDefaultValue())
114+
candsol.append(model.getCoordinateSet().get('LKnee_rz').getDefaultValue())
115+
candsol.append(model.getCoordinateSet().get('RKnee_rz').getDefaultValue())
116+
candsol.append(0.1)
117+
candsol.append(model.getCoordinateSet().get('LHip_rz').getDefaultSpeedValue())
118+
candsol.append(model.getCoordinateSet().get('RHip_rz').getDefaultSpeedValue())
119+
candsol.append(model.getCoordinateSet().get('LKnee_rz').getDefaultSpeedValue())
120+
candsol.append(model.getCoordinateSet().get('RKnee_rz').getDefaultSpeedValue())
121+
122+
# Optimize.
123+
t_start = time.time()
124+
# For a description of arguments to fmin(), run cma.CMAOptions() or see
125+
# http://cma.gforge.inria.fr/apidocs-pycma/cma.evolution_strategy.html#fmin
126+
result = cma.fmin(walker_simulation_objective_function, candsol, 0.5,
127+
options = {'popsize':20, 'tolfun':1e-3, 'tolx':1e-3,
128+
'maxfevals':100})
129+
t_elapsed = time.time() - t_start
130+
print('Elapsed time: %f seconds' % (t_elapsed))
131+
132+
# Find the best solution.
133+
max_distance = max(all_distances)
134+
print('Best distance: %f meters' % (max_distance))
135+
idx = all_distances.index(max_distance)
136+
bestsol = all_candsols[idx]
137+
print(bestsol)
138+
139+
# Assign best solution to model and save.
140+
model.getCoordinateSet().get('LHip_rz').setDefaultValue(bestsol[0])
141+
model.getCoordinateSet().get('RHip_rz').setDefaultValue(bestsol[1])
142+
model.getCoordinateSet().get('LKnee_rz').setDefaultValue(bestsol[2])
143+
model.getCoordinateSet().get('RKnee_rz').setDefaultValue(bestsol[3])
144+
model.getCoordinateSet().get('Pelvis_tx').setDefaultSpeedValue(bestsol[4])
145+
model.getCoordinateSet().get('LHip_rz').setDefaultSpeedValue(bestsol[5])
146+
model.getCoordinateSet().get('RHip_rz').setDefaultSpeedValue(bestsol[6])
147+
model.getCoordinateSet().get('LKnee_rz').setDefaultSpeedValue(bestsol[7])
148+
model.getCoordinateSet().get('RKnee_rz').setDefaultSpeedValue(bestsol[8])
149+
model.printToXML('dynamic_walker_example_model_optimized.osim')
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# ----------------------------------------------------------------------- #
2+
# The OpenSim API is a toolkit for musculoskeletal modeling and #
3+
# simulation. See http://opensim.stanford.edu and the NOTICE file #
4+
# for more information. OpenSim is developed at Stanford University #
5+
# and supported by the US National Institutes of Health (U54 GM072970, #
6+
# R24 HD065690) and by DARPA through the Warrior Web program. #
7+
# #
8+
# Copyright (c) 2005-2019 Stanford University and the Authors #
9+
# Author(s): Thomas Uchida, Akshaykumar Patel #
10+
# #
11+
# Licensed under the Apache License, Version 2.0 (the "License"); #
12+
# you may not use this file except in compliance with the License. #
13+
# You may obtain a copy of the License at #
14+
# http://www.apache.org/licenses/LICENSE-2.0. #
15+
# #
16+
# Unless required by applicable law or agreed to in writing, software #
17+
# distributed under the License is distributed on an "AS IS" BASIS, #
18+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or #
19+
# implied. See the License for the specific language governing #
20+
# permissions and limitations under the License. #
21+
# ----------------------------------------------------------------------- #
22+
23+
# This example demonstrates how to extend the Vec3 class so that two Vec3s
24+
# can be added in Python using `+` and the result is a Vec3. An analogous
25+
# strategy can be used for other operators.
26+
#
27+
# Reference:
28+
# https://stackoverflow.com/questions/50599045/
29+
# python-replacing-a-function-within-a-class-of-a-module
30+
31+
import opensim as osim
32+
33+
# Define the function that will be used when the `+` operator is called
34+
# with two Vec3s.
35+
def myVec3Add(self,v):
36+
newvec = osim.Vec3(self[0]+v[0], self[1]+v[1], self[2]+v[2])
37+
return newvec
38+
39+
# Assign this function to `operator+` in the existing Vec3 class.
40+
osim.Vec3.__add__ = myVec3Add
41+
42+
# Test.
43+
a = osim.Vec3(1,2,3)
44+
b = osim.Vec3(4,5,6)
45+
c = a+b
46+
print(c)
47+
print(type(c))

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ v4.1
1616
- Reading DataTables from files has been simplified. Reading one table from a file typically uses the Table constructor except when the data-source/file contains multiple tables. (In these cases e.g. C3D files, use C3DFileAdapter.read method, then use functions in C3DFileAdapter to get the individual TimeSeriesTable(s)). Writing tables to files has not changed.
1717
- Exposed convertMillimeters2Meters() in osimC3D.m. This function converts COP and moment data from mm to m and now must be invoked prior to writing force data to file. Previously, this was automatically performed during writing forces to file.
1818
- Methods that operate on SimTK::Vec<n> are now available through Java/Matlab and python bindings to add/subtract/divide/multiply vec<n> contents with a scalar (PR #2558)
19+
- The new Stopwatch class allows C++ API users to easily measure the runtime of their code.
1920

2021
Converting from v4.0 to v4.1
2122
----------------------------
@@ -42,6 +43,7 @@ Other Changes
4243
- Performance of reading large data files has been significantly improved. A 50MB .sto file would take 10-11 min to read now takes 2-3 seconds. (PR #2399)
4344
- Added Matlab example script of plotting the Force-length properties of muscles in a models; creating an Actuator file from a model;
4445
building and simulating a simple arm model; using OutputReporters to record and write marker location and coordinate values to file.
46+
- Added Python example that demonstrates how to run an optimization using the cma package and how to avoid an expensive call to `initSystem()` within the objective function. (PR #2604)
4547
- OpenSim 4.1 ships with Python3 bindings as default. It is still possible to create bindings for Python2 if desired by setting CMake variable OPENSIM_PYTHON_VERSION to 2
4648
- For CMake, the option OPENSIM_COPY_DEPENDENCIES option is now an advanced option, and a warning is provided if this option is off but wrapping is turned on.
4749

OpenSim/Common/Stopwatch.h

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
#ifndef OPENSIM_STOPWATCH_H_
2+
#define OPENSIM_STOPWATCH_H_
3+
/* -------------------------------------------------------------------------- *
4+
* OpenSim: Stopwatch.h *
5+
* -------------------------------------------------------------------------- *
6+
* The OpenSim API is a toolkit for musculoskeletal modeling and simulation. *
7+
* See http://opensim.stanford.edu and the NOTICE file for more information. *
8+
* OpenSim is developed at Stanford University and supported by the US *
9+
* National Institutes of Health (U54 GM072970, R24 HD065690) and by DARPA *
10+
* through the Warrior Web program. *
11+
* *
12+
* Copyright (c) 2005-2019 Stanford University and the Authors *
13+
* Author(s): Christopher Dembia *
14+
* *
15+
* Licensed under the Apache License, Version 2.0 (the "License"); you may *
16+
* not use this file except in compliance with the License. You may obtain a *
17+
* copy of the License at http://www.apache.org/licenses/LICENSE-2.0. *
18+
* *
19+
* Unless required by applicable law or agreed to in writing, software *
20+
* distributed under the License is distributed on an "AS IS" BASIS, *
21+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. *
22+
* See the License for the specific language governing permissions and *
23+
* limitations under the License. *
24+
* -------------------------------------------------------------------------- */
25+
26+
/// Record and report elapsed real time ("clock" or "wall" time) in seconds.
27+
class Stopwatch {
28+
public:
29+
/// This stores the start time as the current time.
30+
Stopwatch() { reset(); }
31+
/// Reset the start time to the current time.
32+
void reset() { m_startTime = SimTK::realTimeInNs(); }
33+
/// Return the amount of time that has elapsed since this object was
34+
/// constructed or since reset() has been called.
35+
double getElapsedTime() const {
36+
return SimTK::realTime() - SimTK::nsToSec(m_startTime);
37+
}
38+
/// Get elapsed time in nanoseconds. See SimTK::realTimeInNs() for more
39+
/// information.
40+
long long getElapsedTimeInNs() const {
41+
return SimTK::realTimeInNs() - m_startTime;
42+
}
43+
/// This provides the elapsed time as a formatted string (using format()).
44+
std::string getElapsedTimeFormatted() const {
45+
return formatNs(getElapsedTimeInNs());
46+
}
47+
/// Format the provided elapsed time in nanoseconds into a string.
48+
/// The time may be converted into seconds, milliseconds, or microseconds.
49+
/// Additionally, if the time is greater or equal to 60 seconds, the time in
50+
/// hours and/or minutes is also added to the string.
51+
/// Usually, you can call getElapsedTimeFormatted() instead of calling this
52+
/// function directly. If you call this function directly, use
53+
/// getElapsedTimeInNs() to get a time in nanoseconds (rather than
54+
/// getElapsedTime()).
55+
static std::string formatNs(const long long& nanoseconds) {
56+
std::stringstream ss;
57+
double seconds = SimTK::nsToSec(nanoseconds);
58+
int secRounded = (int)std::round(seconds);
59+
if (seconds > 1)
60+
ss << secRounded << " second(s)";
61+
else if (nanoseconds >= 1000000)
62+
ss << nanoseconds / 1000000 << " millisecond(s)";
63+
else if (nanoseconds >= 1000)
64+
ss << nanoseconds / 1000 << " microsecond(s)";
65+
else
66+
ss << nanoseconds << " nanosecond(s)";
67+
int minutes = secRounded / 60;
68+
int hours = minutes / 60;
69+
if (minutes || hours) {
70+
ss << " (";
71+
if (hours) {
72+
ss << hours << " hour(s), ";
73+
ss << minutes % 60 << " minute(s), ";
74+
ss << secRounded % 60 << " second(s)";
75+
} else {
76+
ss << minutes % 60 << " minute(s), ";
77+
ss << secRounded % 60 << " second(s)";
78+
}
79+
ss << ")";
80+
}
81+
return ss.str();
82+
}
83+
84+
private:
85+
long long m_startTime;
86+
};
87+
88+
89+
#endif // OPENSIM_STOPWATCH_H_

0 commit comments

Comments
 (0)