opensim-org / opensim-core

SimTK OpenSim C++ libraries and command-line applications, and Java/Python wrapping.
https://opensim.stanford.edu
Apache License 2.0
796 stars 320 forks source link

Issues with model initialisation, resetting, and stepping in Python #3536

Closed A-Artemis closed 8 months ago

A-Artemis commented 1 year ago

Hi, I am building an environment for OpenAI stable-baselines3 in Python 3.11.4, but I am unsure how to correctly handle the init, reset, and step of Opensim (using 4.4.1).

My __init__() is

    def __init__(
        self,
        model_path: str,
        integrator_accuracy: float,
        visualize: bool,
    ):
        self.integrator_accuracy = integrator_accuracy
        self.model = opensim.Model(model_path)
        self.integrator_accuracy = integrator_accuracy
        self.state = self.model.initSystem()
        self.manager = opensim.Manager(self.model)
        self.manager.setIntegratorAccuracy(integrator_accuracy)
        self.manager.initialize(self.state)
        self.brain = opensim.PrescribedController()
        self.model.addController(self.brain)
        self.model.setUseVisualizer(visualize=visualize)
        self.state = self.model.initSystem()
        self.model.realizeDynamics(self.state)

        for i, actuator in enumerate(self.model.getActuators()):
            func = opensim.Constant(0.0)
            self.brain.addActuator(actuator)
            self.brain.prescribeControlForActuator(i, func)

my reset():

    def reset(self, seed=None, options=None) -> tuple[np.ndarray, dict]:
        self.istep = 0
        self.state = self.model.initializeState()
        self.model.equilibrateMuscles(self.state)

        for i, actuator in enumerate(self.actuatorSet):
            func = opensim.Constant(0.0)
            self.brain.prescribeControlForActuator(i, func)

        self.manager = opensim.Manager(self.model)
        self.manager.setIntegratorMethod(opensim.Manager.IntegratorMethod_ExplicitEuler)
        self.manager.setIntegratorAccuracy(self.integrator_accuracy)
        self.manager.initialize(self.state)
        self.model.realizeDynamics(self.state)
        _, obs_list = self.get_states()
        return obs_list, {}

And my step()

    def step(self, action: np.ndarray) -> tuple[np.ndarray, float, bool, bool, dict]:
        function_set = self.brain.get_ControlFunctions()
        for i, function in enumerate(function_set):
            func = opensim.Constant.safeDownCast(function)
            func.setValue(float(action[i]))
        self.state = self.manager.integrate(self.step_size * self.istep)
        self.model.realizeDynamics(self.state)

        obs_dict, obs_list = self.get_states()
        reward = self.get_reward(obs_dict=obs_dict)
        end = self.is_terminated()
        done = self.is_truncated(obs_dict=obs_dict)
        self.istep += 1
        return obs_list, reward, end, done, {}

I have noticed that the order of which functions are called is important. In the reset() I need to set the integrator accuracy (or the minimum step size) AFTER setting the integrator method and before the initialize, otherwise the values wont take.

In the __init__ I have two self.state = self.model.initSystem() but I seem to need both for my model to launch, I encounter this error when removing either:

RuntimeError: std::exception in 'OpenSim::Manager::Manager(OpenSim::Model &)': Component has no underlying System.
You must call initSystem() on the top-level Component (i.e. Model) first.

Am I correctly setting up and resetting the environment? Is there a specific order that must be carried out?

For the Manager, what are the differences between the methods in terms of accuracy/speed/functionality. And what is the minimum steps an integrator can do? If I have:

STEP_SIZE = 0.01

self.manager = opensim.Manager(self.model)
self.manager.setIntegratorMinimumStepSize(STEP_SIZE / 10)
self.manager.initialize(self.state)
...
self.manager.integrate(STEP_SIZE)

Will it integrate in 10 steps of size 0.001? This shows a massive speed up in the simulation, but does it remain accurate?

DonMiller9294 commented 1 year ago

:

Initialization (init):

Reset (reset):

Step (step):

Manager and Integration Settings:

Minimum Steps of an Integrator:

Accuracy vs. Speed:

To find the sweet spot for your simulation, you might need to experiment with different settings, observing how they affect your simulation's behavior and accuracy. The OpenSim documentation and input from the community can provide valuable guidance.

In summary, you're tackling the complexities of simulation setup and integration methods, which can be quite involved. Keep experimenting and seeking advice from experts in the field to fine-tune your environment for the best results.

nickbianco commented 1 year ago

Hi @A-Artemis, much of the functionality you're tyring to implement is already available in the classes SimTK::Integrator and SimTK::TimeStepper classes which are managed by OpenSim::Manager. It would likely be more straightforward to use these classes, unless you have a specific reason for needing a custom implementation.

A-Artemis commented 1 year ago

Hey @nickbianco I have been looking at the documentation here https://simtk.org/api_docs/opensim/api_docs/classOpenSim_1_1Manager.html but I am still struggling with it, could you please provide me with an example or editing the integrator and setting custom timesteps?

EDIT: Additionally, is there any difference between using model.realizeDynamics() vs model.realizeAcceleration()? with models using either muscle forces or actuators?

nickbianco commented 1 year ago

Here's a simple example in Python: https://github.com/opensim-org/opensim-core/blob/26b7b9b125c041cef83d6e28bcbfc541218d6e14/Bindings/Python/examples/build_simple_arm_model.py#L161.

To update the SimTK::Integrator, you can do the following:

integrator = manager.getIntegrator()

You can refer to Simbody's documentation for the integrator settings.

EDIT: Additionally, is there any difference between using model.realizeDynamics() vs model.realizeAcceleration()? with models using either muscle forces or actuators?

It depends. If you only need force information, then realizing to SimTK::Stage::Dynamics might be fine. But if you're unsure, realize to SimTK::Stage::Acceleration. Usually Simbody will throw an Exception if you try to compute something that requires a stage you haven't realized to yet.