Skip to content

Commit

Permalink
Merge branch 'main' of github.com:BerkeleyLearnVerify/Scenic
Browse files Browse the repository at this point in the history
  • Loading branch information
Armando Banuelos authored and Armando Banuelos committed May 22, 2024
2 parents 50326f6 + e6832d1 commit 31bd71c
Show file tree
Hide file tree
Showing 6 changed files with 208 additions and 13 deletions.
4 changes: 2 additions & 2 deletions src/scenic/core/object_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -1017,7 +1017,7 @@ class Object(OrientedPoint):
behavior: Behavior for dynamic agents, if any (see :ref:`dynamics`). Default
value ``None``.
lastActions: Tuple of :term:`actions` taken by this agent in the last time step
(or `None` if the object is not an agent or this is the first time step).
(an empty tuple if the object is not an agent or this is the first time step).
"""

_scenic_properties = {
Expand All @@ -1042,7 +1042,7 @@ class Object(OrientedPoint):
"angularVelocity": PropertyDefault((), {"dynamic"}, lambda self: Vector(0, 0, 0)),
"angularSpeed": PropertyDefault((), {"dynamic"}, lambda self: 0),
"behavior": None,
"lastActions": None,
"lastActions": tuple(),
# weakref to scenario which created this object, for internal use
"_parentScenario": None,
}
Expand Down
38 changes: 29 additions & 9 deletions src/scenic/core/simulators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"""

