Skip to content

Commit

Permalink
Merge pull request BerkeleyLearnVerify#22 from UCSCFormalMethods/webo…
Browse files Browse the repository at this point in the history
…ts-vacuum-experiments

implement scripts to collect data from vacuum example
  • Loading branch information
Eric-Vin authored Jan 28, 2023
2 parents dba48ae + 31202d8 commit 2e80d9c
Show file tree
Hide file tree
Showing 5 changed files with 206 additions and 11 deletions.
1 change: 1 addition & 0 deletions examples/webots/vacuum/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
logs
Original file line number Diff line number Diff line change
@@ -1,19 +1,53 @@
import json
from pathlib import Path
from datetime import datetime

from controller import Supervisor

import scenic
from scenic.simulators.webots import WebotsSimulator

# HELPER DATA STRUCTURES & FUNCTIONS
def getFilename(duration: int, numToys: int, iteration: int) -> str:
return f"vacuum_d{str(duration).zfill(2)}_t{str(numToys).zfill(3)}_i{str(iteration).zfill(2)}.json"

# CONSTANTS

# how many times perform simulations?
ITERATION = 10
# how long to run simulation for (minutes)
DURATION = 10
# number of toys to simulate
NUM_TOYS_LIST = [0, 1, 2, 4]

# save logs to `logs`
output_dir = Path(__file__).resolve().parent.parent.parent / "logs"
output_dir.mkdir(parents=True, exist_ok=True)

supervisor = Supervisor()
simulator = WebotsSimulator(supervisor)

path = supervisor.getCustomData()
print(f'Loading Scenic scenario {path}')
scenario = scenic.scenarioFromFile(path)

while True:
scene, _ = scenario.generate()
print('Starting new simulation...')
sim_results = simulator.simulate(scene, verbosity=2).result
for numToys in NUM_TOYS_LIST:
print(f'Loading Scenic scenario {path}')
params = {
"numToys": numToys, # how many toys to place
"duration": DURATION, # how long to run simulation for (minute)
}
scenario = scenic.scenarioFromFile(path, params=params)
for i in range(ITERATION):
filename = getFilename(duration=DURATION, numToys=numToys, iteration=i + 1)
if (output_dir / filename).is_file():
print(f"Skipping simulation for {numToys} toys, #{i + 1} iteration because the file already exists")
continue
print("Calculate", filename)

ts = datetime.now().strftime("%m-%d-%Y-%H-%M-%S")

scene, _ = scenario.generate()
sim_results = simulator.simulate(scene, verbosity=2).result

print(sim_results.records)
s = json.dumps({"params": params, "results": sim_results.records}, indent=4)
with open(output_dir / filename, "x") as f:
f.write(s)
54 changes: 54 additions & 0 deletions examples/webots/vacuum/plot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import json
from pathlib import Path
import sys

import matplotlib.pyplot as plt
from matplotlib.path import Path as PlotPath
from matplotlib.patches import PathPatch
from matplotlib.collections import PatchCollection
import numpy as np
from shapely.geometry import box, LineString

vacuum_radius = 0.335 / 2

# pass the path to the log to argv[1] and it will make a plot

def main(debug=False, plot=False):
with open(sys.argv[1]) as f:
json_str = f.read()
obj = json.loads(json_str)

floor = box(-2.5, -2.5, 2.5, 2.5)

positions = []

for position in obj.get("results").get("VacuumPosition"):
x = position[1][0]
y = position[1][1]
positions.append((x, y))
line = LineString(positions)
dilated = line.buffer(vacuum_radius)

fig, ax = plt.subplots()
ax.set_xlim([-2.5, 2.5])
ax.set_ylim([-2.5, 2.5])
plot_polygon(ax, dilated, facecolor="lightblue", edgecolor="red")
plt.show()

# Plots a Polygon to pyplot `ax`
def plot_polygon(ax, poly, **kwargs):
path = PlotPath.make_compound_path(
PlotPath(np.asarray(poly.exterior.coords)[:, :2]),
*[PlotPath(np.asarray(ring.coords)[:, :2]) for ring in poly.interiors],
)

