Type declarations
actor
Declare an active object with activities (behaviors)
Type declaration
actor <actor-name>[inherits <base-actor-type>[(<condition>)]] [:
<member>+]
<actor-name>- (Required) Must be different from any other defined actor, type, or struct name because the namespace for these constructs is global.
<base-actor-type>- (Optional) The name of a previously defined actor type. The new type inherits all members of the previously defined type as well as its associated scenarios.
<condition>- (Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you add members to an actor only if the field has the value specified.
<member>+-
(Optional) Is a list of one or more of the following:
- Constraint declaration
- Coverage declaration
- Event declaration
- External method declaration
- Field declaration
- Scenario declaration
Each member must be on a separate line and indented consistently from actor. See the Example scenario declaration in the examples below for a detailed explanation of how to declare a scenario inside an actor.
Like structs, actors are compound data structures that you create to store various types of related data. In contrast to structs, actors also are associated with scenarios. Thus, an actor is a collection of both related data and declared activity.
You can use the following extension mechanisms with actors:
- extend
- Unconditional inheritance
- Conditional inheritance
Note
See Type inheritance to learn about inheritance mechanisms.
enum veh_category: [car, truck, motorcycle]
enum track_kind: [single_track, multi_track]
actor my_vehicle:
category: veh_category
emergency_vehicle: bool
track_kind: track_kind
See complete example
enum rv_category: [motorhome, campervan, caravan, fifth_wheel_trailer, popup_camper, truck_camper]
actor recreational_vehicle:
category: rv_category
has_kitchen: bool
actor truck_camper inherits recreational_vehicle:
keep(category==truck_camper)
keep(has_kitchen==false)
See complete example
actor my_truck inherits my_vehicle(category == truck):
num_of_trailers: uint with: keep(it in [0..2])
See complete example
You can declare a scenario inside an actor. The following declarations are equivalent:
# Declaration 1
actor x:
b: bool
scenario y:
do top.something()
See complete example
# Declaration 2
actor x:
b: bool
scenario x.y:
do top.something()
See complete example
You can also extend a scenario of a parent actor:
actor x:
b: bool
scenario y:
do something: top.something(speed: default 20kph)
actor z inherits x (b == true):
extend y:
keep(something.speed == 30kph)
See complete example
However, you cannot declare a scenario inside an extend declaration:
actor x
b: bool
extend x:
scenario y: # Not allowed
do something()
action
Define a basic activity of an actor
Type declaration
action <name> [inherits <base-action-type>(<condition>)] [:
<member>+]
<name>- (Required) Is in the form <actor-name>.<action-name>. The action name must be unique among that actor’s actions, but it can be the same as the action of a different actor. Parentheses are not allowed in action declarations.
<base-action-type>- (Optional) Is the name of an action previously defined with <actor-name>. The new type inherits the behavior of the previously defined type.
<condition>-
(Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you modify the behavior of the action only if the field has the value specified.
Note
Unconditional inheritance is not allowed for actions.
<member>+-
(Optional) Is a list of one or more of the following:
- Constraint declaration
- Coverage declaration
- Event declaration
- External method declaration
- Field declaration
- Modifier invocation
- Behavior specification, modification or monitoring constructs
Notes
- A do member that describes the behavior of the action is required.
- Each member must be on a separate line and indented consistently from action.
- Every action has a predefined duration field of type time.
Actions are similar to scenarios in that they can have the same members, including optional behavior specifications.
They differ from scenarios in that they should be treated as atomic behavior descriptions from the outside: Their internal structure, if any, is to be considered opaque from the point of view of the user.
actor flying_object inherits physical_object:
weight: mass
keep(weight < 10kg)
# action flying_object.fly:
scenario flying_object.fly:
pos: global_position
See complete example.
enum
Define a scalar type with named values
Type declaration
enum <type-name> : [<member>*]
<type-name>- (Required) Must be different from any other defined actor, struct or type name because the namespace for these constructs is global.
<member>*-
(Optional) Is a comma-separated list, enclosed in square brackets and placed on one or more lines, of zero or more enumerations in the form:
<enum-name> [=<exp>]Each <enum-name> must be unique within the type.
<exp> evaluates to a constant value. If no <exp> is specified, the first name in the list is assigned a value of 0, the next 1 and so on.
You can declare a type without defining any values using the following syntax:
enum <type-name> : []
Enumerated types can be extended. You cannot use inheritance, either unconditional or conditional, with enumerated types.
If multiple enumeration types use the same literal, then the literal is overloaded — which literal is evaluated to depends on the type requirements of the context it is used in. If this ambiguity cannot be resolved uniquely, an error is issued.
In order to avoid the potential for such errors, or to be specific which enumeration type is needed, you can either use the casting operator or reference a literal value prefixed with the enumeration type and a separating ! character. See examples 2 and 3 below.
In this example, the enum type declarations define the types of vehicles in an OSC2 environment and the driving style.
enum car_type: [sedan = 1, truck = 2, bus = 3]
# by default, aggressive = 0, normal = 1
enum my_driving_style: [aggressive, normal, timid]
See complete example.
Here is an example of how to resolve ambiguity using the type casting operator as(). Is a the field a or the enum value a? The compiler picks the field.
enum my_enum: [a, b]
actor my_actor:
a: int
x: my_enum with:
keep(it == a)
See complete example.
If the intention is to specify the enumerated type value, use the type casting as() operator. This forces the compiler to treat it an an enum value:
enum my_enum: [a, b]
actor my_actor:
a: int
x: my_enum with:
keep(it == self.a.as(my_enum))
See complete example.
Here are examples of using explicit literals in fields and in expressions.
Extending rgb_color by adding black, overloads black with cmyk_color!black.
extend rgb_color: [black]
extend top.main:
# Example enumeration fields
my_cmyk_color: cmyk_color = black # Resolved to cmyk_color!black
my_new_rgb_color: rgb_color = black # Resolved to rgb_color!black
# Resolution in expressions
# field1: bool = (black == black) # Could be either type -> error issued
field2: bool = (rgb_color!black == rgb_color!black) # True
See complete example.
extend
Add to an existing type or subtype of an enumerated or structured type
Type declaration
extend <type-name> :
<member>+
<type-name>- (Required) Is the name of a previously defined enumerated type, struct, actor, scenario or modifier.
<member>+-
(Required) Is a list of one or more new members of the type.
For enumerated types, the list is comma-separated, enclosed in square brackets, and can be placed on one line.
For compound types, each member must be placed on a separate line and indented consistently from extend.
At compile time, extend modifies the type and thus all instances of the type for the test in which it is included. extend allows you to encapsulate attributes or behaviors as an aspect of an object. It also allows you to modify built-in actors, structs and scenarios.
Because two values are already defined for the driving_style type (aggressive=0, normal=1), erratic has the value of 2, unless explicitly assigned a different value.
extend driving_style: [erratic]
See complete example.
This example extends the struct named storm_data with a field for wind velocity. This extension applies to all instances of storm_data.
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"
struct storm_data:
snow_depth: length
extend storm_data:
var wind_velocity: speed
extend test_config:
set map = "$FTX_PACKAGES/maps/hooder.xodr"
extend top.main:
do sut.car.drive(duration: 5s)
A scenario can have one and only one do block. However, additional do blocks can be defined in scenario extensions. In that case, the do block in the last loaded extension takes precedence over previously loaded ones and is executed.
In this example, the previously defined behavior of scenario3 is replaced by an invocation of scenario2.
scenario top.scenario1:
flag: bool
do log("Scenario 1 is executing")
scenario top.scenario2:
do log("Scenario 2 is executing")
scenario top.scenario3:
do log("Scenario 3 is executing")
extend top.scenario3:
do scenario2()
extend top.main:
do serial:
scenario1()
scenario3()
Here are the results of executing this example:
Foretify> run
Checking license...
Running the test ...
Loading configuration ... done.
[0.000] [MAIN] Executing plan for top.all@1 (top__all)
[0.000] [MAIN] Scenario 1 is executing
[0.000] [MAIN] Scenario 2 is executing
[0.020] [MAIN] Run finished
[0.020] [MAIN] Ending the run
[0.020] [SIMULATOR] stop_run()
Run completed
See complete example.
In this example, the previous behavior of scenario3 is executed first, using previous_do(). Then new behavior is added. You can of course define new behavior first and then call previous_do() or you can use parallel to execute new behavior in parallel with the previous behavior.
scenario top.scenario1:
flag: bool
do log("Scenario 1 is executing")
extend top.scenario1:
do serial:
previous_do()
log("Extending scenario 1 behavior")
extend top.main:
do serial:
scenario1()
Here are the results of executing this example:
Foretify> run
Checking license...
Running the test ...
Loading configuration ... done.
[0.000] [MAIN] Executing plan for top.all@1 (top__all)
[0.000] [MAIN] Scenario 1 is executing
[0.000] [MAIN] Extending scenario 1 behavior
[0.020] [MAIN] Run finished
[0.020] [MAIN] Ending the run
[0.020] [SIMULATOR] stop_run()
Run completed
See complete example.
In the following example, the modifier sut.curving_road() is extended to constrain the curve to be to the right. Below is the original modifier declaration:
modifier sut.curving_road:
gr: road_curvature with:
keep(it.min_radius == 6meter)
keep(it.max_radius == 11meter)
extend sut.curving_road:
keep(gr.direction == right_curve)
See complete example.
global parameter
Declare a parameter that is accessible anywhere
Declaration
global <field-name> : <type> [<with-block>]
<field-name>- (Required) Is a unique name in the global scope.
A global parameter can be accessed within any scenario or actor declaration in the same file, imported files, or importing files. This is in contrast to parameter declarations contained within structured type declarations that are only accessible within these structured type declarations.
Any local fields defined within structured type declarations with the same name as a global parameter shadow the global declaration within that structured type declaration. In other words, within that structured type declaration any references to that field name reference the local field, not the global parameter.
This example shows that setting a default value for a global parameter is not yet supported. However, you can constrain the parameter with a hard or soft constraint when you declare it.
You cannot constrain a global parameter from a dynamic object. The value of a global parameter is set during generation and cannot be changed during the run.
# global count2: uint = 0 # default constraint not supported yet
global count: uint with: keep(soft it == 0) # soft constraint allowed
struct keep_count:
# keep(soft count== 1) # constraining global parameter from dynamic struct not allowed
See complete example.
The following example shows that when a field is defined locally with the same name as a global parameter, a constraint on an entity with that name refers to the local field, not the global parameter.
global count: uint with: keep(it == 0)
extend top.main:
count: uint
keep(count == 2)
See complete example.
modifier
Declare a modifier for scenarios
Declaration
[global] modifier <name> [of <scenario-name>] [:
<member>+]
global- (Optional) Causes an instance of the modifier to be created for every instance of the actor, and persist throughout the test. Note that
globalcannot be used withof <scenario>. <name>- (Required) Is in the form
[<actor-name>].<modifier-name>. The<modifier-name>must be unique among that actor's scenarios and scenario modifiers, but it can be the same as the scenario or scenario modifier of a different actor. To make the modifier visible globally, use top as the actor name or use theglobalparameter. If<actor_name>is not provided, top is used. <scenario-name>- (Optional) Is in the form
<scenario-name>. Specifying the actor name is not allowed because the scenario must be associated with the same actor as the modifier. If you specify the name of a scenario, the modifier can be invoked only by that scenario. It cannot be invoked by other scenarios, even those associated with the same actor. Note thatof <scenario>cannot be used with theglobalparameter. <member>+-
(Optional) Is a list of one or more of the following:
- Constraint declaration
- Coverage declaration
- Event declaration
- External method declaration
- Field declaration
- Modifier invocation
- on directive
Notes
- Scenario modifiers cannot include scenario invocations or do.
- Each member must be on a separate line and indented consistently from modifier.
Scenario modifiers constrain or modify the behavior of a scenario; they do not define the primary behavior of a scenario. Modifiers can be extended.
For global modifiers, @start is emitted at the start of the test and @end is emitted at the end of the test, regardless of whether the test succeeded or failed.
You can use the following extension mechanisms with modifiers:
- extend
- Conditional inheritance
- Unconditional inheritance
- The in modifier
When you create a modifier using the optional global parameter, an instance of the global modifier is created for each instance of the actor. Global modifiers allow composition and encapsulation of modifiers, ensuring that their behavior occurs for each instance.
As an example, assume you have a regular (non-global) modifier that checks that no objects or actors are too close to the actor. Suppose you have another regular modifier that collects coverage of items in the vicinity of that specific actor. You can then write a global modifier that instantiates both modifiers and you get both behaviors for each such actor.
When you create a global modifier for an actor, an implicit field is added to the actor: actor parameter can be used in expressions inside the modifier to access its actor, just like regular modifiers.
Global modifiers have an enabled field. If enabled is constrained to false, all event-based constructs in the modifier are disabled, for example, cover and sample statements. Global modifiers cannot be invoked. They cannot inherit from other modifiers, and cannot be inherited from.
This example declares a modifier that can be invoked only by the vehicle.to_side_on_curve() scenario.
modifier vehicle.object_in_road of to_side_on_curve:
tc: traffic_cone
See complete example.
This example declares a modifier that defines a curving road of the sut actor.
modifier sut.curving_road:
gr: road_curvature
keep(gr.min_radius == 6meter)
keep(gr.max_radius == 11meter)
See complete example.
This example declares a global modifier for the vehicle actor and shows how to use actor to reference the actor instance. Then, under top.main, it shows how to disable the modifier for a specific actor instance.
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"
extend test_config:
set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"
extend issue_kind: [high_speed_at_end]
# Declaring a global modifier
global modifier vehicle.end_speed_info:
# Use 'actor' to reference the actor instance
cover(name: speed_at_end, expression: actor.state.speed, event: end, unit: kph)
# Simple check
on @end if actor.state.speed > 50kph:
other_error(high_speed_at_end, "speed too high at end")
# Disabling a global modifier for a specific actor:
extend top.main:
vehicle_1: vehicle
vehicle_2: vehicle
# Access the modifier field under the actor's end_speed_info
keep(vehicle_2.end_speed_info.enabled == false)
do parallel:
sut.car.drive() with: speed([30..60]kph)
vehicle_1.drive() with: speed([55..70]kph)
vehicle_2.drive() with: speed([25..50]kph)
with:
duration([3..7]s)
scenario
Declare an ordered set of behaviors by an actor
Type declaration
scenario <name>[inherits <base-scenario-type>(<condition>)] [:
<member>+]
<name>- (Required) Is in the form [<actor-name>.]<scenario-name>. The scenario name must be unique among that actor’s scenarios, but it can be the same as the scenario of a different actor. Parentheses are not allowed in scenario declarations.
<base-scenario-type>- (Optional) Is the name of a scenario previously defined with <actor-name>. The new type inherits the behavior of the previously defined type.
<condition>-
(Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you modify the behavior of the scenario only if the field has the value specified.
Note
Unconditional inheritance is not allowed for scenarios.
<member>+-
Is a list of one or more of the following:
- Constraint declaration
- Coverage declaration
- Event declaration
- External method declaration
- Field declaration
- Modifier invocation
- Behavior specification, modification or monitoring constructs
Notes
- A do member that describes the behavior of the scenario is required.
- Each member must be on a separate line and indented consistently from scenario.
- Every scenario has a predefined duration field of type time.
To define the behavior of a scenario, you must use the do member to invoke an operator scenario, such as serial, a library scenario, such as vehicle.drive(), or a user-defined scenario.
The events @start, @end, and @fail are defined for every scenario. They are emitted whenever a scenario instance starts, ends, or fails, respectively. In scenarios that invoke other scenarios, each scenario may emit its own @start, @end and @fail event.
Note
Coverage and performance metrics are not collected if the event triggering collection is not emitted. For example, if a scenario fails, coverage and performance metrics defined on the scenario's @end event are not collected.
Note that coverage or other metrics are impacted if they are emitted on these events. In fact any logic that is defined on these events will not take place if the events are not emitted.
You can control the behavior of a scenario and collect data about its execution by declaring data fields and other members in the scenario itself, in its related actor or structs, or in the test file. For example, the vehicle actor has a field of type speed that allows you to use the speed() scenario modifier to control the speed of a car.
When you declare or extend a scenario, you must associate it with a specific actor by prefixing the scenario name with the actor name in the form actor-instance-name.scenario-name.
Note
Invoking the generic form of the scenario (actor.scenario) is not allowed.
You can use the following extension mechanisms with scenarios:
- extend
- Conditional inheritance
- the in modifier
# A two-phase scenario
scenario traffic.two_phases: # Scenario name
# Define the truck with specific attributes
truck1: vehicle with:
keep(it.color == green)
keep(it.vehicle_category == box_truck)
# Define the behavior
do serial:
phase1: truck1.drive() with:
speed(speed: 0kph, at: start)
speed(speed: 10kph, at: end)
phase2: truck1.drive() with:
speed(speed: [10..15]kph)
See complete example.
enum my_vehicle_category: [car, truck, motorcycle]
enum my_emergency_vehicle_kind: [fire, police, ambulance]
enum light_kind: [parking, low_beam, high_beam, emergency]
actor my_vehicle:
category: my_vehicle_category
emergency_vehicle: bool
See complete example. See Example scenarios for more examples of how to create and reuse scenarios.
struct
Define a compound data structure
Type declaration
struct <type-name>[inherits <base-struct-type>[(<condition>)]] [:
<member>+]
<type-name>- (Required) Must be different from any other defined type, struct or actor name because the namespace for these constructs is global.
<base-struct-type>- (Optional) Is the name of a previously defined struct. The new type inherits all members of the previously defined type.
<condition>- (Optional) Has the form <field-name> == <value>. The field must be of type bool or enum. This syntax lets you add members to a struct only if the field has the value specified.
<member>+-
(Optional) Is a list of one or more of the following:
- Constraint declaration
- Coverage declaration
- Event declaration
- External method declaration
- Field declaration
Each member must be on a separate line and indented consistently from struct.
Structs are compound data structures that you can create to store related data of various types. For example, the AV library has a struct called car_collision that stores data about the vehicles involved in a collision.
You can use the following extension mechanisms with actors:
- extend
- Unconditional inheritance
- Conditional inheritance
This example defines a struct named my_car_status with variables called time and current_speed.
struct my_car_status:
var time: time
var current_speed: speed
See complete example.
This example creates a new struct type my_collision_data from the base type collision_data and adds a field to store the type of the other car involved in the collision.
struct my_collision_data inherits collision_data:
var other_vehicle_category: vehicle_category
See complete example.
This example creates a new struct type snow_storm_data from the base type storm_data and adds a field for snow_depth.
enum storm_type: [rain, ice, snow]
struct storm_data:
storm: storm_type
wind: speed
struct snow_storm_data inherits storm_data(storm == snow):
snow_depth: length
See complete example.
type
Define a physical type
Type declaration
type <name> 'is' <SI-base-exponent-list>
<name>- (Required) The name of the new type you are declaring.
<SI-base-exponent-list>-
(Required) Is in the form:
SI(<SI-unit-name> : <int> [, <SI-unit-name> : <int>]*) <SI-unit-name>-
(Required) Is an SI base unit or derived unit name. The base unit names are:
- Length - meter (m)
- Time - second (s)
- Amount of substance - mole (mole)
- Electric current - ampere (A)
- Temperature - kelvin (K)
- Luminous intensity - candela (cd)
- Mass - kilogram (kg)
lm and deg are examples of a derived unit name.
<int>- (Required) A literal of type integer.
Physical types represent physical quantities. A physical type must define the following:
- The basis for representing a physical quantity such as time.
- A base unit that is used to measure that quantity.
The base unit must be defined with a unit type declaration.
See Numeric literals for a list of the predefined types and their units.
type length is SI(m: 1)
type time is SI(s: 1)
type speed is SI(m: 1, s: -1)
type acceleration is SI(m: 1, s: -2)
type jerk is SI(m: 1, s: -3)
type angle is SI(deg: 1)
type angular_rate is SI(deg: 1, s: -1)
type angular_acceleration is SI(deg: 1, s: -2)
type mass is SI(kg: 1)
type temperature is SI(K: 1)
type pressure is SI(kg: 1, m: -1, s: -2)
type luminous_flux is SI(lm: 1)
type illuminance is SI(lm: 1, m: -2)
unit
Define a unit for a physical type
Type declaration
unit <unit-name> is <physical-type>(factor: <float> [, offset: <float>])
<unit-name>- (Required) The name of the new unit you are declaring.
<physical-type>- (Required) The name of a defined physical type, either an SI base type or a derived type.
<float>- (Required) A floating point value.
Unit names comprise a separate global namespace. All unit names must be unique within that namespace, although the definitions can be shared: m and meter share the same definition, for example.
See Numeric literals for a list of the predefined types and their units.
type speed is SI(m: 1, s: -1)
unit meter_per_second is speed(factor: 1.0)
unit mps is speed(factor: 1.0)
unit kilometer_per_hour is speed(factor: 0.277777778)
unit kmph is speed(factor: 0.277777778)
unit kph is speed(factor: 0.277777778)
unit mile_per_hour is speed(factor: 0.447038889)
unit mph is speed(factor: 0.447038889)
unit miph is speed(factor: 0.447038889)
type temperature is SI(K: 1)
unit K is temperature(factor: 1.0)
unit kelvin is temperature(factor: 1.0)
unit celsius is temperature(factor: 1.0, offset: 273.15)
unit c is temperature(factor: 1.0, offset: 273.15)
unit fahrenheit is temperature(factor: 0.555555556, offset: 255.372222222)
unit f is temperature(factor: 0.555555556, offset: 255.372222222)
Tip
You can configure how units are displayed. See Display unit options for details.
watcher type
A watcher type is a type of an object that has a time life span and emits intervals. An interval is data that captures some behavior over a period of time. Intervals can be viewed in Foretify and Foretify Manager. See Value and interval traces for details.
The watcher can hold any struct member that a modifier can hold (including modifier invocations) and has a specific API for creating and handling intervals.
Watcher
watcher modifier <watcher-name>[(<watcher-data>)] [: <member>+]
<watcher-name>-
(Required) The name of this watcher type.
<watcher-data>-
(Optional) The type of the data that an instance of this watcher emits. The data type has to be derived from any_watcher_data type. The watcher will have a field called data of the declared <watcher-data> type.
The watcher will emit instances of that type (see start_interval and end_interval in the Watcher API section).
<member>+-
(Optional) A sequence of modifier members.
The watcher can hold any struct member that a modifier can hold (including modifier invocations) and has a specific API for creating, handling, and emitting intervals (of type <watcher-data>).
For more details, see Defining watchers and checkers.
The watcher API
Watchers provide the following API:
i_start-
An event that is emitted when a new interval starts.
i_end-
An event that is emitted when an interval ends.
i_clock-
An event that is emitted every system cycle between i_start and i_end.
start_interval(data: <watcher-data> = null)-
This is a predefined method that initializes a new interval with a watcher data struct. It assigns data to the watcher’s data field and emits the event i_start of the watcher. If data is not provided, an empty instance of
will be created. The value for the data parameter should be created using the new_data() method. For more information see, Example 3. new_data()-
This returns a new instance of the watcher’s data struct with the type watcher-data.
end_interval()-
Predefined method that ends the current interval. The method performs the following actions:
-
Emits the event i_end.
-
Samples the interval data coverage.
-
Emits the event top.any_interval_ended.
-
Nullifies the data field.
-
The below example defines a watcher type that creates intervals when the speed of a vehicle exceeds the speed limit.
# define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit
watcher modifier speed_above:
v: vehicle
limit: speed
on @top.w_clk:
if(data == null):
if(v.state.speed > limit):
start_interval()
else:
if(v.state.speed <= limit):
end_interval()
See complete example.
The below example defines a watcher type that creates intervals that hold the maximal speed when the speed of a vehicle exceeds the speed limit.
# Define watcher data that records a speed
struct speed_watcher_data inherits any_watcher_data:
var speed: speed
record(speed, unit: kph)
# Define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit,
# and records the maximal exceeding speed
watcher modifier speed_above(speed_watcher_data):
v: vehicle
limit: speed
on @top.clk:
if(data == null):
if(v.state.speed > limit):
start_interval()
else:
if(v.state.speed <= limit):
end_interval()
on @i_clock:
if(data.speed < v.state.speed): data.speed = v.state.speed
See complete example.
The below example defines a watcher type that creates intervals when the vehicle exceeds the speed limit, and records the vehicle’s acceleration at the start of the interval.
# Define watcher data that records acceleration
struct speed_watcher_data inherits any_watcher_data:
var acceleration_when_exceeded: acceleration
record(acceleration_when_exceeded, unit: mpsps)
# Define a watcher type that create intervals
# when the speed of a vehicle exceeds some speed limit,
# and records the acceleration at the start of the interval
watcher modifier speed_above(speed_watcher_data):
v: vehicle
limit: speed
on @top.clk:
if(data == null):
if(v.state.speed > limit):
var d := new_data()
d.acceleration_when_exceeded = v.state.road_acceleration.lon
start_interval(d)
else:
if(v.state.speed <= limit):
end_interval()
See complete example.
any_watcher_data
any_watcher_data is a predefined struct used for describing intervals. This is the base type of all intervals emitted by watchers and checkers. The watcher will have a field called data of the declared <watcher-data> type.
start_time
start_time: time-
Specifies the simulation time when the interval started.
end_time
end_time: time-
Specifies the simulation time when the interval ended.
end_status
end_status: watcher_data_status-
Specifies the status of the interval. Can have one of the following values:
normal- Normal end of the interval.context_ended- The interval did not end, but its encapsulating scenario/modifier has ended.
issue
issue: issue_struct-
Holds the checker issue when this data is used for a checker declaration.
If you want to preserve interval data for later analysis, you can add a cover or record declaration for a specific field to the interval data struct. When using a cover or record inside an interval data struct, the following limitations apply:
- The ‘expression’ may only be the name of a field in the interval data struct.
- The data is always sampled at the end of the interval. The ‘event’ option is not applicable.
The following example defines a watcher data type that records a given speed for each interval.
# Define watcher data that records a speed
struct speed_watcher_data inherits any_watcher_data:
speed: speed
record(speed, unit: kph)
See complete example.
Compound types
list type
Define an ordered collection of elements of the same type
Type declaration
list of <type>
<type>- (Required) The type of the elements in the list.
You can specify a <list of type> in field or variable declarations, in expressions or in any construct where you can specify a <type>.
You can constrain the size of a list of generatable items and also apply constraints to its members.
You can access, modify and manipulate any list by defining a native method and using the list methods that are valid in that context.
This example declares a list of three items of type speed and constrains the value of each item to a range.
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"
extend test_config:
set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"
extend top.main:
slow_vehicle: vehicle
mid_vehicle: vehicle
fast_vehicle: vehicle
speeds: list of speed with:
keep(speeds.size() == 3)
keep(speeds[0] in [20..35]kph)
keep(speeds[1] in [40..55]kph)
keep(speeds[2] in [60..75]kph)
do parallel:
slow_vehicle.drive() with: speed(speeds[0])
mid_vehicle.drive() with: speed(speeds[1])
fast_vehicle.drive() with: speed(speeds[2])
This example declares a generated list and uses a native method to sort the list and return the value of the first item in the list (the item with the lowest value).
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"
extend test_config:
set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"
extend top.main:
my_ints: list of int
def get_lowest(ints: list of int) -> int is:
return ints.sort(it)[0]
do serial:
sut.car.drive() with: duration([4..7]s)
call logger.log_info("Lowest integer is $(get_lowest(my_ints))")
range type
Define an ordered pair of numeric values that specify a range starting from the lower value and ending at the upper value
Type declaration
range of <type>
<type>- (Required) The name of the type of the lower and upper values. The type can be one of the following: int, uint, float and physical types.
You can set fields of range types using literal ranges or single values of the corresponding type. You can use an equality constraint or the in operator to constrain the value of such a field.
Example 1
This example exhibits the different ways to constrain a range type field.
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"
extend test_config:
set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"
struct ranges:
int_range: range of int
# constrain the range to a specific range
keep(int_range == [5..10])
speed_range: range of speed
# constrain the range to be within a specific range
keep(speed_range in [20..50]kph)
length_range: range of length
# constrain the range to a specific value
keep(length_range == 50m)
extend top.main:
ranges: ranges
in sut_drive with: speed(ranges.speed_range)
do sut_drive: sut.car.drive() with: duration([3..7]s)