Script Examples
Maximizing the Height of a Body
This example uses both a ScriptController and a ScriptMeasure to maximize the height of a specific body part. The scone scenario is defined as follows:
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" } } }
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 LuaModel, which can be used to access any LuaBody, LuaActuator, etc, which can be used during the simulation. The second parameter is a LuaParams, 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.
-- 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
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 LuaModel, which can be used to access any LuaBody, LuaActuator, 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. Returningtrue
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 LuaFrame 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
:
-- 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
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 ScriptController, and is defined as follows:
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 >> } }
The actual control script is defined in data/ScriptControllerBalanceDevice.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