import abc
from collections import OrderedDict, defaultdict
from collections import defaultdict
import enum
import math
import numbers
Expand Down Expand Up @@ -294,7 +294,10 @@ class Simulation(abc.ABC):
timestep (float): Length of each time step in seconds.
objects: List of Scenic objects (instances of `Object`) existing in the
simulation. This list will change if objects are created dynamically.
agents: List of :term:`agents` in the simulation.
agents: List of :term:`agents` in the simulation. An agent is any object that has
or had a behavior at any point in the simulation. The agents list may have objects
appended to the end as the simulation progresses (if a non-agent object has its
behavior overridden), but once an object is in the agents list its position is fixed.
result (`SimulationResult`): Result of the simulation, or `None` if it has not
yet completed. This is the primary object which should be inspected to get
data out of the simulation: the other undocumented attributes of this class
Expand Down Expand Up @@ -331,7 +334,6 @@ def __init__(
self.result = None
self.scene = scene
self.objects = []
self.agents = []
self.trajectory = []
self.records = defaultdict(list)
self.currentTime = 0
Expand Down Expand Up @@ -398,7 +400,7 @@ def __init__(
for obj in self.objects:
disableDynamicProxyFor(obj)
for agent in self.agents:
if agent.behavior._isRunning:
if agent.behavior and agent.behavior._isRunning:
agent.behavior._stop()
# If the simulation was terminated by an exception (including rejections),
# some scenarios may still be running; we need to clean them up without
Expand Down Expand Up @@ -441,10 +443,25 @@ def _run(self, dynamicScenario, maxSteps):
if maxSteps and self.currentTime >= maxSteps:
return TerminationType.timeLimit, f"reached time limit ({maxSteps} steps)"

# Clear lastActions for all objects
for obj in self.objects:
obj.lastActions = tuple()

# Update agents with any objects that now have behaviors (and are not already agents)
self.agents += [
obj for obj in self.objects if obj.behavior and obj not in self.agents
]

# Compute the actions of the agents in this time step
allActions = OrderedDict()
allActions = defaultdict(tuple)
schedule = self.scheduleForAgents()
if not set(self.agents) == set(schedule):
raise RuntimeError("Simulator schedule does not contain all agents")
for agent in schedule:
# If agent doesn't have a behavior right now, continue
if not agent.behavior:
continue

# Run the agent's behavior to get its actions
actions = agent.behavior._step()

Expand Down Expand Up @@ -472,11 +489,13 @@ def _run(self, dynamicScenario, maxSteps):
# Save actions for execution below
allActions[agent] = actions

# Log lastActions
agent.lastActions = actions

# Execute the actions
if self.verbosity >= 3:
for agent, actions in allActions.items():
print(f" Agent {agent} takes action(s) {actions}")
agent.lastActions = actions
self.actionSequence.append(allActions)
self.executeActions(allActions)

Expand All @@ -492,6 +511,7 @@ def setup(self):
but should call the parent implementation to create the objects in the
initial scene (through `createObjectInSimulator`).
"""
self.agents = []
for obj in self.scene.objects:
self._createObject(obj)

Expand Down Expand Up @@ -624,9 +644,9 @@ def executeActions(self, allActions):
functionality.
Args:
allActions: an :obj:`~collections.OrderedDict` mapping each agent to a tuple
of actions. The order of agents in the dict should be respected in case
the order of actions matters.
allActions: a :obj:`~collections.defaultdict` mapping each agent to a tuple
of actions, with the default value being an empty tuple. The order of
agents in the dict should be respected in case the order of actions matters.
"""
for agent, actions in allActions.items():
for action in actions:
Expand Down
30 changes: 30 additions & 0 deletions tests/core/test_simulators.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,3 +64,33 @@ class TestObj:
assert result is not None
assert result.records["test_val_1"] == [(0, "bar"), (1, "bar"), (2, "bar")]
assert result.records["test_val_2"] == result.records["test_val_3"] == "bar"


def test_simulator_bad_scheduler():
class TestSimulation(DummySimulation):
def scheduleForAgents(self):
# Don't include the last agent
return self.agents[:-1]

class TestSimulator(DummySimulator):
def createSimulation(self, scene, **kwargs):
return TestSimulation(scene, **kwargs)

scenario = compileScenic(
"""
behavior Foo():
take 1
class TestObj:
allowCollisions: True
behavior: Foo
for _ in range(5):
new TestObj
"""
)

scene, _ = scenario.generate(maxIterations=1)
simulator = TestSimulator()
with pytest.raises(RuntimeError):
result = simulator.simulate(scene, maxSteps=2)
30 changes: 30 additions & 0 deletions tests/syntax/test_dynamics.py
Original file line number Diff line number Diff line change
Expand Up @@ -2085,3 +2085,33 @@ def test_record():
(2, (4, 0, 0)),
(3, (6, 0, 0)),
)


## lastActions Property
def test_lastActions():
scenario = compileScenic(
"""
behavior Foo():
for i in range(4):
take i
ego = new Object with behavior Foo, with allowCollisions True
other = new Object with allowCollisions True
record ego.lastActions as ego_lastActions
record other.lastActions as other_lastActions
"""
)
result = sampleResult(scenario, maxSteps=4)
assert tuple(result.records["ego_lastActions"]) == (
(0, tuple()),
(1, (0,)),
(2, (1,)),
(3, (2,)),
(4, (3,)),
)
assert tuple(result.records["other_lastActions"]) == (
(0, tuple()),
(1, tuple()),
(2, tuple()),
(3, tuple()),
(4, tuple()),
)
109 changes: 109 additions & 0 deletions tests/syntax/test_modular.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from scenic.core.simulators import DummySimulator, TerminationType
from tests.utils import (
compileScenic,
sampleActionsFromScene,
sampleEgo,
sampleEgoActions,
sampleEgoFrom,
Expand Down Expand Up @@ -809,6 +810,75 @@ def test_override_behavior():
assert tuple(actions) == (1, -1, -2, 2)


def test_override_none_behavior():
scenario = compileScenic(
"""
scenario Main():
setup:
ego = new Object
compose:
wait
do Sub() for 2 steps
wait
scenario Sub():
setup:
override ego with behavior Bar
behavior Bar():
x = -1
while True:
take x
x -= 1
""",
scenario="Main",
)
actions = sampleEgoActions(scenario, maxSteps=4)
assert tuple(actions) == (None, -1, -2, None)


def test_override_leakage():
scenario = compileScenic(
"""
scenario Main():
setup:
ego = new Object with prop 1
compose:
do Sub1()
scenario Sub1():
setup:
override ego with prop 2, with behavior Bar
behavior Bar():
terminate
""",
scenario="Main",
)
scene = sampleScene(scenario)
assert scene.objects[0].prop == 1
sampleActionsFromScene(scene)
assert scene.objects[0].prop == 1

scenario = compileScenic(
"""
scenario Main():
setup:
ego = new Object with prop 1
compose:
do Sub1()
scenario Sub1():
setup:
override ego with prop 2, with behavior Bar
behavior Bar():
raise NotImplementedError()
wait
""",
scenario="Main",
)
scene = sampleScene(scenario)
assert scene.objects[0].prop == 1
with pytest.raises(NotImplementedError):
sampleActionsFromScene(scene)
assert scene.objects[0].prop == 1


def test_override_dynamic():
with pytest.raises(SpecifierError):
compileScenic(
Expand Down Expand Up @@ -1048,3 +1118,42 @@ def test_scenario_signature(body):
assert name4 == "qux"
assert p4.default is inspect.Parameter.empty
assert p4.kind is inspect.Parameter.VAR_KEYWORD


# lastActions Property
def test_lastActions_modular():
scenario = compileScenic(
"""
scenario Main():
setup:
ego = new Object
record ego.lastActions as lastActions
compose:
do Sub1() for 2 steps
do Sub2() for 2 steps
do Sub1() for 2 steps
wait
scenario Sub1():
setup:
override ego with behavior Bar
scenario Sub2():
setup:
override ego with behavior None
behavior Bar():
x = -1
while True:
take x
x -= 1
""",
scenario="Main",
)
result = sampleResult(scenario, maxSteps=6)
assert tuple(result.records["lastActions"]) == (
(0, tuple()),
(1, (-1,)),
(2, (-2,)),
(3, tuple()),
(4, tuple()),
(5, (-1,)),
(6, (-2,)),
)
10 changes: 8 additions & 2 deletions tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,10 @@ def sampleEgoActions(
asMapping=False,
timestep=timestep,
)
return [actions[0] for actions in allActions]
return [
actions[0] if actions else (None if singleAction else tuple())
for actions in allActions
]


def sampleEgoActionsFromScene(
Expand All @@ -108,7 +111,10 @@ def sampleEgoActionsFromScene(
)
if allActions is None:
return None
return [actions[0] for actions in allActions]
return [
actions[0] if actions else (None if singleAction else tuple())
for actions in allActions
]


def sampleActions(
Expand Down

0 comments on commit 31bd71c

Please sign in to comment.