def RelativeTo(X, Y): """The 'X relative to Y' polymorphic operator. Allowed forms: F relative to G (with at least one a field, the other a field or heading) <vector> relative to <oriented point> (and vice versa) <vector> relative to <vector> <heading> relative to <heading> """ xf, yf = isA(X, VectorField), isA(Y, VectorField) if xf or yf: if xf and yf and X.valueType != Y.valueType: raise RuntimeParseError('"X relative to Y" with X, Y fields of different types') fieldType = X.valueType if xf else Y.valueType error = '"X relative to Y" with field and value of different types' def helper(context): pos = context.position.toVector() xp = X[pos] if xf else toType(X, fieldType, error) yp = Y[pos] if yf else toType(Y, fieldType, error) return xp + yp return DelayedArgument({'position'}, helper) else: if isinstance(X, OrientedPoint): # TODO too strict? if isinstance(Y, OrientedPoint): raise RuntimeParseError('"X relative to Y" with X, Y both oriented points') Y = toVector(Y, '"X relative to Y" with X an oriented point but Y not a vector') return X.relativize(Y) elif isinstance(Y, OrientedPoint): X = toVector(X, '"X relative to Y" with Y an oriented point but X not a vector') return Y.relativize(X) else: X = toTypes(X, (Vector, float), '"X relative to Y" with X neither a vector nor scalar') Y = toTypes(Y, (Vector, float), '"X relative to Y" with Y neither a vector nor scalar') return evaluateRequiringEqualTypes(lambda: X + Y, X, Y, '"X relative to Y" with vector and scalar')
def __init__(self, opts): if isinstance(opts, dict): options, weights = [], [] for opt, prob in opts.items(): if not isinstance(prob, (float, int)): raise RuntimeParseError( f'discrete distribution weight {prob}' ' is not a constant number') if prob < 0: raise RuntimeParseError( f'discrete distribution weight {prob} is negative') if prob == 0: continue options.append(opt) weights.append(prob) self.optWeights = dict(zip(options, weights)) else: weights = None options = tuple(opts) self.optWeights = None if len(options) == 0: raise RuntimeParseError( 'tried to make discrete distribution over empty domain!') index = self.makeSelector(len(options) - 1, weights) super().__init__(index, options)
def makeRequirement(ty, reqID, req, line): if evaluatingRequirement: raise RuntimeParseError(f'tried to use "{ty.value}" inside a requirement') elif currentBehavior is not None: raise RuntimeParseError(f'"{ty.value}" inside a behavior on line {line}') elif currentSimulation is not None: currentScenario._addDynamicRequirement(ty, req, line) else: # requirement being defined at compile time currentScenario._addRequirement(ty, reqID, req, line, 1)
def mutate(*objects): # TODO update syntax """Function implementing the mutate statement.""" if evaluatingRequirement: raise RuntimeParseError('used mutate statement inside a requirement') if len(objects) == 0: objects = currentScenario._objects for obj in objects: if not isinstance(obj, Object): raise RuntimeParseError('"mutate X" with X not an object') obj.mutationEnabled = True
def param(*quotedParams, **params): """Function implementing the param statement.""" if evaluatingRequirement: raise RuntimeParseError('tried to create a global parameter inside a requirement') elif currentSimulation is not None: raise RuntimeParseError('tried to create a global parameter during a simulation') for name, value in params.items(): if name not in lockedParameters: _globalParameters[name] = toDistribution(value) assert len(quotedParams) % 2 == 0, quotedParams it = iter(quotedParams) for name, value in zip(it, it): if name not in lockedParameters: _globalParameters[name] = toDistribution(value)
def registerObject(obj): """Add a Scenic object to the global list of created objects. This is called by the Object constructor. """ if evaluatingRequirement: raise RuntimeParseError('tried to create an object inside a requirement') elif currentBehavior is not None: raise RuntimeParseError('tried to create an object inside a behavior') elif activity > 0 or currentScenario: assert not evaluatingRequirement assert isinstance(obj, _Constructible) currentScenario._registerObject(obj) if currentSimulation: currentSimulation.createObject(obj)
def _invokeSubBehavior(self, agent, subs, modifier=None): if modifier: if modifier.name == 'for': # do X for Y [seconds | steps] timeLimit = modifier.value if not isinstance(timeLimit, (float, int)): raise RuntimeParseError('"do X for Y" with Y not a number') assert modifier.terminator in (None, 'seconds', 'steps') if modifier.terminator != 'steps': timeLimit /= veneer.currentSimulation.timestep startTime = veneer.currentSimulation.currentTime condition = lambda: veneer.currentSimulation.currentTime - startTime >= timeLimit elif modifier.name == 'until': # do X until Y condition = modifier.value else: raise RuntimeError( f'internal parsing error: impossible modifier {modifier}') def body(behavior, agent): yield from self._invokeInner(agent, subs) handler = lambda behavior, agent: BlockConclusion.ABORT yield from runTryInterrupt(self, agent, body, [condition], [handler]) else: yield from self._invokeInner(agent, subs)
def _start(self): assert self._prepared # Compute time limit now that we know the simulation timestep self._elapsedTime = 0 self._timeLimitInSteps = self._timeLimit if self._timeLimitIsInSeconds: self._timeLimitInSteps /= veneer.currentSimulation.timestep veneer.startScenario(self) with veneer.executeInScenario(self): # Start compose block if self._compose is not None: if not inspect.isgeneratorfunction(self._compose): from scenic.syntax.translator import composeBlock raise RuntimeParseError( f'"{composeBlock}" does not invoke any scenarios') self._runningIterator = self._compose() # Initialize behavior coroutines of agents for agent in self._agents: assert isinstance(agent.behavior, Behavior), agent.behavior agent.behavior.start(agent) # Initialize monitor coroutines for monitor in self._monitors: monitor.start()
def wrapStarredValue(value, lineno): if isinstance(value, TupleDistribution) or not needsSampling(value): return value elif isinstance(value, Distribution): return [StarredDistribution(value, lineno)] else: raise RuntimeParseError(f'iterable unpacking cannot be applied to {value}')
def model(namespace, modelName): global loadingModel if loadingModel: raise RuntimeParseError( f'Scenic world model itself uses the "model" statement') if lockedModel is not None: modelName = lockedModel try: loadingModel = True module = importlib.import_module(modelName) except ModuleNotFoundError as e: if e.name == modelName: raise InvalidScenarioError( f'could not import world model {modelName}') from None else: raise finally: loadingModel = False names = module.__dict__.get('__all__', None) if names is not None: for name in names: namespace[name] = getattr(module, name) else: for name, value in module.__dict__.items(): if not name.startswith('_'): namespace[name] = value
def require(reqID, req, line, prob=1): """Function implementing the require statement.""" if evaluatingRequirement: raise RuntimeParseError('tried to create a requirement inside a requirement') if currentSimulation is not None: # requirement being evaluated at runtime if prob >= 1 or random.random() <= prob: result = req() assert not needsSampling(result) if needsLazyEvaluation(result): raise RuntimeParseError(f'requirement on line {line} uses value' ' undefined outside of object definition') if not result: raise RejectSimulationException(f'requirement on line {line}') else: # requirement being defined at compile time currentScenario._addRequirement(requirements.RequirementType.require, reqID, req, line, prob)
def coerceToAny(thing, types, error): """Coerce something into any of the given types, printing an error if impossible.""" for ty in types: if canCoerce(thing, ty): return coerce(thing, ty, error) from scenic.syntax.veneer import verbosePrint verbosePrint(f'Failed to coerce {thing} of type {underlyingType(thing)} to {types}', file=sys.stderr) raise RuntimeParseError(error)
def ego(obj=None): """Function implementing loads and stores to the 'ego' pseudo-variable. The translator calls this with no arguments for loads, and with the source value for stores. """ egoObject = currentScenario._ego if obj is None: if egoObject is None: raise RuntimeParseError('referred to ego object not yet assigned') elif not isinstance(obj, Object): raise RuntimeParseError('tried to make non-object the ego object') else: currentScenario._ego = obj for scenario in runningScenarios: if scenario._ego is None: scenario._ego = obj return egoObject
def dfs(spec): if spec in done: return elif spec in seen: raise RuntimeParseError( f'specifier for property {spec.property} ' 'depends on itself') seen.add(spec) for dep in spec.requiredProperties: child = properties.get(dep) if child is None: raise RuntimeParseError( f'property {dep} required by ' f'specifier {spec} is not specified') else: dfs(child) order.append(spec) done.add(spec)
def Follow(F, X, D): """The 'follow <field> from <vector> for <number>' operator.""" if not isinstance(F, VectorField): raise RuntimeParseError('"follow F from X for D" with F not a vector field') X = toVector(X, '"follow F from X for D" with X not a vector') D = toScalar(D, '"follow F from X for D" with D not a number') pos = F.followFrom(X, D) heading = F[pos] return OrientedPoint(position=pos, heading=heading)
def __init__(self, *args, **kwargs): self.args = tuple(toDistribution(arg) for arg in args) self.kwargs = { name: toDistribution(arg) for name, arg in kwargs.items() } if not inspect.isgeneratorfunction(self.makeGenerator): raise RuntimeParseError( f'{self} does not take any actions' ' (perhaps you forgot to use "take" or "do"?)') # Validate arguments to the behavior sig = inspect.signature(self.makeGenerator) try: sig.bind(None, *args, **kwargs) except TypeError as e: raise RuntimeParseError(str(e)) from e Samplable.__init__(self, itertools.chain(self.args, self.kwargs.values())) Invocable.__init__(self)
def _invokeInner(self, agent, subs): assert len(subs) == 1 sub = subs[0] if not isinstance(sub, Behavior): raise RuntimeParseError(f'expected a behavior, got {sub}') sub.start(agent) with veneer.executeInBehavior(sub): try: yield from sub._runningIterator finally: sub.stop()
def VisibleFrom(base): """The 'visible from <Point>' specifier. Specifies 'position', with no dependencies. This uses the given object's 'visibleRegion' property, and so correctly handles the view regions of Points, OrientedPoints, and Objects. """ if not isinstance(base, Point): raise RuntimeParseError('specifier "visible from O" with O not a Point') return Specifier('position', Region.uniformPointIn(base.visibleRegion))
def __init__(self, prop, value, deps=None, optionals={}, internal=False): self.property = prop self.value = toDelayedArgument(value, internal) if deps is None: deps = set() deps |= requiredProperties(value) if prop in deps: raise RuntimeParseError( f'specifier for property {prop} depends on itself') self.requiredProperties = deps self.optionals = optionals
def ApparentHeading(X, Y=None): """The 'apparent heading of <oriented point> [from <vector>]' operator. If the 'from <vector>' is omitted, the position of ego is used. """ if not isinstance(X, OrientedPoint): raise RuntimeParseError('"apparent heading of X from Y" with X not an OrientedPoint') if Y is None: Y = ego() Y = toVector(Y, '"relative heading of X from Y" with Y not a vector') return apparentHeadingAtPoint(X.position, X.heading, Y)
def evaluateRequiringEqualTypes(func, thingA, thingB, typeError='type mismatch'): """Evaluate the func, assuming thingA and thingB have the same type. If func produces a lazy value, it should not have any required properties beyond those of thingA and thingB.""" if not needsLazyEvaluation(thingA) and not needsLazyEvaluation(thingB): if underlyingType(thingA) is not underlyingType(thingB): raise RuntimeParseError(typeError) return func() else: # cannot check the types now; create proxy object to check types after evaluation return TypeEqualityChecker(func, thingA, thingB, typeError)
def sampleGiven(self, value): val = value[self.dist] suffix = None if self.coercer: if canCoerceType(type(val), self.valueType): try: return self.coercer(val) except CoercionFailure as e: suffix = f' ({e.args[0]})' elif isinstance(val, self.valueType): return val if suffix is None: suffix = f' (expected {self.valueType.__name__}, got {type(val).__name__})' raise RuntimeParseError(self.errorMessage + suffix, self.loc)
def CanSee(X, Y): """The 'X can see Y' polymorphic operator. Allowed forms: <point> can see <object> <point> can see <vector> """ if not isinstance(X, Point): raise RuntimeParseError('"X can see Y" with X not a Point') if isinstance(Y, Point): return X.canSee(Y) else: Y = toVector(Y, '"X can see Y" with Y not a vector') return X.visibleRegion.containsPoint(Y)
def __init__(self, requiredProperties, attributes, value): self.requiredProperties = set(requiredProperties) self.value = value def enabled(thing, default): if thing in attributes: attributes.remove(thing) return True else: return default self.isAdditive = enabled('additive', False) self.isDynamic = enabled('dynamic', False) for attr in attributes: raise RuntimeParseError(f'unknown property attribute "{attr}"')
def leftSpecHelper(syntax, pos, dist, axis, toComponents, makeOffset): extras = set() if canCoerce(dist, float): dx, dy = toComponents(coerce(dist, float)) elif canCoerce(dist, Vector): dx, dy = coerce(dist, Vector) else: raise RuntimeParseError(f'"{syntax} X by D" with D not a number or vector') if isinstance(pos, OrientedPoint): # TODO too strict? val = lambda self: pos.relativize(makeOffset(self, dx, dy)) new = DelayedArgument({axis}, val) extras.add('heading') else: pos = toVector(pos, f'specifier "{syntax} X" with X not a vector') val = lambda self: pos.offsetRotated(self.heading, makeOffset(self, dx, dy)) new = DelayedArgument({axis, 'heading'}, val) return Specifier('position', new, optionals=extras)
def _invokeInner(self, agent, subs): for sub in subs: if not isinstance(sub, DynamicScenario): raise RuntimeParseError(f'expected a scenario, got {sub}') sub._prepare() sub._start() self._subScenarios = list(subs) while True: newSubs = [] for sub in self._subScenarios: terminationReason = sub._step() if isinstance(terminationReason, EndSimulationAction): yield terminationReason elif terminationReason is None: newSubs.append(sub) self._subScenarios = newSubs if not newSubs: return yield None
def Beyond(pos, offset, fromPt=None): """The 'beyond X by Y [from Z]' polymorphic specifier. Specifies 'position', with no dependencies. Allowed forms: beyond <vector> by <number> [from <vector>] beyond <vector> by <vector> [from <vector>] If the 'from <vector>' is omitted, the position of ego is used. """ pos = toVector(pos, 'specifier "beyond X by Y" with X not a vector') dType = underlyingType(offset) if dType is float or dType is int: offset = Vector(0, offset) elif dType is not Vector: raise RuntimeParseError('specifier "beyond X by Y" with Y not a number or vector') if fromPt is None: fromPt = ego() fromPt = toVector(fromPt, 'specifier "beyond X by Y from Z" with Z not a vector') lineOfSight = fromPt.angleTo(pos) return Specifier('position', pos.offsetRotated(lineOfSight, offset))
def Following(field, dist, fromPt=None): """The 'following F [from X] for D' specifier. Specifies 'position', and optionally 'heading', with no dependencies. Allowed forms: following <field> [from <vector>] for <number> If the 'from <vector>' is omitted, the position of ego is used. """ if fromPt is None: fromPt = ego() else: dist, fromPt = fromPt, dist if not isinstance(field, VectorField): raise RuntimeParseError('"following F" specifier with F not a vector field') fromPt = toVector(fromPt, '"following F from X for D" with X not a vector') dist = toScalar(dist, '"following F for D" with D not a number') pos = field.followFrom(fromPt, dist) heading = field[pos] val = OrientedVector.make(pos, heading) return Specifier('position', val, optionals={'heading'})
def coerce(thing, ty, error='wrong type'): """Coerce something into the given type.""" assert canCoerce(thing, ty), (thing, ty) import scenic.syntax.veneer as veneer # TODO improve? realType = ty if ty is float: coercer = coerceToFloat elif ty is Heading: coercer = coerceToHeading ty = numbers.Real realType = float elif ty is Vector: coercer = coerceToVector elif ty is veneer.Behavior: coercer = coerceToBehavior else: coercer = None if isinstance(thing, Distribution): vt = thing.valueType if typing.get_origin(vt) is typing.Union: possibleTypes = typing.get_args(vt) else: possibleTypes = (vt, ) if all(issubclass(possible, ty) for possible in possibleTypes): return thing # no coercion necessary else: return TypecheckedDistribution(thing, realType, error, coercer=coercer) elif coercer: try: return coercer(thing) except CoercionFailure as e: raise RuntimeParseError(f'{error} ({e.args[0]})') from None else: return thing
def __init__(self, region=everywhere): if needsSampling(region): raise RuntimeParseError('workspace region must be fixed') super().__init__('workspace', orientation=region.orientation) self.region = region