def constructScenarioFrom(namespace): """Build a Scenario object from an executed Scenic module.""" # Extract ego object if namespace['_egoObject'] is None: raise InvalidScenarioError('did not specify ego object') # 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 # Create Scenario object scenario = Scenario(workspace, namespace['_objects'], namespace['_egoObject'], namespace['_params'], namespace['_externalParams'], namespace['_requirements'], namespace['_requirementDeps']) # Prune infeasible parts of the space if usePruning: pruning.prune(scenario, verbosity=verbosity) return scenario
def validate(self): """Make some simple static checks for inconsistent built-in requirements.""" objects = self.objects staticVisibility = not needsSampling(self.egoObject.visibleRegion) staticBounds = [self.hasStaticBounds(obj) for obj in objects] for i in range(len(objects)): oi = objects[i] # skip objects with unknown positions or bounding boxes if not staticBounds[i]: continue # Require object to be contained in the workspace/valid region container = self.containerOfObject(oi) 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 evaluator(): result = req() assert not needsSampling(result) if needsLazyEvaluation(result): raise InvalidScenarioError(f'requirement on line {line} uses value' ' undefined outside of object definition') return result
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 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.items(): 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.items(): 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 storeScenarioStateIn(namespace, requirementSyntax, lineMap, filename): """Post-process an executed Scenic module, extracting state from the veneer.""" # Extract created Objects namespace['_objects'] = tuple(veneer.allObjects) namespace['_egoObject'] = veneer.egoObject # Extract global parameters namespace['_params'] = veneer.globalParameters for name, value in veneer.globalParameters.items(): if needsLazyEvaluation(value): raise InvalidScenarioError( f'parameter {name} uses value {value}' ' undefined outside of object definition') # Extract external parameters namespace['_externalParams'] = tuple(veneer.externalParameters) # Extract requirements, scan for relations used for pruning, and create closures requirements = veneer.pendingRequirements finalReqs = veneer.inheritedReqs requirementDeps = set( ) # things needing to be sampled to evaluate the requirements namespace['_requirements'] = finalReqs namespace['_requirementDeps'] = requirementDeps def makeClosure(req, bindings, ego, line): """Create a closure testing the requirement in the correct runtime state.""" def evaluator(): result = req() assert not needsSampling(result) if needsLazyEvaluation(result): raise InvalidScenarioError( f'requirement on line {line} uses value' ' undefined outside of object definition') return result def closure(values): # 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 if ego is not None: veneer.egoObject = values[ego] # evaluate requirement condition, reporting errors on the correct line try: veneer.evaluatingRequirement = True result = executePythonFunction(evaluator, lineMap, filename) finally: veneer.evaluatingRequirement = False return result return closure for reqID, (req, bindings, ego, line, prob) in requirements.items(): # Check whether requirement implies any relations used for pruning reqNode = requirementSyntax[reqID] relations.inferRelationsFrom(reqNode, bindings, ego, line, lineMap) # Gather dependencies of the requirement for value in bindings.values(): if needsSampling(value): requirementDeps.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) requirementDeps.add(ego) # Construct closure finalReqs.append((makeClosure(req, bindings, ego, line), prob))