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 __init__(self, center, radius, resolution=32): super().__init__('Circle', center, radius) self.center = center.toVector() self.radius = radius self.circumcircle = (self.center, self.radius) if not (needsSampling(self.center) or needsSampling(self.radius)): ctr = shapely.geometry.Point(self.center) self.polygon = ctr.buffer(self.radius, resolution=resolution)
def handler2(self, *args): if needsSampling(self): return VectorOperatorDistribution(op, self, args) elif any(needsSampling(arg) for arg in args): return VectorMethodDistribution(method, self, args, {}) elif any(needsLazyEvaluation(arg) for arg in args): # see analogous comment in distributionFunction return makeDelayedFunctionCall(handler2, args, {}) else: return method(self, *args)
def helper(*args): if needsSampling(instance): return VectorOperatorDistribution(op, instance, args) elif any(needsSampling(arg) for arg in args): return VectorMethodDistribution(method, instance, args, {}) elif any(needsLazyEvaluation(arg) for arg in args): # see analogous comment in distributionFunction return makeDelayedFunctionCall(helper, args, {}) else: return wrapped(*args)
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 domainForValue(value): """Return a Domain for this type of Scenic value, when possible""" ty = underlyingType(value) if ty is float or ty is int: domain = scalarDomain elif ty is GTACarModel: domain = gtaModelDomain elif ty is WebotsCarModel: domain = webotsModelDomain elif ty is Color: domain = colorDomain elif canCoerceType(ty, Vector): domain = vectorDomain elif ty is str: # We can only handle strings when they come from a finite set of # possibilities; we heuristically detect that here. if isinstance(value, Options): domain = Categorical(*value.options) else: domain = None # we can't ensure the domain is finite else: domain = None # no corresponding Domain known if not needsSampling(value): # We can handle constants of unknown types, but when possible we # convert the value to a VerifAI type. value = convertToVerifaiType(value, strict=False) return Constant(value) return domain
def __init__(self, points=None, polygon=None, orientation=None): super().__init__('Polygon', orientation=orientation) if polygon is None and points is None: raise RuntimeError('must specify points or polygon for PolygonalRegion') if polygon is None: points = tuple(points) if len(points) == 0: raise RuntimeError('tried to create PolygonalRegion from empty point list!') for point in points: if needsSampling(point): raise RuntimeError('only fixed PolygonalRegions are supported') self.points = points polygon = shapely.geometry.Polygon(points) if isinstance(polygon, shapely.geometry.Polygon): self.polygons = shapely.geometry.MultiPolygon([polygon]) elif isinstance(polygon, shapely.geometry.MultiPolygon): self.polygons = polygon else: raise RuntimeError(f'tried to create PolygonalRegion from non-polygon {polygon}') if not self.polygons.is_valid: raise RuntimeError('tried to create PolygonalRegion with ' f'invalid polygon {self.polygons}') if points is None and len(self.polygons) == 1 and len(self.polygons[0].interiors) == 0: self.points = tuple(self.polygons[0].exterior.coords[:-1]) triangles = [] for polygon in self.polygons: triangles.extend(triangulatePolygon(polygon)) self.trianglesAndBounds = tuple((tri, tri.bounds) for tri in triangles) areas = (triangle.area for triangle in triangles) self.cumulativeTriangleAreas = tuple(itertools.accumulate(areas))
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 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 constructScenarioFrom(namespace): # 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 scenario = Scenario(workspace, namespace['_objects'], namespace['_egoObject'], namespace['_params'], namespace['_requirements'], namespace['_requirementDeps']) if usePruning: pruning.prune(scenario, verbosity=verbosity) return scenario
def domainForObject(obj, ignoredProperties): """Construct a Domain for the given Scenic Object""" domains = {} for prop in obj.properties: if prop in ignoredProperties: continue value = getattr(obj, prop) if prop in normalizedProperties: value = coerce(value, normalizedProperties[prop]) # TODO improve this system... (need to get better type info in Scenic) if prop in specialDomainProperties and needsSampling(value): dom = specialDomainProperties[prop] else: dom = domainForValue(value) if dom is None: ty = underlyingType(value) print(f'WARNING: skipping property "{prop}" of unknown type {ty}') else: domains[prop] = dom # add type as additional property value = type(obj).__name__ dom = domainForValue(value) assert dom is not None assert 'type' not in domains domains['type'] = dom return Struct(domains)
def getConstantPolygon(self): assert not any( needsSampling(c) or needsLazyEvaluation(c) for c in self.corners) # TODO refactor??? corners = [(x, y) for x, y in self.corners] # convert Vectors to tuples return shapely.geometry.Polygon(corners)
def uniformPoint(self): """Sample a uniformly-random point in this `Region`. Can only be called on fixed Regions with no random parameters. """ assert not needsSampling(self) return self.uniformPointInner()
def handler2(self, *args, **kwargs): if any( needsSampling(arg) for arg in itertools.chain(args, kwargs.values())): return MethodDistribution(method, self, args, kwargs) else: return method(self, *args, **kwargs)
def helper(self, *args, **kwargs): if any(needsSampling(arg) for arg in itertools.chain(args, kwargs.values())): return VectorMethodDistribution(method, self, args, kwargs) elif any(needsLazyEvaluation(arg) for arg in itertools.chain(args, kwargs.values())): # see analogous comment in distributionFunction return makeDelayedFunctionCall(helper, (self,) + args, kwargs) else: return method(self, *args, **kwargs)
def __init__(self, name, points, kdTree=None, orientation=None, tolerance=1e-6): super().__init__(name, orientation=orientation) self.points = tuple(points) for point in self.points: if needsSampling(point): raise RuntimeError('only fixed PointSetRegions are supported') self.kdTree = scipy.spatial.cKDTree(self.points) if kdTree is None else kdTree self.orientation = orientation self.tolerance = tolerance
def polygon(self): position, heading, hw, hh = self.position, self.heading, self.hw, self.hh if any( needsSampling(c) or needsLazyEvaluation(c) for c in (position, heading, hw, hh)): return None # can only convert fixed Regions to Polygons corners = RotatedRectangle.makeCorners(position.x, position.y, heading, hw, hh) return shapely.geometry.Polygon(corners)
def toPolygon(thing): if needsSampling(thing): return None if hasattr(thing, 'polygon'): return thing.polygon if hasattr(thing, 'polygons'): return thing.polygons if hasattr(thing, 'lineString'): return thing.lineString return None
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 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 _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 convertToVerifaiType(value, strict=True): """Attempt to convert a Scenic value to a type known to VerifAI""" ty = underlyingType(value) if ty is float or ty is int: return float(value) elif ty is list or ty is tuple: return tuple(convertToVerifaiType(e, strict=strict) for e in value) elif issubclass(ty, dict) and not needsSampling(value): return frozendict(value) elif ty is GTACarModel: return value elif ty is WebotsCarModel: return value elif ty is Color: return value elif canCoerceType(ty, Vector): return tuple(coerce(value, Vector)) elif strict: # Unknown type, so give up if we're being strict raise RuntimeError( f'attempted to convert Scenic value {value} of unknown type {ty}') else: return value
def __init__(self, center, radius, heading, angle, resolution=32): super().__init__('Sector', center, radius, heading, angle) self.center = center.toVector() self.radius = radius self.heading = heading self.angle = angle r = (radius / 2) * cos(angle / 2) self.circumcircle = (self.center.offsetRadially(r, heading), r) if not any(needsSampling(x) for x in (self.center, radius, heading, angle)): ctr = shapely.geometry.Point(self.center) circle = ctr.buffer(self.radius, resolution=resolution) if angle >= math.tau - 0.001: self.polygon = circle else: mask = shapely.geometry.Polygon([ self.center, self.center.offsetRadially(radius, heading + angle/2), self.center.offsetRadially(2*radius, heading), self.center.offsetRadially(radius, heading - angle/2) ]) self.polygon = circle & mask
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 = req() assert not needsSampling(result) if needsLazyEvaluation(result): raise RuntimeParseError( f'requirement on line {line} uses value' ' undefined outside of object definition') return result except RuntimeParseError as e: cause = e if showInternalBacktrace else None raise InterpreterParseError(e, line) from cause finally: veneer.evaluatingRequirement = False
def show(self, workspace, plt, highlight=False): if needsSampling(self): raise RuntimeError('tried to show() symbolic Object') pos = self.position mpos = workspace.langToMapCoords(pos) if highlight: # Circle around object rad = 1.5 * max(self.width, self.height) c = plt.Circle(mpos, rad, color='g', fill=False) plt.gca().add_artist(c) # View cone ha = self.viewAngle / 2.0 camera = self.position.offsetRotated(self.heading, self.cameraOffset) cpos = workspace.langToMapCoords(camera) for angle in (-ha, ha): p = camera.offsetRadially(20, self.heading + angle) edge = [cpos, workspace.langToMapCoords(p)] x, y = zip(*edge) plt.plot(x, y, 'b:') corners = [ workspace.langToMapCoords(corner) for corner in self.corners ] x, y = zip(*corners) color = self.color if hasattr(self, 'color') else (1, 0, 0) plt.fill(x, y, color=color) #plt.plot(x + (x[0],), y + (y[0],), color="g", linewidth=1) frontMid = averageVectors(corners[0], corners[1]) baseTriangle = [frontMid, corners[2], corners[3]] triangle = [averageVectors(p, mpos, weight=0.5) for p in baseTriangle] x, y = zip(*triangle) plt.fill(x, y, "w") plt.plot(x + (x[0], ), y + (y[0], ), color="k", linewidth=1)
def polygon(self): if any( needsSampling(c) or needsLazyEvaluation(c) for c in self.corners): return None # can only convert fixed Regions to Polygons return self.getConstantPolygon()
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 hasStaticBounds(self, obj): if needsSampling(obj.position): return False if any(needsSampling(corner) for corner in obj.corners): return False return True
def __init__(self, region=everywhere): if needsSampling(region): raise RuntimeParseError('workspace region must be fixed') super().__init__('workspace', orientation=region.orientation) self.region = region