patch = PathPatch(path, **kwargs)
collection = PatchCollection([patch], **kwargs)

ax.add_collection(collection, autolim=True)
ax.autoscale_view()
return collection


if __name__ == "__main__":
main()
90 changes: 90 additions & 0 deletions examples/webots/vacuum/summary.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import json
from pathlib import Path
from collections import defaultdict
from statistics import mean, median, stdev, quantiles

import matplotlib.pyplot as plt
from matplotlib.path import Path as PlotPath
from matplotlib.patches import PathPatch
from matplotlib.collections import PatchCollection
import numpy as np
from shapely.geometry import box, LineString

output_dir = Path(__file__).parent / "logs"

# find all json files in the output directory
files = output_dir.glob("*.json")

vacuum_radius = 0.335 / 2


def main(debug=False, plot=False):
# number of toys to list of coverage
aggregate = defaultdict(list)

for file in files:
with file.open() as f:
if debug:
print(f"processing {file.name}...")
json_str = f.read()
obj = json.loads(json_str)

floor = box(-2.5, -2.5, 2.5, 2.5)

positions = []

for position in obj.get("results").get("VacuumPosition"):
x = position[1][0]
y = position[1][1]
positions.append((x, y))
line = LineString(positions)
dilated = line.buffer(vacuum_radius)

if plot:
fig, ax = plt.subplots()
ax.set_xlim([-2.5, 2.5])
ax.set_ylim([-2.5, 2.5])
plot_polygon(ax, dilated, facecolor="lightblue", edgecolor="red")
plt.show()

floor_area = floor.area
cleaned_area = dilated.area
if debug:
print(
"coverage: {:.2} / {:.2} = {:.2%}".format(
cleaned_area, floor_area, cleaned_area / floor_area
),
)
aggregate[obj.get("params").get("numToys")].append(
cleaned_area / floor_area
)

for numToys, fractions in aggregate.items():
stat = {
"sample": len(fractions),
"mean": mean(fractions),
"median": median(fractions),
"stdev": stdev(fractions)
}
print(f"coverage for {numToys} toy(s)")
for key, value in stat.items():
print(f" {key}: {value}")


# Plots a Polygon to pyplot `ax`
def plot_polygon(ax, poly, **kwargs):
path = PlotPath.make_compound_path(
PlotPath(np.asarray(poly.exterior.coords)[:, :2]),
*[PlotPath(np.asarray(ring.coords)[:, :2]) for ring in poly.interiors],
)

patch = PathPatch(path, **kwargs)
collection = PatchCollection([patch], **kwargs)

ax.add_collection(collection, autolim=True)
ax.autoscale_view()
return collection


if __name__ == "__main__":
main()
24 changes: 20 additions & 4 deletions examples/webots/vacuum/vacuum.scenic
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import numpy as np
import trimesh
import random

param numToys = 0
param duration = 10

# TODO DENSITY NOT GETTING WRITTEN
# TODO Assigning random seed inside `with x` statement breaks reproducibility

## Class Definitions ##

class Vacuum(WebotsObject):
Expand Down Expand Up @@ -159,9 +165,19 @@ toy_stack = new BlockToy on toy_stack
toy_stack = new BlockToy on toy_stack

# Spawn some toys
for _ in range(4):
new Toy on floor
for _ in range(globalParameters.numToys):
new Toy on floor.topSurface

# toy = new BlockToy on floor.topSurface
# toy = new BlockToy on toy.topSurface
# toy = new BlockToy on toy.topSurface
# toy = new BlockToy on toy.topSurface

# toy = new BlockToy on floor.topSurface
# toy = new BlockToy on toy.topSurface
# toy = new BlockToy on toy.topSurface
# toy = new BlockToy on toy.topSurface

## Simulation Setup ##
terminate after 1*60 seconds
record ego.position as VacuumPosition
terminate after globalParameters.duration * 60 seconds
record (ego.position.x, ego.position.y) as VacuumPosition

0 comments on commit 2e80d9c

Please sign in to comment.