Skip to content

Define relative motion

The movement modifiers that set a speed, position or lane can be either absolute or relative. For example, speed([40..60]kph) is an absolute speed whereas speed([10..15]kph, faster_than: sut.car) is a relative speed. When you describe a vehicle’s movement relative to another vehicle, the recommendations described below should be applied.

These modifiers also have a track parameter that specifies whether the vehicle reacts to the actual behavior of the referenced vehicle or to its expected behavior. By default, the vehicle reacts to the actual behavior of the referenced vehicle, but there may be cases where the referenced vehicle behaves in ways that you want the vehicle to ignore. In those cases, you can require the vehicle to track the projected behavior of the referenced vehicle.

Avoid specifying absolute speed for an entire movement

Motivation

All movement modifiers have an optional at: parameter, with the following possible values:

  • all – the modifier is enforced throughout this phase (default)
  • start – the modifier is enforced at the start of the phase
  • end – the modifier is enforced at the end of the phase

However, defining a speed() modifier with the default at: all parameter might overly constrain a drive and cause scenario execution to fail.

Problematic coding style

The following code requires the SUT vehicle to travel at a constant speed throughout the drive. If other modifiers are also specified, they might not be met during scenario execution.

OSC2 code: do not use at: all for speed()
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle

    do serial():
        parallel(overlap:equal):
            sut.car.drive() with:
                speed([40..60]kph, at: all)
        lead_vehicle.drive()

extend top.main:
    do sut.lead_vehicle_braking()

Recommended coding style

It is recommended to specify either at: start or at: end when using the speed() modifier.

OSC2 code: use at: start or at: end for speed()
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle

    do serial():
        parallel(overlap:equal):
            sut.car.drive() with:
                speed([40..60]kph, at: start)
                keep_speed()
            lead_vehicle.drive()

extend top.main:

    do sut.lead_vehicle_braking()

Linter rule name

LINT_ABSOLUTE_SPEED

Do not define the SUT’s movements relative to other actors

Motivation

Defining the SUT’s behavior relative to an NPC is not recommended because the actual behavior of the SUT is unpredictable and might lead to extended or incomplete scenario execution.

Problematic coding style

The intention of the following scenario is that a lead vehicle challenges the SUT by braking suddenly. However, the actual SUT might behave differently than described here, changing lanes, for example, instead of staying in the same lane.

OSC2 code: do not define SUT behavior relative to NPCs
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle
    sut_safety_time_distance: time with:
        keep(default it == 2sec)

    do serial():
        parallel(overlap:equal):
            sut.car.drive() with:
                lane(same_as: lead_vehicle, at: all) # triggers LINT_ABSOLUTE_SPEED
                position(time: [2..2.5]second, behind: lead_vehicle, at: start)
                position(time: [0.1..1.5]second, behind: lead_vehicle, at: end)
            lead_vehicle.drive()
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

Recommended coding style

In this version of the scenario, Foretify can control the behavior of the lead_vehicle to make the scenario execute as intended.

OSC2 code: define behavior of NPCs relative to the SUT
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle

    do serial():
        parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                lane(same_as: sut.car, at: start)
                keep_lane()
                position(time: [2..2.5]second, ahead_of: sut.car, at: start)
                position(time: [0.1..1.5]second, ahead_of: sut.car, at: end)
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

Linter rule name

LINT_RELATIONAL_EGO_DRIVE

Define a safety distance parameter

Motivation

The initial conditions of a drive—for example, bringing a lead_vehicle within [2..2.5] seconds ahead of the SUT—might not be met because some SUTs have a defined safety region ahead of them. In that case, their safety-keeping logic doesn’t allow other vehicles to stay in this region. Once any vehicle enters the safety region, the SUT brakes.

Problematic coding style

In the following scenario, if the SUT is designed to maintain at least a three second safety distance from any leading vehicle, it will not let the NPC reach the distance ahead of it that is required by the scenario in order to start.

OSC2 code: no defined safety distance parameter
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle

    do serial():
        parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                lane(same_as: sut.car, at: start)
                keep_lane()
                position(time: [2..2.5]second, ahead_of: sut.car, at: start)
                position(time: [0.1..1.5]second)
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

