Page 1 of 1

flushing and loading models to/from file

Posted: Wed Mar 04, 2020 2:23 pm
by bfcosta
I was playing with one of the example files (this one: ${OPENSIM_HOME}/share/doc/OpenSim/Code/Python/build_simple_arm_model.py) when I realized that I could save it as an XML file to later load this simple model. This is interesting because one can save some coding to build the model. However, the equivalent code of this example -- the one that uses the saved XML file to reload the model -- needs to set again the state of two coordinates which I was expecting to have been previously recorded in the XML file when the model was once saved. I can't get it why do I need to reset these values. It is important for me to understand it, because I'd like to load models from files rather than recreate them step by step in python.
The code below helps to visualize the test I have made. The lines commented with "#~ " are the parts from the original file that builds the model. In the end, there is the code for loading the same model from a file that represents this example. After initializing the system, I still need to reset the state of two joints that I was expecting to have been recorded when the XML was first created.

Code: Select all

import opensim as osim 

#~ arm = osim.Model()
#~ arm.setName("bicep_curl")
#~ arm.setUseVisualizer(True)

#~ # ---------------------------------------------------------------------------
#~ # Create two links, each with a mass of 1 kg, centre of mass at the body's
#~ # origin, and moments and products of inertia of zero.
#~ # ---------------------------------------------------------------------------

#~ humerus = osim.Body("humerus",
                    #~ 1.0,
                    #~ osim.Vec3(0),
                    #~ osim.Inertia(0, 0, 0))
#~ radius = osim.Body("radius",
                   #~ 1.0,
                   #~ osim.Vec3(0),
                   #~ osim.Inertia(0, 0, 0))

#~ # ---------------------------------------------------------------------------
#~ # Connect the bodies with pin joints. Assume each body is 1m long.
#~ # ---------------------------------------------------------------------------

#~ shoulder = osim.PinJoint("shoulder",
                         #~ arm.getGround(), # PhysicalFrame
                         #~ osim.Vec3(0),
                         #~ osim.Vec3(0),
                         #~ humerus, # PhysicalFrame
                         #~ osim.Vec3(0, 1, 0),
                         #~ osim.Vec3(0))

#~ elbow = osim.PinJoint("elbow",
                      #~ humerus, # PhysicalFrame
                      #~ osim.Vec3(0),
                      #~ osim.Vec3(0),
                      #~ radius, # PhysicalFrame
                      #~ osim.Vec3(0, 1, 0),
                      #~ osim.Vec3(0))

#~ # ---------------------------------------------------------------------------
#~ # Add a muscle that flexes the elbow (actuator for robotics people).
#~ # ---------------------------------------------------------------------------

#~ biceps = osim.Millard2012EquilibriumMuscle("biceps",  # Muscle name
                                           #~ 200.0,  # Max isometric force
                                           #~ 0.6,  # Optimal fibre length
                                           #~ 0.55,  # Tendon slack length
                                           #~ 0.0)  # Pennation angle
#~ biceps.addNewPathPoint("origin",
                       #~ humerus,
                       #~ osim.Vec3(0, 0.8, 0))

#~ biceps.addNewPathPoint("insertion",
                       #~ radius,
                       #~ osim.Vec3(0, 0.7, 0))

#~ # ---------------------------------------------------------------------------
#~ # Add a controller that specifies the excitation of the muscle.
#~ # ---------------------------------------------------------------------------

#~ brain = osim.PrescribedController()
#~ brain.addActuator(biceps)
#~ brain.prescribeControlForActuator("biceps",
                                  #~ osim.StepFunction(0.5, 3.0, 0.3, 1.0))

#~ # ---------------------------------------------------------------------------
#~ # Build model with components created above.
#~ # ---------------------------------------------------------------------------

#~ arm.addBody(humerus)
#~ arm.addBody(radius)
#~ arm.addJoint(shoulder) # Now required in OpenSim4.0
#~ arm.addJoint(elbow)
#~ arm.addForce(biceps)
#~ arm.addController(brain)

#~ # ---------------------------------------------------------------------------
#~ # Add a console reporter to print the muscle fibre force and elbow angle.
#~ # ---------------------------------------------------------------------------

#~ # We want to write our simulation results to the console.
#~ reporter = osim.ConsoleReporter()
#~ reporter.set_report_time_interval(1.0)
#~ reporter.addToReport(biceps.getOutput("fiber_force"))
#~ elbow_coord = elbow.getCoordinate().getOutput("value")
#~ reporter.addToReport(elbow_coord, "elbow_angle")
#~ arm.addComponent(reporter)

#~ # ---------------------------------------------------------------------------
#~ # Add display geometry. 
#~ # ---------------------------------------------------------------------------
#~ bodyGeometry = osim.Ellipsoid(0.1, 0.5, 0.1)
#~ bodyGeometry.setColor(osim.Vec3(0.5)) # Gray
#~ humerusCenter = osim.PhysicalOffsetFrame()
#~ humerusCenter.setName("humerusCenter")
#~ humerusCenter.setParentFrame(humerus)
#~ humerusCenter.setOffsetTransform(osim.Transform(osim.Vec3(0, 0.5, 0)))
#~ humerus.addComponent(humerusCenter)
#~ humerusCenter.attachGeometry(bodyGeometry.clone())

