Script Examples

About

This tutorial demonstrates the use of custom scripts using ScriptController and ScriptMeasure.

High Jump

This example uses both a ScriptController and a ScriptMeasure to perform a standing high-jump optimization. The scone scenario is defined as followed:

CmaOptimizer {
	signature_prefix = DATE_TIME
 
	SimulationObjective {
		max_duration = 2
 
		# Model used in simulation
		OpenSimModel {
			model_file = data/Human0914.osim
			fixed_control_step_size = 0.005 # higher step sizes give better performance
		}
 
		# Controller based on lua script
		ScriptController {
			script_file = "data/ScriptControllerJump.lua"
		}
 
		# Measure based on lua script
		ScriptMeasure {
			minimize = 0 # let the optimizer know we want to maximize this measure
			target_body = "toes_r" # this parameter will be used in the script
			script_file = "data/ScriptMeasureJump.lua"
		}
	}
}

Jump Controller

The actual controller is defined in data/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 )
	-- 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.1, 0, 1 )
		slope[ i ] = par:create_from_mean_std( name .. "-slope", 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
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. 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 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 data/ScriptMeasureJump.lua:

-- SCONE script for a high-jump measure.
-- See Tutorial 6a - Script - High Jump
 
function init( model )
	-- 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 )
 
	-- global boolean that keeps track if we have moved upwards
	has_moved_up = false
end
 
function update( model )
	-- get current vertical position and velocity
	local pos = body:com_pos().y
	local vel = body:com_vel().y
 
	-- check if we are at peak height
	if has_moved_up then -- have we been moving up?
		if vel < 0 then -- are we moving down?
			-- we are at the peak!
			return true -- return true to end the simulation
		end
	else
		if vel > 0.01 then -- are we starting to move up?
			has_moved_up = true -- set the flag
		end
	end
 
	return false -- return false to keep going
end
 
function result( model )
	-- return the vertical position as the final result
	-- this is either peak height
	-- or the height at the end of the simulation
	return body:com_pos().y
end
 
function store_data( frame )
	-- store some values for analysis
	frame:set_value( "body_height", body:com_pos().y )
	frame:set_bool( "has_moved_up", has_moved_up )
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
  • by tgeijten