-
Notifications
You must be signed in to change notification settings - Fork 58
Add dynamic rulebook code and example #59
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0d93359
4923689
da70943
f0f5368
8243d80
365d078
6107c6e
e2d6cf9
3384862
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -120,3 +120,5 @@ venv.bak/ | |
| dmypy.json | ||
|
|
||
| *.cproject | ||
|
|
||
| examples/dynamic_rulebook/*/outputs/ | ||
Large diffs are not rendered by default.
Large diffs are not rendered by default.
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is the example really specific to Town05? If not, can we change it to use Town01 so that it's not necessary to include another map in the repo? (Town01 is already under the |
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,179 @@ | ||
| """ | ||
| Framework for experimentation of multi-objective and dynamic falsification. | ||
|
|
||
| Author: Kai-Chun Chang. Based on Kesav Viswanadha's code. | ||
| """ | ||
|
|
||
| import time | ||
| import os | ||
| import numpy as np | ||
| from dotmap import DotMap | ||
| import traceback | ||
| import argparse | ||
| import importlib | ||
| import random | ||
|
|
||
| from verifai.samplers.scenic_sampler import ScenicSampler | ||
| from verifai.scenic_server import ScenicServer | ||
| from verifai.falsifier import generic_falsifier, generic_parallel_falsifier | ||
| from verifai.monitor import multi_objective_monitor, specification_monitor | ||
| from verifai.rulebook import rulebook | ||
|
|
||
| import networkx as nx | ||
| import pandas as pd | ||
|
|
||
| def announce(message): | ||
| lines = message.split('\n') | ||
| size = max([len(p) for p in lines]) + 4 | ||
| def pad(line): | ||
| ret = '* ' + line | ||
| ret += ' ' * (size - len(ret) - 1) + '*' | ||
| return ret | ||
| lines = list(map(pad, lines)) | ||
| m = '\n'.join(lines) | ||
| border = '*' * size | ||
| print(border) | ||
| print(m) | ||
| print(border) | ||
|
|
||
| """ | ||
| Runs all experiments in a directory. | ||
| """ | ||
| def run_experiments(path, rulebook=None, parallel=False, model=None, | ||
| sampler_type=None, headless=False, num_workers=5, output_dir='outputs', | ||
| experiment_name=None, max_time=None, n_iters=None, max_steps=300): | ||
| if not os.path.exists(output_dir): | ||
| os.mkdir(output_dir) | ||
| paths = [] | ||
| if os.path.isdir(path): | ||
| for root, _, files in os.walk(path): | ||
| for name in files: | ||
| fname = os.path.join(root, name) | ||
| if os.path.splitext(fname)[1] == '.scenic': | ||
| paths.append(fname) | ||
| else: | ||
| paths = [path] | ||
| for p in paths: | ||
| falsifier = run_experiment(p, rulebook=rulebook, | ||
| parallel=parallel, model=model, sampler_type=sampler_type, headless=headless, | ||
| num_workers=num_workers, max_time=max_time, n_iters=n_iters, max_steps=max_steps) | ||
| df = pd.concat([falsifier.error_table.table, falsifier.safe_table.table]) | ||
| if experiment_name is not None: | ||
| outfile = experiment_name | ||
| else: | ||
| root, _ = os.path.splitext(p) | ||
| outfile = root.split('/')[-1] | ||
| if parallel: | ||
| outfile += '_parallel' | ||
| if model: | ||
| outfile += f'_{model}' | ||
| if sampler_type: | ||
| outfile += f'_{sampler_type}' | ||
| outfile += '.csv' | ||
| outpath = os.path.join(output_dir, outfile) | ||
| print(f'(multi.py) Saving output to {outpath}') | ||
| df.to_csv(outpath) | ||
|
|
||
| """ | ||
| Runs a single falsification experiment. | ||
|
|
||
| Arguments: | ||
| path: Path to Scenic script to be run. | ||
| parallel: Whether or not to enable parallelism. | ||
| model: Which simulator model to use (e.g. scenic.simulators.newtonian.driving_model) | ||
| sampler_type: Which VerifAI sampelr to use (e.g. halton, scenic, ce, mab, etc.) | ||
| headless: Whether or not to display each simulation. | ||
| num_workers: Number of parallel workers. Only used if parallel is true. | ||
| """ | ||
| def run_experiment(scenic_path, rulebook=None, parallel=False, model=None, | ||
| sampler_type=None, headless=False, num_workers=5, max_time=None, | ||
| n_iters=5, max_steps=300): | ||
| # Construct rulebook | ||
| rb = rulebook | ||
|
|
||
| # Construct sampler (scenic_sampler.py) | ||
| print(f'(multi.py) Running Scenic script {scenic_path}') | ||
| params = {'verifaiSamplerType': sampler_type} if sampler_type else {} | ||
| params['render'] = not headless | ||
| params['seed'] = 0 | ||
| params['use2DMap'] = True | ||
| sampler = ScenicSampler.fromScenario(scenic_path, maxIterations=40000, params=params, model=model) | ||
| s_type = sampler.scenario.params.get('verifaiSamplerType', None) | ||
|
|
||
| # Construct falsifier (falsifier.py) | ||
| falsifier_params = DotMap( | ||
| n_iters=n_iters, | ||
| save_error_table=True, | ||
| save_safe_table=True, | ||
| max_time=max_time, | ||
| verbosity=1, | ||
| ) | ||
| server_options = DotMap(maxSteps=max_steps, verbosity=1, | ||
| scenic_path=scenic_path, scenario_params=params, scenario_model=model, | ||
| num_workers=num_workers) | ||
| falsifier_class = generic_parallel_falsifier if parallel else generic_falsifier | ||
| falsifier = falsifier_class(monitor=rb, ## modified | ||
| sampler_type=s_type, | ||
| sampler=sampler, | ||
| falsifier_params=falsifier_params, | ||
| server_options=server_options, | ||
| server_class=ScenicServer) | ||
| print(f'(multi.py) Sampler type: {falsifier.sampler_type}') | ||
|
|
||
| # Run falsification | ||
| t0 = time.time() | ||
| print('(multi.py) Running falsifier...') | ||
| falsifier.run_falsifier() | ||
| t = time.time() - t0 | ||
| print() | ||
| print(f'(multi.py) Generated {len(falsifier.samples)} samples in {t} seconds with {falsifier.num_workers} workers') | ||
| print(f'(multi.py) Number of counterexamples: {len(falsifier.error_table.table)}') | ||
| if not parallel: | ||
| print(f'(multi.py) Sampling time: {falsifier.total_sample_time}') | ||
| print(f'(multi.py) Simulation time: {falsifier.total_simulate_time}') | ||
| print(f'(multi.py) Confidence interval: {falsifier.get_confidence_interval()}') | ||
| return falsifier | ||
|
|
||
| if __name__ == '__main__': | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument('--scenic-path', '-sp', type=str, default='uberCrashNewton.scenic', | ||
| help='Path to Scenic script') | ||
| parser.add_argument('--graph-path', '-gp', type=str, default=None, | ||
| help='Path to graph file') | ||
| parser.add_argument('--rule-path', '-rp', type=str, default=None, | ||
| help='Path to rule file') | ||
| parser.add_argument('--segment-func-path', '-sfp', type=str, default=None, | ||
| help='Path to segment function file') | ||
| parser.add_argument('--output-dir', '-o', type=str, default=None, | ||
| help='Directory to save output trajectories') | ||
| parser.add_argument('--output-csv-dir', '-co', type=str, default=None, | ||
| help='Directory to save output error tables (csv files)') | ||
| parser.add_argument('--parallel', action='store_true') | ||
| parser.add_argument('--num-workers', type=int, default=5, help='Number of parallel workers') | ||
| parser.add_argument('--sampler-type', '-s', type=str, default=None, | ||
| help='verifaiSamplerType to use') | ||
| parser.add_argument('--experiment-name', '-e', type=str, default=None, | ||
| help='verifaiSamplerType to use') | ||
| parser.add_argument('--model', '-m', type=str, default='scenic.simulators.newtonian.driving_model') | ||
| parser.add_argument('--headless', action='store_true') | ||
| parser.add_argument('--n-iters', '-n', type=int, default=None, help='Number of simulations to run') | ||
| parser.add_argument('--max-time', type=int, default=None, help='Maximum amount of time to run simulations') | ||
| parser.add_argument('--single-graph', action='store_true', help='Only a unified priority graph') | ||
| parser.add_argument('--seed', type=int, default=0, help='Random seed') | ||
| parser.add_argument('--using-sampler', type=int, default=-1, help='Assigning sampler to use') | ||
| parser.add_argument('--max-simulation-steps', type=int, default=300, help='Maximum number of simulation steps') | ||
| parser.add_argument('--exploration-ratio', type=float, default=2.0, help='Exploration ratio') | ||
| args = parser.parse_args() | ||
| if args.n_iters is None and args.max_time is None: | ||
| raise ValueError('At least one of --n-iters or --max-time must be set') | ||
|
|
||
| random.seed(args.seed) | ||
| np.random.seed(args.seed) | ||
|
|
||
| rb = rulebook(args.graph_path, args.rule_path, args.segment_func_path, save_path=args.output_dir, single_graph=args.single_graph, | ||
| using_sampler=args.using_sampler, exploration_ratio=args.exploration_ratio) | ||
| run_experiments(args.scenic_path, rulebook=rb, | ||
| parallel=args.parallel, model=args.model, | ||
| sampler_type=args.sampler_type, headless=args.headless, | ||
| num_workers=args.num_workers, output_dir=args.output_csv_dir, experiment_name=args.experiment_name, | ||
| max_time=args.max_time, n_iters=args.n_iters, max_steps=args.max_simulation_steps) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| """ | ||
| TITLE: Multi 01 | ||
| AUTHOR: Kai-Chun Chang, kaichunchang@berkeley.edu | ||
| DESCRIPTION: The ego vehicle is driving along its lane when it encounters a blocking car ahead. The ego attempts to change to the opposite lane to bypass the blocking car before returning to its original lane. | ||
| """ | ||
|
|
||
| ################################# | ||
| # MAP AND MODEL # | ||
| ################################# | ||
|
|
||
| param map = localPath('../maps/Town05.xodr') | ||
| param carla_map = 'Town05' | ||
| model scenic.domains.driving.model | ||
|
|
||
| ################################# | ||
| # CONSTANTS # | ||
| ################################# | ||
|
|
||
| MODEL = 'vehicle.lincoln.mkz_2017' | ||
|
|
||
| param EGO_SPEED = VerifaiRange(6, 9) #7 | ||
| param DIST_THRESHOLD = VerifaiRange(12, 14) #13 | ||
| param BLOCKING_CAR_DIST = VerifaiRange(15, 20) | ||
| param BYPASS_DIST = VerifaiRange(4, 6) #5 | ||
|
|
||
| DIST_TO_INTERSECTION = 15 | ||
| TERM_DIST = 40 | ||
|
|
||
| ################################# | ||
| # AGENT BEHAVIORS # | ||
| ################################# | ||
|
|
||
| behavior EgoBehavior(path): | ||
| current_lane = network.laneAt(self) | ||
| laneChangeCompleted = False | ||
| bypassed = False | ||
| try: | ||
| do FollowLaneBehavior(globalParameters.EGO_SPEED, laneToFollow=current_lane) | ||
| interrupt when (distance to blockingCar) < globalParameters.DIST_THRESHOLD and not laneChangeCompleted: | ||
| do LaneChangeBehavior(path, is_oppositeTraffic=True, target_speed=globalParameters.EGO_SPEED) | ||
| do FollowLaneBehavior(globalParameters.EGO_SPEED, is_oppositeTraffic=True) until (distance to blockingCar) > globalParameters.BYPASS_DIST | ||
| laneChangeCompleted = True | ||
| interrupt when (blockingCar can see ego) and (distance to blockingCar) > globalParameters.BYPASS_DIST and not bypassed: | ||
| current_laneSection = network.laneSectionAt(self) | ||
| rightLaneSec = current_laneSection._laneToLeft | ||
| do LaneChangeBehavior(rightLaneSec, is_oppositeTraffic=False, target_speed=globalParameters.EGO_SPEED) | ||
| bypassed = True | ||
|
|
||
| ################################# | ||
| # SPATIAL RELATIONS # | ||
| ################################# | ||
|
|
||
| #Find lanes that have a lane to their left in the opposite direction | ||
| laneSecsWithLeftLane = [] | ||
| for lane in network.lanes: | ||
| for laneSec in lane.sections: | ||
| if laneSec._laneToLeft is not None: | ||
| if laneSec._laneToLeft.isForward is not laneSec.isForward: | ||
| laneSecsWithLeftLane.append(laneSec) | ||
|
|
||
| assert len(laneSecsWithLeftLane) > 0, \ | ||
| 'No lane sections with adjacent left lane with opposing \ | ||
| traffic direction in network.' | ||
|
|
||
| initLaneSec = Uniform(*laneSecsWithLeftLane) | ||
| leftLaneSec = initLaneSec._laneToLeft | ||
|
|
||
| spawnPt = new OrientedPoint on initLaneSec.centerline | ||
|
|
||
| ################################# | ||
| # SCENARIO SPECIFICATION # | ||
| ################################# | ||
|
|
||
| ego = new Car at spawnPt, | ||
| with blueprint MODEL, | ||
| with behavior EgoBehavior(leftLaneSec) | ||
|
|
||
| blockingCar = new Car following roadDirection from ego for globalParameters.BLOCKING_CAR_DIST, | ||
| with blueprint MODEL, | ||
| with viewAngle 90 deg | ||
|
|
||
| require (distance from blockingCar to intersection) > DIST_TO_INTERSECTION | ||
| terminate when (distance to spawnPt) > TERM_DIST | ||
|
|
||
| ################################# | ||
| # RECORDING # | ||
| ################################# | ||
|
|
||
| record initial (initLaneSec.polygon.exterior.coords) as initLaneCoords | ||
| record initial (leftLaneSec.polygon.exterior.coords) as leftLaneCoords | ||
| record (ego.lane is initLaneSec.lane) as egoIsInInitLane | ||
| record (ego.lane is leftLaneSec.lane) as egoIsInLeftLane |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| # ID 0 | ||
| # Node list | ||
| 0 rule0 | ||
| 1 rule1 | ||
| # Edge list |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # ID 0 | ||
| # Node list | ||
| 0 rule0 | ||
| 1 rule1 | ||
| # Edge list | ||
| 1 0 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| # ID 1 | ||
| # Node list | ||
| 0 rule0 | ||
| 1 rule1 | ||
| # Edge list | ||
| 0 1 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| # ID 2 | ||
| # Node list | ||
| 1 rule1 | ||
| # Edge list |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| import numpy as np | ||
|
|
||
| def segment_function(simulation): | ||
| positions = np.array(simulation.result.trajectory) | ||
| switch_idx_1 = len(simulation.result.trajectory) | ||
| switch_idx_2 = len(simulation.result.trajectory) | ||
| distances_to_obs = positions[:, 0, :] - positions[:, 1, :] | ||
| distances_to_obs = np.linalg.norm(distances_to_obs, axis=1) | ||
| for i in range(len(distances_to_obs)): | ||
| if distances_to_obs[i] < 8.5 and switch_idx_1 == len(simulation.result.trajectory): | ||
| switch_idx_1 = i | ||
| continue | ||
| if distances_to_obs[i] > 10 and switch_idx_1 < len(simulation.result.trajectory) and switch_idx_2 == len(simulation.result.trajectory): | ||
| switch_idx_2 = i | ||
| break | ||
| assert switch_idx_1 < len(simulation.result.trajectory), "Switching point 1 cannot be found" | ||
|
|
||
| indices_0 = np.arange(0, switch_idx_1) | ||
| indices_1 = np.arange(switch_idx_1, switch_idx_2) | ||
| indices_2 = np.arange(switch_idx_2, len(simulation.result.trajectory)) | ||
|
|
||
| return [indices_0, indices_1, indices_2] |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| import numpy as np | ||
|
|
||
| def rule0(simulation, indices): # safe distance to obstacle | ||
| if indices.size == 0: | ||
| return 1 | ||
| positions = np.array(simulation.result.trajectory) | ||
| distances_to_adv1 = positions[indices, [0], :] - positions[indices, [1], :] | ||
| distances_to_adv1 = np.linalg.norm(distances_to_adv1, axis=1) | ||
| rho = np.min(distances_to_adv1, axis=0) - 3 | ||
| return rho | ||
|
|
||
| def rule1(simulation, indices): # ego is in the left lane | ||
| if indices.size == 0: | ||
| return 1 | ||
| ego_is_in_left_lane = np.array(simulation.result.records["egoIsInLeftLane"], dtype=bool) | ||
| for i in indices: | ||
| if ego_is_in_left_lane[i][1]: | ||
| return -1 | ||
| return 1 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's move this to a new
.gitignorefile in theexamples/dynamic_rulebookdirectory, so that it will keep working even if we move that folder later.