def valueRequiringEqualTypes(val, thingA, thingB, typeError='type mismatch'): """Return the value, assuming thingA and thingB have the same type.""" if not needsLazyEvaluation(thingA) and not needsLazyEvaluation(thingB): if underlyingType(thingA) is not underlyingType(thingB): raise RuntimeParseError(typeError) return val else: return TypeEqualityChecker(val, thingA, thingB, typeError)
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 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 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 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 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 toTypes(thing, types, typeError='wrong type'): """Convert something to any of the given types, printing an error if possible.""" if needsLazyEvaluation(thing): # cannot check the type now; create proxy object to check type after evaluation return TypeChecker(thing, types, typeError) else: return coerceToAny(thing, types, typeError)
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 test_distribution_method_encapsulation_lazy(): vf = VectorField("Foo", lambda pos: 0) da = DelayedArgument(set(), lambda context: Options([1, 2])) pt = vf.followFrom(Vector(0, 0), da, steps=1) assert isinstance(pt, DelayedArgument) evpt = valueInContext(pt, {}) assert not needsLazyEvaluation(evpt) assert isinstance(evpt, VectorMethodDistribution) assert evpt.method is underlyingFunction(vf.followFrom)
def test_delayed_call(): da = DelayedArgument(('blob', ), lambda context: (lambda x, y=1: x * y + context.blob)) res = da(42, y=2) assert isinstance(res, DelayedArgument) Thingy = namedtuple('Thingy', 'blob') evres = valueInContext(res, Thingy(blob=-7)) assert not needsLazyEvaluation(evres) assert evres == 77
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 test_delayed_call(): da = DelayedArgument(('blob', ), lambda context: (lambda x, y=1: x * y + context.blob)) res = da(42, y=2) assert isinstance(res, DelayedArgument) context = LazilyEvaluable.makeContext(blob=-7) evres = valueInContext(res, context) assert not needsLazyEvaluation(evres) assert evres == 77
def applyTo(self, obj, optionals): """Apply specifier to an object, including the specified optional properties.""" val = self.value.evaluateIn(obj) val = toDistribution(val) assert not needsLazyEvaluation(val) obj._specify(self.property, val) for opt in optionals: assert opt in self.optionals obj._specify(opt, getattr(val, opt))
def __init__(self, dependencies): deps = [] props = set() for dep in dependencies: if needsSampling(dep) or needsLazyEvaluation(dep): deps.append(dep) props.update(requiredProperties(dep)) super().__init__(props) self._dependencies = tuple(deps) # fixed order for reproducibility self._conditioned = self # version (partially) conditioned on requirements
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 helper(*args, **kwargs): args = tuple(toDistribution(arg) for arg in args) kwargs = { name: toDistribution(arg) for name, arg in kwargs.items() } if any(needsSampling(arg) for arg in itertools.chain(args, kwargs.values())): return MethodDistribution(method, instance, args, kwargs) elif any(needsLazyEvaluation(arg) for arg in itertools.chain(args, kwargs.values())): # see analogous comment in distributionFunction return makeDelayedFunctionCall(helper, args, kwargs) else: return wrapped(*args, **kwargs)
def helper(*args, **kwargs): args = tuple(toDistribution(arg) for arg in args) kwargs = { name: toDistribution(arg) for name, arg in kwargs.items() } if any(needsSampling(arg) for arg in itertools.chain(args, kwargs.values())): return FunctionDistribution(wrapped, args, kwargs, support, valueType) elif any(needsLazyEvaluation(arg) for arg in itertools.chain(args, kwargs.values())): # recursively call this helper (not the original function), since the # delayed arguments may evaluate to distributions, in which case we'll # have to make a FunctionDistribution return makeDelayedFunctionCall(helper, args, kwargs) else: return wrapped(*args, **kwargs)
def toDistribution(val): """Wrap Python data types with Distributions, if necessary. For example, tuples containing Samplables need to be converted into TupleDistributions in order to keep track of dependencies properly. """ if isinstance(val, (tuple, list)): coords = [toDistribution(c) for c in val] if any(needsSampling(c) or needsLazyEvaluation(c) for c in coords): if isinstance(val, tuple) and hasattr(val, '_fields'): # namedtuple builder = type(val)._make else: builder = type(val) return TupleDistribution(*coords, builder=builder) return val
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 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 evaluateIn(self, context): """See `LazilyEvaluable.evaluateIn`.""" value = super().evaluateIn(context) # Check that all dependencies have been evaluated assert all(not needsLazyEvaluation(dep) for dep in value._dependencies) return value
def generate(self, maxIterations=2000, verbosity=0, feedback=None): objects = self.objects # choose which custom requirements will be enforced for this sample activeReqs = [ req for req, prob in self.requirements if random.random() <= 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 RuntimeError( 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) assert isinstance(sampledObj.position, Vector) sampledObj.heading = float(sampledObj.heading) # 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(sample): rejection = 'user-specified requirement' 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] if isinstance(value, Samplable) else value assert not needsLazyEvaluation(sampledValue) sampledParams[param] = sampledValue scene = Scene(self.workspace, sampledObjects, ego, sampledParams) return scene, iterations
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 storeScenarioStateIn(namespace, requirementSyntax, filename): # 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 requirements and create proper 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): 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 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) # 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))
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))