-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #187 from BerkeleyLearnVerify/dynamics-refactor
Refactor dynamics.py; fix message for rejected simulations
- Loading branch information
Showing
13 changed files
with
599 additions
and
550 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
"""Support for dynamic behaviors and modular scenarios. | ||
A few classes are exposed here for external use, including: | ||
* `Action`; | ||
* `GuardViolation`, `InvariantViolation`, and `PreconditionViolation`; | ||
* `StuckBehaviorWarning`. | ||
Everything else defined in the submodules is an implementation detail and | ||
should not be used outside of Scenic (it may change at any time). | ||
""" | ||
|
||
from .actions import Action | ||
from .guards import GuardViolation, InvariantViolation, PreconditionViolation | ||
from .utils import RejectSimulationException, StuckBehaviorWarning | ||
|
||
#: Timeout in seconds after which a `StuckBehaviorWarning` will be raised. | ||
stuckBehaviorWarningTimeout = 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
"""Actions taken by dynamic agents.""" | ||
|
||
import abc | ||
|
||
|
||
class Action(abc.ABC): | ||
"""An :term:`action` which can be taken by an agent for one step of a simulation.""" | ||
|
||
def canBeTakenBy(self, agent): | ||
"""Whether this action is allowed to be taken by the given agent. | ||
The default implementation always returns True. | ||
""" | ||
return True | ||
|
||
@abc.abstractmethod | ||
def applyTo(self, agent, simulation): | ||
"""Apply this action to the given agent in the given simulation. | ||
This method should call simulator APIs so that the agent will take this action | ||
during the next simulated time step. Depending on the simulator API, it may be | ||
necessary to batch each agent's actions into a single update: in that case you | ||
can have this method set some state on the agent, then apply the actual update | ||
in an overridden implementation of `Simulation.executeActions`. For examples, | ||
see the CARLA interface: `scenic.simulators.carla.actions` has some CARLA-specific | ||
actions which directly call CARLA APIs, while the generic steering and braking | ||
actions from `scenic.domains.driving.actions` are implemented using the batching | ||
approach (see for example the ``setThrottle`` method of the class | ||
`scenic.simulators.carla.model.Vehicle`, which sets state later read by | ||
``CarlaSimulation.executeActions`` in `scenic.simulators.carla.simulator`). | ||
""" | ||
raise NotImplementedError | ||
|
||
|
||
class _EndSimulationAction(Action): | ||
"""Special action indicating it is time to end the simulation. | ||
Only for internal use. | ||
""" | ||
|
||
def __init__(self, line): | ||
self.line = line | ||
|
||
def __str__(self): | ||
return f'"terminate simulation" executed on line {self.line}' | ||
|
||
def applyTo(self, agent, simulation): | ||
assert False | ||
|
||
|
||
class _EndScenarioAction(Action): | ||
"""Special action indicating it is time to end the current scenario. | ||
Only for internal use. | ||
""" | ||
|
||
def __init__(self, scenario, line): | ||
self.scenario = scenario | ||
self.line = line | ||
|
||
def __str__(self): | ||
return f'"terminate" executed on line {self.line}' | ||
|
||
def applyTo(self, agent, simulation): | ||
assert False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
"""Behaviors and monitors.""" | ||
|
||
import functools | ||
import inspect | ||
import itertools | ||
import sys | ||
import warnings | ||
|
||
from scenic.core.distributions import Samplable, toDistribution | ||
import scenic.core.dynamics as dynamics | ||
from scenic.core.errors import InvalidScenarioError | ||
from scenic.core.type_support import CoercionFailure | ||
from scenic.core.utils import alarm | ||
|
||
from .invocables import Invocable | ||
from .utils import StuckBehaviorWarning | ||
|
||
|
||
class Behavior(Invocable, Samplable): | ||
"""Dynamic behaviors of agents. | ||
Behavior statements are translated into definitions of subclasses of this class. | ||
""" | ||
|
||
_noActionsMsg = ( | ||
'does not take any actions (perhaps you forgot to use "take" or "do"?)' | ||
) | ||
|
||
def __init_subclass__(cls): | ||
if "__signature__" in cls.__dict__: | ||
# We're unpickling a behavior; skip this step. | ||
return | ||
|
||
if cls.__module__ is not __name__: | ||
import scenic.syntax.veneer as veneer | ||
|
||
if veneer.currentScenario: | ||
veneer.currentScenario._behaviors.append(cls) | ||
|
||
target = cls.makeGenerator | ||
target = functools.partial(target, 0, 0) # account for Scenic-inserted args | ||
cls.__signature__ = inspect.signature(target) | ||
|
||
def __init__(self, *args, **kwargs): | ||
args = tuple(toDistribution(arg) for arg in args) | ||
kwargs = {name: toDistribution(arg) for name, arg in kwargs.items()} | ||
|
||
# Validate arguments to the behavior | ||
sig = inspect.signature(self.makeGenerator) | ||
sig.bind(None, *args, **kwargs) # raises TypeError on incompatible arguments | ||
Samplable.__init__(self, itertools.chain(args, kwargs.values())) | ||
Invocable.__init__(self, *args, **kwargs) | ||
|
||
if not inspect.isgeneratorfunction(self.makeGenerator): | ||
raise InvalidScenarioError(f"{self} {self._noActionsMsg}") | ||
|
||
@classmethod | ||
def _canCoerceType(cls, ty): | ||
return issubclass(ty, cls) or ty in (type, type(None)) | ||
|
||
@classmethod | ||
def _coerce(cls, thing): | ||
if thing is None or isinstance(thing, cls): | ||
return thing | ||
elif issubclass(thing, cls): | ||
return thing() | ||
else: | ||
raise CoercionFailure(f"expected type of behavior, got {thing}") | ||
|
||
def sampleGiven(self, value): | ||
args = (value[arg] for arg in self._args) | ||
kwargs = {name: value[val] for name, val in self._kwargs.items()} | ||
return type(self)(*args, **kwargs) | ||
|
||
def _assignTo(self, agent): | ||
if self._agent and agent is self._agent._dynamicProxy: | ||
# Assigned again (e.g. by override) to same agent; do nothing. | ||
return | ||
if self._isRunning: | ||
raise InvalidScenarioError( | ||
f"tried to reuse behavior object {self} already assigned to {self._agent}" | ||
) | ||
self._start(agent) | ||
|
||
def _start(self, agent): | ||
super()._start() | ||
self._agent = agent | ||
self._runningIterator = self.makeGenerator(agent, *self._args, **self._kwargs) | ||
self._checkAllPreconditions() | ||
|
||
def _step(self): | ||
import scenic.syntax.veneer as veneer | ||
|
||
super()._step() | ||
assert self._runningIterator | ||
|
||
def alarmHandler(signum, frame): | ||
if sys.gettrace(): | ||
return # skip the warning if we're in the debugger | ||
warnings.warn( | ||
f"the behavior {self} is taking a long time to take an action; " | ||
"maybe you have an infinite loop with no take/wait statements?", | ||
StuckBehaviorWarning, | ||
) | ||
|
||
timeout = dynamics.stuckBehaviorWarningTimeout | ||
with veneer.executeInBehavior(self), alarm(timeout, alarmHandler): | ||
try: | ||
actions = self._runningIterator.send(None) | ||
except StopIteration: | ||
actions = () # behavior ended early | ||
return actions | ||
|
||
def _stop(self, reason=None): | ||
super()._stop(reason) | ||
self._agent = None | ||
self._runningIterator = None | ||
|
||
@property | ||
def _isFinished(self): | ||
return self._runningIterator is None | ||
|
||
def _invokeInner(self, agent, subs): | ||
import scenic.syntax.veneer as veneer | ||
|
||
assert len(subs) == 1 | ||
sub = subs[0] | ||
if not isinstance(sub, Behavior): | ||
raise TypeError(f"expected a behavior, got {sub}") | ||
sub._start(agent) | ||
with veneer.executeInBehavior(sub): | ||
try: | ||
yield from sub._runningIterator | ||
finally: | ||
if sub._isRunning: | ||
sub._stop() | ||
|
||
def __repr__(self): | ||
items = itertools.chain( | ||
(repr(arg) for arg in self._args), | ||
(f"{key}={repr(val)}" for key, val in self._kwargs.items()), | ||
) | ||
allArgs = ", ".join(items) | ||
return f"{self.__class__.__name__}({allArgs})" | ||
|
||
|
||
class Monitor(Behavior): | ||
"""Monitors for dynamic simulations. | ||
Monitor statements are translated into definitions of subclasses of this class. | ||
""" | ||
|
||
_noActionsMsg = 'does not take any actions (perhaps you forgot to use "wait"?)' | ||
|
||
def _start(self): | ||
return super()._start(None) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
"""Preconditions and invariants of behaviors and scenarios.""" | ||
|
||
|
||
class GuardViolation(Exception): | ||
"""Abstract exception raised when a guard of a behavior is violated. | ||
This will never be raised directly; either of the subclasses `PreconditionViolation` | ||
or `InvariantViolation` will be used, as appropriate. | ||
""" | ||
|
||
violationType = "guard" | ||
|
||
def __init__(self, behavior, lineno): | ||
self.behaviorName = behavior.__class__.__name__ | ||
self.lineno = lineno | ||
|
||
def __str__(self): | ||
return ( | ||
f"violated {self.violationType} of {self.behaviorName} on line {self.lineno}" | ||
) | ||
|
||
|
||
class PreconditionViolation(GuardViolation): | ||
"""Exception raised when a precondition is violated. | ||
Raised when a precondition is violated when invoking a behavior | ||
or when a precondition encounters a `RejectionException`, so that | ||
rejections count as precondition violations. | ||
""" | ||
|
||
violationType = "precondition" | ||
|
||
|
||
class InvariantViolation(GuardViolation): | ||
"""Exception raised when an invariant is violated. | ||
Raised when an invariant is violated when invoking/resuming a behavior | ||
or when an invariant encounters a `RejectionException`, so that | ||
rejections count as invariant violations. | ||
""" | ||
|
||
violationType = "invariant" |
Oops, something went wrong.