def validate(self): """Make some simple static checks for inconsistent built-in requirements. :meta private: """ objects = self.objects staticVisibility = self.egoObject and not needsSampling(self.egoObject.visibleRegion) staticBounds = [self.hasStaticBounds(obj) for obj in objects] for i in range(len(objects)): oi = objects[i] container = self.containerOfObject(oi) # Trivial case where container is empty if isinstance(container, EmptyRegion): raise InvalidScenarioError(f'Container region of {oi} is empty') # skip objects with unknown positions or bounding boxes if not staticBounds[i]: continue # Require object to be contained in the workspace/valid region if not needsSampling(container) and not container.containsObject(oi): raise InvalidScenarioError(f'Object at {oi.position} does not fit in container') # Require object to be visible from the ego object if staticVisibility and oi.requireVisible is True and oi is not self.egoObject: if not self.egoObject.canSee(oi): raise InvalidScenarioError(f'Object at {oi.position} is not visible from ego') # Require object to not intersect another object for j in range(i): oj = objects[j] if not staticBounds[j]: continue if oi.intersects(oj): raise InvalidScenarioError(f'Object at {oi.position} intersects' f' object at {oj.position}')
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 forParameters(params, globalParams): """Create an `ExternalSampler` given the sets of external and global parameters. The scenario may explicitly select an external sampler by assigning the global parameter ``externalSampler`` to a subclass of `ExternalSampler`. Otherwise, a `VerifaiSampler` is used by default. Args: params (tuple): Tuple listing each `ExternalParameter`. globalParams (dict): Dictionary of global parameters for the `Scenario`. Note that the values of these parameters may be instances of `Distribution`! Returns: An `ExternalSampler` configured for the given parameters. """ if len(params) > 0: externalSampler = globalParams.get('externalSampler', VerifaiSampler) if not issubclass(externalSampler, ExternalSampler): raise InvalidScenarioError( f'externalSampler type {externalSampler}' ' not subclass of ExternalSampler') return externalSampler(params, globalParams) else: return None
def pruneContainment(scenario, verbosity): """Prune based on the requirement that individual Objects fit within their container. Specifically, if O is positioned uniformly in region B and has container C, then we can instead pick a position uniformly in their intersection. If we can also lower bound the radius of O, then we can first erode C by that distance. """ for obj in scenario.objects: base = matchInRegion(obj.position) if base is None: # match objects positioned uniformly in a Region continue if isinstance(base, regions.EmptyRegion): raise InvalidScenarioError(f'Object {obj} placed in empty region') basePoly = regions.toPolygon(base) if basePoly is None: # to prune, the Region must be polygonal continue if basePoly.is_empty: raise InvalidScenarioError(f'Object {obj} placed in empty region') container = scenario.containerOfObject(obj) containerPoly = regions.toPolygon(container) if containerPoly is None: # the object's container must also be polygonal return None minRadius, _ = supportInterval(obj.inradius) if minRadius is not None: # if we can lower bound the radius, erode the container containerPoly = containerPoly.buffer(-minRadius) elif base is container: continue newBasePoly = basePoly & containerPoly # restrict the base Region to the container if newBasePoly.is_empty: raise InvalidScenarioError( f'Object {obj} does not fit in container') if verbosity >= 1: if basePoly.area > 0: ratio = newBasePoly.area / basePoly.area else: ratio = newBasePoly.length / basePoly.length percent = 100 * (1.0 - ratio) print( f' Region containment constraint pruned {percent:.1f}% of space.' ) newBase = regions.regionFromShapelyObject(newBasePoly, orientation=base.orientation) newPos = regions.Region.uniformPointIn(newBase) obj.position.conditionTo(newPos)
def _toScenario(self, namespace): assert self._prepared if self._ego is None and self._compose is None: msg = 'did not specify ego object' modScenarios = namespace['_scenarios'] if self._dummyNamespace and len(modScenarios) == 1: if modScenarios[0]._requiresArguments(): msg += ( '\n(Note: this Scenic file contains a modular scenario, but it\n' 'cannot be used as the top-level scenario since it requires\n' 'arguments; so the whole file is being used as a top-level\n' 'scenario and needs an ego object.)') else: msg += ( '\n(Note: this Scenic file contains a modular scenario, but also\n' 'other code; so the whole file is being used as a top-level\n' 'scenario and needs an ego object.)') raise InvalidScenarioError(msg) # Extract workspace, if one is specified if 'workspace' in namespace: workspace = namespace['workspace'] if not isinstance(workspace, Workspace): raise InvalidScenarioError( f'workspace {workspace} is not a Workspace') if needsSampling(workspace): raise InvalidScenarioError('workspace must be a fixed region') if needsLazyEvaluation(workspace): raise InvalidScenarioError('workspace uses value undefined ' 'outside of object definition') else: workspace = None from scenic.core.scenarios import Scenario scenario = Scenario(workspace, self._simulatorFactory, self._objects, self._ego, self._globalParameters, self._externalParameters, self._requirements, self._requirementDeps, self._monitors, self._behaviorNamespaces, self) # TODO unify these! return scenario
def model(namespace, modelName): if lockedModel is not None: modelName = lockedModel try: module = importlib.import_module(modelName) except ModuleNotFoundError: raise InvalidScenarioError(f'could not import world model {modelName}') from None 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 compile(self, namespace, scenario, syntax=None): """Create a closure testing the requirement in the correct runtime state. While we're at it, determine whether the requirement implies any relations we can use for pruning, and gather all of its dependencies. """ bindings, ego, line = self.bindings, self.egoObject, self.line condition = self.condition # Check whether requirement implies any relations used for pruning if self.ty.constrainsSampling and syntax: relations.inferRelationsFrom(syntax, bindings, ego, line) # Gather dependencies of the requirement deps = set() for value in bindings.values(): if needsSampling(value): deps.add(value) if needsLazyEvaluation(value): raise InvalidScenarioError( f'requirement on line {line} uses value {value}' ' undefined outside of object definition') if ego is not None: assert isinstance(ego, Samplable) deps.add(ego) # Construct closure def closure(values): global evaluatingRequirement, currentScenario # rebind any names referring to sampled objects for name, value in bindings.items(): if value in values: namespace[name] = values[value] # rebind ego object, which can be referred to implicitly boundEgo = None if ego is None else values[ego] # evaluate requirement condition, reporting errors on the correct line import scenic.syntax.veneer as veneer with veneer.executeInRequirement(scenario, boundEgo): result = condition() assert not needsSampling(result) if needsLazyEvaluation(result): raise InvalidScenarioError( f'requirement on line {line} uses value' ' undefined outside of object definition') return result return CompiledRequirement(self, closure, deps)
def closure(values): global evaluatingRequirement, currentScenario # rebind any names referring to sampled objects for name, value in bindings.items(): if value in values: namespace[name] = values[value] # rebind ego object, which can be referred to implicitly boundEgo = None if ego is None else values[ego] # evaluate requirement condition, reporting errors on the correct line import scenic.syntax.veneer as veneer with veneer.executeInRequirement(scenario, boundEgo): result = condition() assert not needsSampling(result) if needsLazyEvaluation(result): raise InvalidScenarioError( f'requirement on line {line} uses value' ' undefined outside of object definition') return result
def inferDistanceRelations(matcher, reqNode, ego, line): """Infer bounds on distances from a requirement.""" distMatcher = lambda node: matcher.matchUnaryFunction('DistanceFrom', node) allBounds = matcher.matchBounds(reqNode, distMatcher) for target, bounds in allBounds: if not isinstance(target, Object): continue assert target is not ego if ego is None: raise InvalidScenarioError( 'distance w.r.t. unassigned ego on line {line}') lower, upper = bounds if lower < 0: lower = 0 if upper == float('inf'): continue # skip trivial bounds rel = DistanceRelation(target, lower, upper) ego._relations.append(rel) conv = DistanceRelation(ego, lower, upper) target._relations.append(conv)
def inferRelativeHeadingRelations(matcher, reqNode, ego, line): """Infer bounds on relative headings from a requirement.""" rhMatcher = lambda node: matcher.matchUnaryFunction( 'RelativeHeading', node) allBounds = matcher.matchBounds(reqNode, rhMatcher) for target, bounds in allBounds: if not isinstance(target, Object): continue assert target is not ego if ego is None: raise InvalidScenarioError( 'relative heading w.r.t. unassigned ego on line {line}') lower, upper = bounds if lower < -math.pi: lower = -math.pi if upper > math.pi: upper = math.pi if lower == -math.pi and upper == math.pi: continue # skip trivial bounds rel = RelativeHeadingRelation(target, lower, upper) ego._relations.append(rel) conv = RelativeHeadingRelation(ego, -upper, -lower) target._relations.append(conv)
def generate(self, maxIterations=2000, verbosity=0, feedback=None): """Sample a `Scene` from this scenario. Args: maxIterations (int): Maximum number of rejection sampling iterations. verbosity (int): Verbosity level. feedback (float): Feedback to pass to external samplers doing active sampling. See :mod:`scenic.core.external_params`. Returns: A pair with the sampled `Scene` and the number of iterations used. Raises: `RejectionException`: if no valid sample is found in **maxIterations** iterations. """ objects = self.objects # choose which custom requirements will be enforced for this sample activeReqs = [ req for req in self.initialRequirements if random.random() <= req.prob ] # do rejection sampling until requirements are satisfied rejection = True iterations = 0 while rejection is not None: if iterations > 0: # rejected the last sample if verbosity >= 2: print( f' Rejected sample {iterations} because of: {rejection}' ) if self.externalSampler is not None: feedback = self.externalSampler.rejectionFeedback if iterations >= maxIterations: raise RejectionException( f'failed to generate scenario in {iterations} iterations') iterations += 1 try: if self.externalSampler is not None: self.externalSampler.sample(feedback) sample = Samplable.sampleAll(self.dependencies) except RejectionException as e: rejection = e continue rejection = None ego = sample[self.egoObject] # Normalize types of some built-in properties for obj in objects: sampledObj = sample[obj] assert not needsSampling(sampledObj) # position, heading assert isinstance(sampledObj.position, Vector) sampledObj.heading = float(sampledObj.heading) # behavior behavior = sampledObj.behavior if behavior is not None and not isinstance( behavior, veneer.Behavior): raise InvalidScenarioError( f'behavior {behavior} of Object {obj} is not a behavior' ) # Check built-in requirements for i in range(len(objects)): vi = sample[objects[i]] # Require object to be contained in the workspace/valid region container = self.containerOfObject(vi) if not container.containsObject(vi): rejection = 'object containment' break # Require object to be visible from the ego object if vi.requireVisible and vi is not ego and not ego.canSee(vi): rejection = 'object visibility' break # Require object to not intersect another object for j in range(i): vj = sample[objects[j]] if vi.intersects(vj): rejection = 'object intersection' break if rejection is not None: break if rejection is not None: continue # Check user-specified requirements for req in activeReqs: if not req.satisfiedBy(sample): rejection = f'user-specified requirement (line {req.line})' break # obtained a valid sample; assemble a scene from it sampledObjects = tuple(sample[obj] for obj in objects) sampledParams = {} for param, value in self.params.items(): sampledValue = sample[value] assert not needsLazyEvaluation(sampledValue) sampledParams[param] = sampledValue sampledNamespaces = {} for modName, namespace in self.behaviorNamespaces.items(): sampledNamespace = { name: sample[value] for name, value in namespace.items() } sampledNamespaces[modName] = (namespace, sampledNamespace, namespace.copy()) alwaysReqs = (veneer.BoundRequirement(req, sample) for req in self.alwaysRequirements) terminationConds = (veneer.BoundRequirement(req, sample) for req in self.terminationConditions) termSimulationConds = (veneer.BoundRequirement(req, sample) for req in self.terminateSimulationConditions) scene = Scene(self.workspace, sampledObjects, ego, sampledParams, alwaysReqs, terminationConds, termSimulationConds, self.monitors, sampledNamespaces, self.dynamicScenario) return scene, iterations
def run(self, maxSteps): """Run the simulation. Throws a RejectSimulationException if a requirement is violated. """ trajectory = self.trajectory if self.currentTime > 0: raise RuntimeError('tried to run a Simulation which has already run') assert len(trajectory) == 1 actionSequence = [] import scenic.syntax.veneer as veneer veneer.beginSimulation(self) dynamicScenario = self.scene.dynamicScenario try: # Initialize dynamic scenario dynamicScenario._start() # Update all objects in case the simulator has adjusted any dynamic # properties during setup self.updateObjects() # Run simulation assert self.currentTime == 0 terminationReason = None while maxSteps is None or self.currentTime < maxSteps: if self.verbosity >= 3: print(f' Time step {self.currentTime}:') # Run compose blocks of compositional scenarios terminationReason = dynamicScenario._step() # Check if any requirements fail dynamicScenario._checkAlwaysRequirements() # Run monitors newReason = dynamicScenario._runMonitors() if newReason is not None: terminationReason = newReason # "Always" and scenario-level requirements have been checked; # now safe to terminate if the top-level scenario has finished # or a monitor requested termination if terminationReason is not None: break terminationReason = dynamicScenario._checkSimulationTerminationConditions() if terminationReason is not None: break # Compute the actions of the agents in this time step allActions = OrderedDict() schedule = self.scheduleForAgents() for agent in schedule: behavior = agent.behavior if not behavior._runningIterator: # TODO remove hack behavior.start(agent) actions = behavior.step() if isinstance(actions, EndSimulationAction): terminationReason = str(actions) break assert isinstance(actions, tuple) if len(actions) == 1 and isinstance(actions[0], (list, tuple)): actions = tuple(actions[0]) if not self.actionsAreCompatible(agent, actions): raise InvalidScenarioError(f'agent {agent} tried incompatible ' f' action(s) {actions}') allActions[agent] = actions if terminationReason is not None: break # Execute the actions if self.verbosity >= 3: for agent, actions in allActions.items(): print(f' Agent {agent} takes action(s) {actions}') self.executeActions(allActions) # Run the simulation for a single step and read its state back into Scenic self.step() self.updateObjects() self.currentTime += 1 # Save the new state trajectory.append(self.currentState()) actionSequence.append(allActions) if terminationReason is None: terminationReason = f'reached time limit ({maxSteps} steps)' result = SimulationResult(trajectory, actionSequence, terminationReason) return result finally: self.destroy() for obj in self.scene.objects: disableDynamicProxyFor(obj) for agent in self.agents: agent.behavior.stop() for monitor in self.scene.monitors: monitor.stop() veneer.endSimulation(self)
def _rejectSample(message): if veneer.isActive(): raise InvalidScenarioError(message) else: raise RejectionException(message)