Skip to content
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 85 additions & 13 deletions src/scenic/core/external_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@

"""

from abc import ABC, abstractmethod
import warnings

from dotmap import DotMap
import numpy

Expand Down Expand Up @@ -182,6 +185,7 @@ def __init__(self, params, globalParams):
import verifai.server

# construct FeatureSpace
timeBound = globalParams.get("timeBound", 0)
usingProbs = False
self.params = tuple(params)
for index, param in enumerate(self.params):
Expand All @@ -193,11 +197,23 @@ def __init__(self, params, globalParams):
param.index = index
if param.probs is not None:
usingProbs = True

if timeBound == 0 and any(param.timeSeries for param in self.params):
warnings.warn(
"TimeSeries external parameter used by no time bound specified "
"(Did you provide `maxSteps` when creating ScenicSampler?)."
)

space = verifai.features.FeatureSpace(
{
self.nameForParam(index): verifai.features.Feature(param.domain)
self.nameForParam(index): (
verifai.features.Feature(param.domain)
if not param.timeSeries
else verifai.features.TimeSeriesFeature(param.domain)
)
for index, param in enumerate(self.params)
}
},
timeBound=timeBound,
)

# set up VerifAI sampler
Expand Down Expand Up @@ -252,30 +268,57 @@ def __init__(self, params, globalParams):
self.rejectionFeedback = 1
self.cachedSample = None

self._lastSample = None
self._lastDynamicSample = None
self._lastTime = -1

def nextSample(self, feedback):
return self.sampler.nextSample(feedback)
if feedback is not None:
assert self._lastSample is not None
self._lastSample.update(feedback)

def update(self, sample, info, rho):
self.sampler.update(sample, info, rho)
self._lastSample = self.sampler.getSample()
return self._lastSample

def getSample(self):
return self.sampler.getSample()
def nextDynamicSample(self):
import scenic.syntax.veneer as veneer

assert veneer.currentSimulation is not None

if veneer.currentSimulation.currentTime > self._lastTime:
feedback = veneer.currentSimulation
self._lastDynamicSample = self.cachedSample.getDynamicSample(feedback)
self._lastTime = veneer.currentSimulation.currentTime

return self._lastDynamicSample

def valueFor(self, param):
return getattr(self.cachedSample, self.nameForParam(param.index))
if not param.timeSeries:
return param.extractOutput(
getattr(self.cachedSample.staticSample, self.nameForParam(param.index))
)
else:
callback = lambda: param.extractOutput(
getattr(
self.nextDynamicSample(),
self.nameForParam(param.index),
)
)
return TimeSeriesParameter(callback)

@staticmethod
def nameForParam(i):
"""Parameter name for a given index in the Feature Space."""
return f"param{i}"


class ExternalParameter(Distribution):
class ExternalParameter(Distribution, ABC):
"""A value determined by external code rather than Scenic's internal sampler."""

def __init__(self):
super().__init__()
self.sampler = None
self.timeSeries = False
import scenic.syntax.veneer as veneer # TODO improve?

veneer.registerExternalParameter(self)
Expand All @@ -289,6 +332,37 @@ def sampleGiven(self, value):
assert self.sampler is not None
return self.sampler.valueFor(self)

@abstractmethod
def extractOutput(self, value):
pass


class TimeSeriesParameter:
def __init__(self, callback):
self._callback = callback
self._lastTime = -1

def getSample(self):
import scenic.syntax.veneer as veneer

assert veneer.currentSimulation is not None

if veneer.currentSimulation.currentTime <= self._lastTime:
raise RuntimeError(
"Attempted `getSample` for a timeSeries property twice in one timestep."
)

self._lastTime = veneer.currentSimulation.currentTime
return self._callback()


def TimeSeries(param):
if not isinstance(param, ExternalParameter):
raise ValueError("Cannot turn a non `ExternalParameter` into a time series")

param.timeSeries = True
return param


class VerifaiParameter(ExternalParameter):
"""An external parameter sampled using one of VerifAI's samplers."""
Expand Down Expand Up @@ -341,8 +415,7 @@ def __init__(self, low, high, buckets=None, weights=None):
total = sum(weights)
self.probs = tuple(wt / total for wt in weights)

def sampleGiven(self, value):
value = super().sampleGiven(value)
def extractOutput(self, value):
assert len(value) == 1
return value[0]

Expand All @@ -367,8 +440,7 @@ def __init__(self, low, high, weights=None):
else:
self.probs = None

def sampleGiven(self, value):
value = super().sampleGiven(value)
def extractOutput(self, value):
assert len(value) == 1
return value[0]

Expand Down
2 changes: 2 additions & 0 deletions src/scenic/syntax/veneer.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@
"VerifaiRange",
"VerifaiDiscreteRange",
"VerifaiOptions",
"TimeSeries",
"File",
"Files",
# Constructible types
Expand Down Expand Up @@ -201,6 +202,7 @@
from scenic.core.dynamics.invocables import BlockConclusion, runTryInterrupt
from scenic.core.dynamics.scenarios import DynamicScenario
from scenic.core.external_params import (
TimeSeries,
VerifaiDiscreteRange,
VerifaiOptions,
VerifaiParameter,
Expand Down
Loading