Skip to content

Commit 3af563d

Browse files
authored
Merge pull request #187 from BerkeleyLearnVerify/dynamics-refactor
Refactor dynamics.py; fix message for rejected simulations
2 parents dc8f524 + 27f577e commit 3af563d

File tree

13 files changed

+599
-550
lines changed

13 files changed

+599
-550
lines changed

docs/conf.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,8 @@ def maketable(items, columns=5, gap=4):
202202
)
203203
from sphinx.ext.autodoc import ClassDocumenter, FunctionDocumenter
204204

205-
from scenic.core.dynamics import Behavior, DynamicScenario, Monitor
205+
from scenic.core.dynamics.behaviors import Behavior, Monitor
206+
from scenic.core.dynamics.scenarios import DynamicScenario
206207

207208

208209
class ScenicBehavior(PyFunction):
@@ -557,7 +558,8 @@ def key(entry):
557558

558559
from sphinx.ext.autodoc import ClassDocumenter
559560

560-
from scenic.core.dynamics import Behavior, DynamicScenario
561+
from scenic.core.dynamics.behaviors import Behavior
562+
from scenic.core.dynamics.scenarios import DynamicScenario
561563

562564
orig_add_directive_header = ClassDocumenter.add_directive_header
563565

src/scenic/core/dynamics/__init__.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
"""Support for dynamic behaviors and modular scenarios.
2+
3+
A few classes are exposed here for external use, including:
4+
5+
* `Action`;
6+
* `GuardViolation`, `InvariantViolation`, and `PreconditionViolation`;
7+
* `StuckBehaviorWarning`.
8+
9+
Everything else defined in the submodules is an implementation detail and
10+
should not be used outside of Scenic (it may change at any time).
11+
"""
12+
13+
from .actions import Action
14+
from .guards import GuardViolation, InvariantViolation, PreconditionViolation
15+
from .utils import RejectSimulationException, StuckBehaviorWarning
16+
17+
#: Timeout in seconds after which a `StuckBehaviorWarning` will be raised.
18+
stuckBehaviorWarningTimeout = 10

src/scenic/core/dynamics/actions.py

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
"""Actions taken by dynamic agents."""
2+
3+
import abc
4+
5+
6+
class Action(abc.ABC):
7+
"""An :term:`action` which can be taken by an agent for one step of a simulation."""
8+
9+
def canBeTakenBy(self, agent):
10+
"""Whether this action is allowed to be taken by the given agent.
11+
12+
The default implementation always returns True.
13+
"""
14+
return True
15+
16+
@abc.abstractmethod
17+
def applyTo(self, agent, simulation):
18+
"""Apply this action to the given agent in the given simulation.
19+
20+
This method should call simulator APIs so that the agent will take this action
21+
during the next simulated time step. Depending on the simulator API, it may be
22+
necessary to batch each agent's actions into a single update: in that case you
23+
can have this method set some state on the agent, then apply the actual update
24+
in an overridden implementation of `Simulation.executeActions`. For examples,
25+
see the CARLA interface: `scenic.simulators.carla.actions` has some CARLA-specific
26+
actions which directly call CARLA APIs, while the generic steering and braking
27+
actions from `scenic.domains.driving.actions` are implemented using the batching
28+
approach (see for example the ``setThrottle`` method of the class
29+
`scenic.simulators.carla.model.Vehicle`, which sets state later read by
30+
``CarlaSimulation.executeActions`` in `scenic.simulators.carla.simulator`).
31+
"""
32+
raise NotImplementedError
33+
34+
35+
class _EndSimulationAction(Action):
36+
"""Special action indicating it is time to end the simulation.
37+
38+
Only for internal use.
39+
"""
40+
41+
def __init__(self, line):
42+
self.line = line
43+
44+
def __str__(self):
45+
return f'"terminate simulation" executed on line {self.line}'
46+
47+
def applyTo(self, agent, simulation):
48+
assert False
49+
50+
51+
class _EndScenarioAction(Action):
52+
"""Special action indicating it is time to end the current scenario.
53+
54+
Only for internal use.
55+
"""
56+
57+
def __init__(self, scenario, line):
58+
self.scenario = scenario
59+
self.line = line
60+
61+
def __str__(self):
62+
return f'"terminate" executed on line {self.line}'
63+
64+
def applyTo(self, agent, simulation):
65+
assert False