Recommended coding style

To solve this problem, you could simply add the three seconds to the specified initial position like this: [5..7.5]second.

However, the safety distance may be defined differently in different circumstances or for different versions of the SUT. It makes sense to define a parameter that represents the default SUT safety distance so that it can be set to different values.

As shown below, using the sut_safety_distance parameter makes it clear that the NPC is outside of the SUT’s safety zone at the beginning of the drive but inside the safety zone at the end.

OSC2 code: safety_distance parameter defined
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle
    sut_safety_time_distance: time with:
        keep(default it == 3sec)

    do serial():
        parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                lane(same_as: sut.car, at: start)
                keep_lane()
                position(time: [sut_safety_time_distance..sut_safety_time_distance + 2.5second],
                    ahead_of: sut.car, at: start)
                position(time: [0.1second..sut_safety_time_distance],
                    ahead_of: sut.car, at: end )
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

If you load this scenario with a version of the SUT that has a different safety distance, you can easily adjust the sut_safety_distance parameter accordingly for all relative modifiers:

OSC2 code: specify a different safety distance
extend sut.lead_vehicle_braking:
    keep(sut_safety_time_distance == 2sec)

extend top.main:

    do sut.lead_vehicle_braking()

Linter rule name

Not yet implemented.

Use the track parameter properly

Motivation

At some point in an NPC’s drive, you might want it to track the actual movement of the SUT. At another point, you might want it to track the projected movement of the SUT. The projected movement is a projection of the state of the reference vehicle at the start of the drive, using its actual speed at the start of the drive and calculating its position at the end of the drive, assuming that the speed is kept.

Problematic coding style

The lead_vehicle in the scenario below will most likely fail to reach the end condition of the drive, [0.1second..sut_safety_time_distance], because the SUT will brake and not let the lead_vehicle get that dangerously close to it. This is how a properly working SUT should react.

However, with the default track: actual option, the target position for the NPC is constantly updated based on the changes in the state of the SUT. This means the lead_vehicle will also brake since it must try to reach its target position. At some point, the SUT will completely stop and the lead_vehicle will stop ahead of it, resulting in a very unrealistic situation and missing the intent of the scenario.

Even worse, if the SUT accelerates, the lead_vehicle also accelerates in an effort to reach the end condition of the drive. This prevents the collision that would normally occur and may hide critical bugs in the SUT.

OSC2 code: with track: actual, lead_vehicle's target position is constantly updated
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle
    sut_safety_time_distance: time with:
        keep(default it == 2sec)

    do serial():
        parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                lane(same_as: sut.car, at: start)
                keep_lane()
                position(time: [sut_safety_time_distance..sut_safety_time_distance + 2.5second],
                    ahead_of: sut.car, at: start)
                position(time: [0.1second..sut_safety_time_distance],
                    ahead_of: sut.car, at: end)
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

Recommended coding style

Using the track: projected option in the second position() modifier requires Foretify to determine the state of the SUT at the beginning of the drive and project the end position of the SUT based on that initial state. The projection assumes the SUT keeps driving at the same speed. Then Foretify calculates the lead_vehicle’s position based on this projection and does not update it during the drive.

OSC2 code: with track: projected, lead_vehicle's target position is not updated
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle
    sut_safety_time_distance: time with:
        keep(default it == 2sec)

    do serial():
        parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                lane(same_as: sut.car, at: start)
                keep_lane()
                position(time: [sut_safety_time_distance..sut_safety_time_distance + 2.5second], ahead_of: sut.car, at: start)
                position(time: [0.1second..sut_safety_time_distance], ahead_of: sut.car, at: end , track: projected)
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

Another potential issue is that the SUT might choose to avoid the danger of the aggressively slowing NPC by changing lanes. In this case, the lead_vehicle will chase after it and switch into the lane of the SUT, again resulting in a very extreme and unrealistic scenario.

