BerkeleyLearnVerify / Scenic

A compiler and scenario generator for the Scenic scenario description language.
https://scenic-lang.org/
Other
274 stars 92 forks source link

Bug Report: Unable to override behavior if no behavior is given initially #222

Closed beyazit-yalcinkaya closed 4 months ago

beyazit-yalcinkaya commented 6 months ago

System Details

  1. Python 3.9.10
  2. Scenic 3.0.0b2
  3. 2.3 GHz Quad-Core Intel Core i7 macOS Sonoma 14.3.1 (23D60)
  4. Newtonian

Detailed Description

If a behavior is not specified for an agent initially, its behavior cannot be overridden afterward by another scenario.

Steps To Reproduce

  1. To reproduce the issue run the following python script and scenic scenario. Scenario:
    
    """ Scenario Description
    Based on 2019 Carla Challenge Traffic Scenario 03.
    Leading vehicle decelerates suddenly due to an obstacle and 
    ego-vehicle must react, performing an emergency brake or an avoidance maneuver.
    """
    param map = localPath('../../../tests/scenic/Town01.xodr')
    # param map = localPath('../../tests/scenic/Town01.xodr')
    param carla_map = 'Town01'

model scenic.simulators.newtonian.driving_model

CONSTANTS

EGO_SPEED = 10 THROTTLE_ACTION = 0.6 BRAKE_ACTION = 1.0 EGO_TO_OBSTACLE = Range(-20, -15) EGO_BRAKING_THRESHOLD = 11

behavior FollowTrajectoryBehaviorAvoidCollision(target_speed, trajectory): try: do FollowTrajectoryBehavior(target_speed=target_speed, trajectory=trajectory)

interrupt when withinDistanceToAnyObjs(self, EGO_BRAKING_THRESHOLD):
    take SetBrakeAction(BRAKE_ACTION)

scenario SubScenario1(target): setup:

print(f"X = {X}")

    # print(f"EGO_TO_OBSTACLE = {EGO_TO_OBSTACLE}")
    override target with behavior FollowLaneBehavior(EGO_SPEED)

scenario SubScenario2(target, direction): setup:

print(f"X = {X}")

    # print(f"EGO_TO_OBSTACLE = {EGO_TO_OBSTACLE}")
    current_lane = target.lane
    straight_manuevers = filter(lambda i: i.type == direction, current_lane.maneuvers)
    maneuver = None
    if len(straight_manuevers) > 0:
        maneuver = Uniform(*straight_manuevers)
    assert maneuver
    override target with behavior FollowTrajectoryBehaviorAvoidCollision(target_speed=5, trajectory=[target.lane, maneuver.connectingLane, maneuver.endLane])

    points = maneuver.connectingLane.points[1:]
    if len(points) > 0:
        point = Uniform(*maneuver.connectingLane.points)
    obstacle = new Car at point

scenario SubScenario2S(target): compose: do SubScenario2(target, ManeuverType.STRAIGHT)

scenario SubScenario2L(target): compose: do SubScenario2(target, ManeuverType.LEFT_TURN)

scenario Main(): setup:

print(f"X = {X}")

    # print(f"EGO_TO_OBSTACLE = {EGO_TO_OBSTACLE}")
    ego = new Car following roadDirection from 171.87 @ 2.04 for EGO_TO_OBSTACLE
compose:
    do SubScenario1(ego) until (distance to intersection) < 5
    do choose SubScenario2S(ego), SubScenario2L(ego)
Python script:

import math import os.path import sys

from dotmap import DotMap

from verifai.samplers import ScenicSampler from verifai.scenic_server import ScenicServer from verifai.falsifier import generic_falsifier, compositional_falsifier from verifai.monitor import specification_monitor, mtl_specification

Load the Scenic scenario and create a sampler from it

if len(sys.argv) > 1: path = sys.argv[1] else: path = os.path.join(os.path.dirname(file), 'newtonian/intersection.scenic')

sampler = ScenicSampler.fromScenario(path, mode2D=True)

Define the specification (i.e. evaluation metric) as an MTL formula.

Our example spec will say that the ego object stays at least 5 meters away

from all other objects.

class MyMonitor(specification_monitor): def init(self): self.specification = mtl_specification(['G safe']) super().init(self.specification)

def evaluate(self, simulation):
    # Get trajectories of objects from the result of the simulation
    traj = simulation.result.trajectory

    # Compute time-stamped sequence of values for 'safe' atomic proposition;
    # we'll define safe = "distance from ego to all other objects > 5"
    safe_values = []
    for positions in traj:
        ego = positions[0]
        dist = min((ego.distanceTo(other) for other in positions[1:]),
                   default=math.inf)
        safe_values.append(dist - 5)
    eval_dictionary = {'safe' : list(enumerate(safe_values)) }

    # Evaluate MTL formula given values for its atomic propositions
    return self.specification.evaluate(eval_dictionary)

Set up the falsifier

falsifier_params = DotMap( n_iters=2, verbosity=1, save_error_table=True, save_safe_table=True,

uncomment to save these tables to files; we'll print them out below

# error_table_path='error_table.csv',
# safe_table_path='safe_table.csv'

) server_options = DotMap(maxSteps=100, verbosity=0) falsifier = generic_falsifier(sampler=sampler, monitor=MyMonitor(), falsifier_params=falsifier_params, server_class=ScenicServer, server_options=server_options)

Perform falsification and print the results

falsifier.run_falsifier() print('Error table:') print(falsifier.error_table.table) print('Safe table:') print(falsifier.safe_table.table)

When this program is run, one can see that the ego is not moving.
2. If we change the declaration of `ego` in the `Main` scenario as follows, `ego` moves as expected.

ego = new Car following roadDirection from 171.87 @ 2.04 for EGO_TO_OBSTACLE, with behavior FollowLaneBehavior(0)


Notice that the only difference is that now, we specify a dummy behavior for `ego`.

### Issue Submission Checklist

- [X] I am reporting an issue, not asking a question
- [X] I checked the open issues, forum, etc. and have not found any solution
- [x] I have provided all necessary code, etc. to reproduce the issue
Eric-Vin commented 6 months ago

Can be reproduced with the following minimal example:

param map = localPath('assets/maps/CARLA/Town05.xodr')
param carla_map = 'Town05'
param time_step = 1.0/10

model scenic.domains.driving.model

behavior TestBehavior1():
    while True:
        print("FOO")
        wait

behavior TestBehavior2():
    while True:
        print("BAR")
        wait

scenario Sub(target):
    setup:
        override target with behavior TestBehavior2()

scenario Main():
    setup:
        ego = new Car #with behavior TestBehavior1()
    compose:
        do Sub(ego)
Eric-Vin commented 5 months ago

Workaround for now: Set a default behavior.

Eric-Vin commented 4 months ago

Fixed in #252