#~ radiusCenter = osim.PhysicalOffsetFrame()
#~ radiusCenter.setName("radiusCenter")
#~ radiusCenter.setParentFrame(radius)
#~ radiusCenter.setOffsetTransform(osim.Transform(osim.Vec3(0, 0.5, 0)))
#~ radius.addComponent(radiusCenter)
#~ radiusCenter.attachGeometry(bodyGeometry.clone())

#~ # ---------------------------------------------------------------------------
#~ # Configure the model.
#~ # ---------------------------------------------------------------------------

#~ state = arm.initSystem()
#~ # Fix the shoulder at its default angle and begin with the elbow flexed.
#~ shoulder.getCoordinate().setLocked(state, True)
#~ elbow.getCoordinate().setValue(state, 0.5 * osim.SimTK_PI)
#~ arm.equilibrateMuscles(state)
#~ arm.printToXML('arm.xml')

# ---------------------------------------------------------------------------
# Loading model from XML
# ---------------------------------------------------------------------------
arm = osim.Model('arm.xml')
arm.setUseVisualizer(True)
state = arm.initSystem()
# ---------------------------------------------------------------------------
# Why do I need this? Isn't it recorded in the model?
# ---------------------------------------------------------------------------
shoulder = arm.updJointSet().get('shoulder')
elbow = arm.updJointSet().get('elbow')
shoulder.getCoordinate().setLocked(state, True)
elbow.getCoordinate().setValue(state, 0.5 * osim.SimTK_PI)
arm.equilibrateMuscles(state)

# ---------------------------------------------------------------------------
# Configure the visualizer.
# ---------------------------------------------------------------------------

viz = arm.updVisualizer().updSimbodyVisualizer()
viz.setBackgroundColor(osim.Vec3(0)) # white
viz.setGroundHeight(-2)

# ---------------------------------------------------------------------------
# Simulate.
# ---------------------------------------------------------------------------

manager = osim.Manager(arm)
state.setTime(0)
manager.initialize(state)
state = manager.integrate(10.0)

Re: flushing and loading models to/from file

Posted: Wed Mar 04, 2020 2:53 pm
by aymanh
Hi Bernardo,

Good question. The coordinate value(s) are not part of the "Model" and so they don't get written to the XML file, however they are part of the "state". We intentionally separate the model from the state. Integration for example advances the state over time without changing the model. A trajectory is a set of states, etc.

It is possible to save "default" values for state variables in the model file. Typically this is done using method like:
coordinate.setDefaultLocked(true) or coordinate.setDefaultValue(desired)
Then these defaults are saved in the XML file and are used to initialize the state every time you call initSystem on the model.

Hope this helps,
-Ayman

Re: flushing and loading models to/from file

Posted: Thu Mar 05, 2020 8:24 am
by bfcosta
Ok. I guess that if you want to work with different initial/default states, you'll need to replicate the model saving them with different filenames, each one with its own defaults reset.

In my first message code, it seems preferred to use updCoordinate() method rather than getCoordinate() to change default values or maybe values in general. Both work fine in python however, in c++, updCoordinate() is the only one that references an updatable object. Also, printoToXML seems to exist only in python. For c++ coding, I couldn't find its equivalent as printBasicInfo() does not outputs an XML. Is there any?

Code: Select all

shoulder.updCoordinate().setDefaultLocked(True)
elbow.updCoordinate().setDefaultValue(0.5 * osim.SimTK_PI)
arm.finalizeConnections() # needed for XML printing
arm.printToXML('arm.xml')
Note that finalizeConnections() is needed before outputing the model to XML, unless that initSystem() hasn't been called yet. If you are loading the model from an XML file, calling model.equilibrateMuscles(state) makes the simulation abort at its beginning. Don't know why however this step can be omitted and seems only needed if one is changing coordinate values of the current state.

Re: flushing and loading models to/from file

Posted: Thu Mar 05, 2020 9:56 am
by aymanh
Bernardo,

Indeed, while both get and upd methods are available on objects due to lack of const correctness outside C++, the correct usage pattern is to use upd methods when making changes so that the downstream effects (invalidation of system or recreation of state) are done correctly.

Typically you need to call finalize connections only when you make structural changes to the model (adding components or rewiring components/sockets).

The method printToXML in python is the equivalent of print in C++, except that the print method name is reserved in python so we had to rename it.

Hope this helps,
-Ayman

Re: flushing and loading models to/from file

Posted: Thu Mar 05, 2020 1:13 pm
by bfcosta
One final question about this example. The python code of this example uses an object called Manager to run a simulation of the arm movement. This same object exists in cpp. However, the github page for this same example coded in cpp uses a bultin function called simulate(). What is the difference between them? All I could get is that using simulate in cpp, one can run many simulations for the same opened window, while with the python solution using Manager, the window is closed after the first simulation run.

Python code for simulation run:

Code: Select all

manager = osim.Manager(arm)
state.setTime(0)
manager.initialize(state)
state = manager.integrate(10.0)
Equivalent code in cpp uses simulate although it is possible to provide the same steps:

Code: Select all

//~ OpenSim::Manager manager(model);
//~ state.setTime(0);
//~ manager.initialize(state);
//~ state = manager.integrate(10.0);
// Simulate.
simulate(model, state, 10.0);

Re: flushing and loading models to/from file

Posted: Fri Mar 06, 2020 12:25 pm
by aymanh
Hello,

simulate method is used for convenience and internally it creates a Manager instance, the convenience method may not be exposed/available in python/java/Matlab but the Manager class functionality has definitely been widely used documented with multiple examples.

Hope this helps,
-Ayman