tutorials:script

Differences

This shows you the differences between two versions of the page.


Previous revision
tutorials:script [2023/12/14 16:11] (current) thomas
Line 1: Line 1:
 +====== Script Examples ======
 +<callout type="info" icon="true" title="About">
 +This tutorial demonstrates the use of custom scripts using [[ref:script_controller]] and [[ref:script_measure]].
 +</callout>
  
 +==== Maximizing the Height of a Body ====
 +This example uses both a [[ref:script_controller]] and a [[ref:script_measure]] to maximize the height of a specific body part. The scone scenario is defined as follows:
 +<code python>
 +CmaOptimizer {
 + signature_prefix = DATE_TIME
 +
 + SimulationObjective {
 + max_duration = 2
 +
 + # Model used in simulation
 + ModelOpenSim3 {
 + model_file = models/H0918v3.osim
 + state_init_file = init/InitStateStand.zml
 + initial_load = 1
 + fixed_control_step_size = 0.005 # larger step sizes give better performance
 + }
 +
 + # Controller based on lua script
 + ScriptController {
 + script_file = "controllers/ScriptControllerFeedForward.lua"
 + }
 +
 + # Measure based on lua script
 + ScriptMeasure {
 + minimize = 0 # let the optimizer know we want to maximize this measure
 + target_body = "calcn_r" # this parameter will be used in the script
 + script_file = "measures/ScriptMeasureBodyHeight.lua"
 + }
 + }
 +}
 +</code>
 +
 +==== FeedForward Controller ====
 +The actual controller is defined in ''controllers/ScriptControllerJump.lua''. It defines the following functions:
 +  * ''init( model, par )'', which is called at the beginning of the simulation. The first parameter is a [[ref:lua_model]], which can be used to access any [[ref::lua_body]], [[ref:lua_actuator]], etc, which can be used during the simulation. The second parameter is a [[ref:lua_params]], which can be used to create custom parameters for the optimization.
 +  * ''update( model )'', which is called at each timestep during the simulation. In this function, the controller should compute actuator values and add them to the actuator inputs.
 +
 +<code lua>
 +-- SCONE script for a simple feed-forward controller.
 +-- See Tutorial 6a - Script - High Jump
 +
 +function init( model, par, side )
 + -- keep a list of offsets and slopes to compute the excitation
 + offset = {}
 + slope = {}
 +
 + -- keep a list of all actuators
 + actuators = {}
 +
 + -- iterate over all actuators in the model
 + for i = 1, model:actuator_count() do
 + -- store the actuator in the list
 + actuators[ i ] = model:actuator( i )
 +
 + -- create parameters for both slope and offset
 + local name = actuators[ i ]:name()
 + offset[ i ] = par:create_from_mean_std( name .. ".offset", 0.3, 0.01, 0, 1 )
 + slope[ i ] = par:create_from_mean_std( name .. ".slope", 0, 0.1, -10, 10 )
 + end
 +end
 +
 +function update( model )
 + -- get the current simulation time
 + local t = model:time()
 +
 + -- iterate over all actuators
 + for i = 1, #actuators do
 + local excitation = offset[ i ] + t * slope[ i ]
 + actuators[ i ]:add_input( excitation )
 + end
 +
 + return false
 +end
 +</code>
 +
 +==== Jump Measure ====
 +The actual measure is defined in ''data/ScriptMeasureJump.lua''. It defines the following functions:
 +  * ''function init( model, par )'', which is called at the beginning of the simulation. The first parameter is a [[ref:lua_model]], which can be used to access any [[ref::lua_body]], [[ref:lua_actuator]], etc, which can be used during the simulation. Unlike controllers, Measures are not allowed to create parameters, since they should evaluate parameter instances instead.
 +  * ''function update( model )'', which is called at each time step during the simulation. This function is optional, and can be used to do some internal bookkeeping, depending on the type of a measure. It is not allowed to generate actuator inputs here. Returning ''true'' will terminate the simulation.
 +  * ''function result( model )'', which is called at the end of the simulation, and should return the final result.
 +  * ''function store_data( frame )'', which is called at each time step, and can be used to store custom data channels via the [[ref::lua_frame]] parameter. These channels which will be written to the ''.sto'' file, and become visible in the SCONE Studio Analysis window.
 +
 +The actual measure is defined in ''measures/ScriptMeasureBodyHeight.lua'':
 +<code lua>
 +-- SCONE script for a high-jump measure.
 +-- See Tutorial 6a - Script - High Jump
 +
 +function init( model, par, side )
 + -- get the 'target_body' parameter from ScriptMeasure, or set to "pelvis"
 + target_body = scone.target_body or "pelvis"
 +
 + -- find the actual body with the same name
 + body = model:find_body( target_body )
 +
 + -- body height integral, used to compute average height
 + body_H = 0
 +end
 +
 +function update( model )
 + -- get current vertical position and velocity
 + local body_height = body:com_pos().y
 +
 + -- update best_height
 + body_H = body_H + model:delta_time() * body_height
 +
 + -- stop simulation if com pos is below 0.5
 + if model:com_pos().y < 0.5 then
 + return true -- terminate the simulation
 + else
 + return false -- keep simulating
 + end
 +end
 +
 +function result( model )
 + -- this is called at the end of the simulation
 + -- fitness corresponds to average body height
 + local fitness = body_H / model:max_duration()
 +
 + return fitness
 +end
 +
 +function store_data( frame )
 + -- store some values for analysis
 + frame:set_value( "body_height", body:com_pos().y )
 +end
 +</code>
 +
 +==== Balance Device ====
 +This example demonstrates a possible implementation of a 'virtual balance device', which can generate a moment on a specific body if its orientation is outside a specific range.
 +
 +The SCONE scenario uses a Geyer & Herr gait controller in combination with a [[ref:script_controller]], and is defined as follows:
 +<code python>
 +CmaOptimizer {
 + signature_prefix = DATE_TIME
 +
 + SimulationObjective {
 + max_duration = 10
 +
 + # Model used in simulation
 + OpenSimModel {
 + model_file = data/Human0914.osim
 + fixed_control_step_size = 0.005 # higher step sizes give better performance
 + enable_external_forces = 1 # Required when applying external forces
 +
 + # Optimize initial state parameters
 + state_init_file = data/InitStateGait10.sto
 + initial_state_offset = 0~0.01<-0.5,0.5>
 + initial_state_offset_exclude = "*_tx;*_ty;*_u"
 + }
 +
 + CompositeController {
 + # Controller for gait, based on [Geyer & Herr 2010]
 + << data/ControllerGH2010.scone >>
 +
 + ScriptController {
 + name = BalanceDevice
 + target_body = torso
 + min_deg = -20~2 # optimize this parameter
 + max_deg = 5~0.5 # optimize this parameter
 + moment = 50
 + duration = 0.2
 + script_file = "data/ScriptControllerBalanceDevice.lua"
 + }
 + }
 +
 + # Measure for gait
 + << data/MeasureGait05.scone >>
 + }
 +}
 +</code>
 +
 +The actual control script is defined in ''data/ScriptControllerBalanceDevice.lua'':
 +<code lua>
 +-- SCONE script that simulates a device that generates an external moment at a specific condition.
 +-- See Tutorial 6b - Script - Balance Device
 +
 +function init( model, par )
 + -- get the 'target_body' parameter from ScriptController, or set to "pelvis"
 + target_body = model:find_body( scone.target_body or "pelvis" )
 +
 + -- get 'min_deg' and 'max_deg' parameters from ScriptController (required!)
 + if ( scone.min_deg and scone.min_deg ) then
 + body_min = par:create_from_string( "min_deg", scone.min_deg )
 + body_max = par:create_from_string( "max_deg", scone.max_deg )
 + else
 + -- produce an error and abort the simulation
 + error( "Must set min_deg and max_deg parameters!" )
 + end
 +
 + -- get 'device_moment' and 'device_duration' parameters from ScriptController (not required!)
 + device_moment_mag = scone.moment and par:create_from_string( "device_moment", scone.moment ) or 100
 + device_duration = scone.duration and par:create_from_string( "device_duration", scone.duration ) or 0.2
 +
 + -- initialize global variables that keep track of the device state
 + device_start = 0
 + device_moment = 0
 +end
 +
 +function update( model )
 + local t = model:time()
 +
 + -- check if the device is currently active
 + if device_moment ~= 0 then
 + -- check if the device should deactivate
 + if t - device_start > device_duration then
 + -- add opposite external moment to deactivate device
 + target_body:add_external_moment( 0, 0, -device_moment )
 + device_moment = 0
 + -- print a message to the scone messages window
 + scone.debug( "device deactivated at " .. t )
 + end
 + -- device is not active, check if we activate it
 + elseif device_start == 0 then -- device was not activated before
 + -- get the current body orientation in degrees and check with body_min / body_max
 + local ori = 57.3 * target_body:ang_pos().z
 + if ori < body_min then
 + device_moment = device_moment_mag
 + elseif ori > body_max then
 + device_moment = -device_moment_mag
 + end
 +
 + -- check if device_moment was set
 + if device_moment ~= 0 then
 + device_start = t
 + target_body:add_external_moment( 0, 0, device_moment )
 +
 + -- print a message to the scone messages window
 + scone.debug( "device activated at t=" .. t .. " ori=" .. ori .. " moment=" .. device_moment )
 + end
 + end
 +
 + -- return false to keep going
 + return false;
 +end
 +</code>