To prevent the lead_vehicle from following the SUT into a different lane at the end of the drive, you can again use the track parameter. The first lane() modifier defines the start condition with the default track: actual, and the second defines the end condition with track: projected.

OSC2 code: with track: projected, lead_vehicle does not change lanes
import "$FTX_BASIC/exe_platforms/sumo_ssp/config/sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/straight_long_road.xodr"

scenario sut.lead_vehicle_braking:
    lead_vehicle: vehicle
    sut_safety_time_distance: time with:
        keep(default it == 2sec)

    do serial():
        parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                lane(same_as: sut.car, at: start)
                lane(same_as: sut.car, track: projected, at: end)
                position(time: [sut_safety_time_distance..sut_safety_time_distance + 2.5second],
                    ahead_of: sut.car, at: start)
                position(time: [0.1second..sut_safety_time_distance],
                    ahead_of: sut.car, track: projected, at: end )
    with:
        duration([10..10]second)

extend top.main:

    do sut.lead_vehicle_braking()

Linter rule name

Not yet implemented.

Avoid absolute position after relative position

Motivation

In some cases, you might want to bring the NPC to a specific location, like a highway exit, after the previous phase where it had travelled relative to the SUT which was not being controlled longitudinally by Foretify. This can create problematic situations because the SUT might decide in the preceding phase to drive much faster or much slower than what the planner assumed. In this case, the NPC (which moved relative to the SUT) might pass or stay very far from the desired highway exit in the relative drive and therefore might miss the needed exit and hence the intent of the absolute position.

Problematic coding style

In phase p1 of the code below, the NPC is requested to drive relative to the SUT (ahead of it), while in phase p2 the NPC is requested to get to a highway exit in 2 seconds. These requirements create a large dependency on the actual speed of the SUT in phase p1 and might fail the scenario as an incomplete scenario if the SUT drives very fast or very slow.

OSC2 code: unsuccessful absolute position after a relative position
import "$FTX/env/basic/exe_platforms/model_ssp/config/model_sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/Town04.xodr"

scenario sut.lead_vehicle_and_hw_exit:
    lead_vehicle: vehicle
    highway_exit: highway_exit

    do serial():
        p1: parallel(overlap:equal):
                sut.car.drive()
                lead_vehicle.drive() with:
                    position(distance: [20..30]m, ahead_of: sut.car, at: end)
        p2: parallel(overlap:equal):
                sut.car.drive()
                lead_vehicle.drive() with:
                    along(highway_exit, at :end)
                    duration(time: 2sec)

extend top.main:
    do sut.lead_vehicle_and_hw_exit()

Recommended coding style

To better handle cases like the one above, try to estimate the maximal expected speed of the SUT, max_expected_speed, and use it as a guideline for the planner. In this way, you ensure that the vehicles don't pass the exit (a definite scenario failure). However, this will most likely also bring the vehicle relatively far from the exit at the end of p1 (since the actual speed of the SUT would probably be lower than the max_expected_speed). To overcome this, add a duration constraint on the p1 phase so that the created gap won't be too big. Also remove the duration requirement on the p2 phase or increase its tolerance to allow more time for the NPC to compensate for the gap and reach the exit. The following example illustrates this strategy.

OSC2 code: successful absolute position after a relative position
import "$FTX/env/basic/exe_platforms/model_ssp/config/model_sumo_config.osc"

extend test_config:
    set map = "$FTX_PACKAGES/maps/Town04.xodr"

scenario sut.lead_vehicle_and_hw_exit:
    lead_vehicle: vehicle
    highway_exit: highway_exit
    max_expected_speed: speed with:
        keep (it == 70kph)

    do serial():
        p1: parallel(overlap:equal, duration: 1sec):
            sut.car.drive() with:
                speed(max_expected_speed, run_mode: best_effort)
            lead_vehicle.drive() with:
                position(distance: [20..30]m, ahead_of: sut.car, at: end)
        p2: parallel(overlap:equal):
            sut.car.drive()
            lead_vehicle.drive() with:
                along(highway_exit, at :end)

extend top.main:
    do sut.lead_vehicle_and_hw_exit()