src/scenic/core/dynamics/behaviors.py

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
"""Behaviors and monitors."""
2+
3+
import functools
4+
import inspect
5+
import itertools
6+
import sys
7+
import warnings
8+
9+
from scenic.core.distributions import Samplable, toDistribution
10+
import scenic.core.dynamics as dynamics
11+
from scenic.core.errors import InvalidScenarioError
12+
from scenic.core.type_support import CoercionFailure
13+
from scenic.core.utils import alarm
14+
15+
from .invocables import Invocable
16+
from .utils import StuckBehaviorWarning
17+
18+
19+
class Behavior(Invocable, Samplable):
20+
"""Dynamic behaviors of agents.
21+
22+
Behavior statements are translated into definitions of subclasses of this class.
23+
"""
24+
25+
_noActionsMsg = (
26+
'does not take any actions (perhaps you forgot to use "take" or "do"?)'
27+
)
28+
29+
def __init_subclass__(cls):
30+
if "__signature__" in cls.__dict__:
31+
# We're unpickling a behavior; skip this step.
32+
return
33+
34+
if cls.__module__ is not __name__:
35+
import scenic.syntax.veneer as veneer
36+
37+
if veneer.currentScenario:
38+
veneer.currentScenario._behaviors.append(cls)
39+
40+
target = cls.makeGenerator
41+
target = functools.partial(target, 0, 0) # account for Scenic-inserted args
42+
cls.__signature__ = inspect.signature(target)
43+
44+
def __init__(self, *args, **kwargs):
45+
args = tuple(toDistribution(arg) for arg in args)
46+
kwargs = {name: toDistribution(arg) for name, arg in kwargs.items()}
47+
48+
# Validate arguments to the behavior
49+
sig = inspect.signature(self.makeGenerator)
50+
sig.bind(None, *args, **kwargs) # raises TypeError on incompatible arguments
51+
Samplable.__init__(self, itertools.chain(args, kwargs.values()))
52+
Invocable.__init__(self, *args, **kwargs)
53+
54+
if not inspect.isgeneratorfunction(self.makeGenerator):
55+
raise InvalidScenarioError(f"{self} {self._noActionsMsg}")
56+
57+
@classmethod
58+
def _canCoerceType(cls, ty):
59+
return issubclass(ty, cls) or ty in (type, type(None))
60+
61+
@classmethod
62+
def _coerce(cls, thing):
63+
if thing is None or isinstance(thing, cls):
64+
return thing
65+
elif issubclass(thing, cls):
66+
return thing()
67+
else:
68+
raise CoercionFailure(f"expected type of behavior, got {thing}")
69+
70+
def sampleGiven(self, value):
71+
args = (value[arg] for arg in self._args)
72+
kwargs = {name: value[val] for name, val in self._kwargs.items()}
73+
return type(self)(*args, **kwargs)
74+
75+
def _assignTo(self, agent):
76+
if self._agent and agent is self._agent._dynamicProxy:
77+
# Assigned again (e.g. by override) to same agent; do nothing.
78+
return
79+
if self._isRunning:
80+
raise InvalidScenarioError(
81+
f"tried to reuse behavior object {self} already assigned to {self._agent}"
82+
)
83+
self._start(agent)
84+
85+
def _start(self, agent):
86+
super()._start()
87+
self._agent = agent
88+
self._runningIterator = self.makeGenerator(agent, *self._args, **self._kwargs)
89+
self._checkAllPreconditions()
90+
91+
def _step(self):
92+
import scenic.syntax.veneer as veneer
93+
94+
super()._step()
95+
assert self._runningIterator
96+
97+
def alarmHandler(signum, frame):
98+
if sys.gettrace():
99+
return # skip the warning if we're in the debugger
100+
warnings.warn(
101+
f"the behavior {self} is taking a long time to take an action; "
102+
"maybe you have an infinite loop with no take/wait statements?",
103+
StuckBehaviorWarning,
104+
)
105+
106+
timeout = dynamics.stuckBehaviorWarningTimeout
107+
with veneer.executeInBehavior(self), alarm(timeout, alarmHandler):
108+
try:
109+
actions = self._runningIterator.send(None)
110+
except StopIteration:
111+
actions = () # behavior ended early
112+
return actions
113+
114+
def _stop(self, reason=None):
115+
super()._stop(reason)
116+
self._agent = None
117+
self._runningIterator = None
118+
119+
@property
120+
def _isFinished(self):
121+
return self._runningIterator is None
122+
123+
def _invokeInner(self, agent, subs):
124+
import scenic.syntax.veneer as veneer
125+
126+
assert len(subs) == 1
127+
sub = subs[0]
128+
if not isinstance(sub, Behavior):
129+
raise TypeError(f"expected a behavior, got {sub}")
130+
sub._start(agent)
131+
with veneer.executeInBehavior(sub):
132+
try:
133+
yield from sub._runningIterator
134+
finally:
135+
if sub._isRunning:
136+
sub._stop()
137+
138+
def __repr__(self):
139+
items = itertools.chain(
140+
(repr(arg) for arg in self._args),
141+
(f"{key}={repr(val)}" for key, val in self._kwargs.items()),
142+
)
143+
allArgs = ", ".join(items)
144+
return f"{self.__class__.__name__}({allArgs})"
145+
146+
147+
class Monitor(Behavior):
148+
"""Monitors for dynamic simulations.
149+
150+
Monitor statements are translated into definitions of subclasses of this class.
151+
"""
152+
153+
_noActionsMsg = 'does not take any actions (perhaps you forgot to use "wait"?)'
154+
155+
def _start(self):
156+
return super()._start(None)

src/scenic/core/dynamics/guards.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
"""Preconditions and invariants of behaviors and scenarios."""
2+
3+
4+
class GuardViolation(Exception):
5+
"""Abstract exception raised when a guard of a behavior is violated.
6+
7+
This will never be raised directly; either of the subclasses `PreconditionViolation`
8+
or `InvariantViolation` will be used, as appropriate.
9+
"""
10+
11+
violationType = "guard"
12+
13+
def __init__(self, behavior, lineno):
14+
self.behaviorName = behavior.__class__.__name__
15+
self.lineno = lineno
16+
17+
def __str__(self):
18+
return (
19+
f"violated {self.violationType} of {self.behaviorName} on line {self.lineno}"
20+
)
21+
22+
23+
class PreconditionViolation(GuardViolation):
24+
"""Exception raised when a precondition is violated.
25+
26+
Raised when a precondition is violated when invoking a behavior
27+
or when a precondition encounters a `RejectionException`, so that
28+
rejections count as precondition violations.
29+
"""
30+
31+
violationType = "precondition"
32+
33+
34+
class InvariantViolation(GuardViolation):
35+
"""Exception raised when an invariant is violated.
36+
37+
Raised when an invariant is violated when invoking/resuming a behavior
38+
or when an invariant encounters a `RejectionException`, so that
39+
rejections count as invariant violations.
40+
"""
41+
42+
violationType = "invariant"

0 commit comments

Comments
 (0)