diff --git a/docs/api.rst b/docs/api.rst index 440f450f2..9e8fc299f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -28,10 +28,16 @@ the sampled values for all the global parameters and objects in the scene from t import os os.chdir('..') +To make sampling deterministic, seed Scenic's random number generators +before calling `Scenario.generate` using `scenic.setSeed` (the programmatic +equivalent of the :option:`--seed` command-line option): + +.. autofunction:: scenic.setSeed + .. testcode:: - import random, scenic - random.seed(12345) + import scenic + scenic.setSeed(12345) scenario = scenic.scenarioFromString('ego = new Object with foo Range(0, 5)') scene, numIterations = scenario.generate() print(f'ego has foo = {scene.egoObject.foo}') diff --git a/docs/options.rst b/docs/options.rst index 1df985596..3efdc6c75 100644 --- a/docs/options.rst +++ b/docs/options.rst @@ -48,6 +48,8 @@ General Scenario Control (although :mod:`random` and :mod:`numpy.random` should not be used in place of Scenic's own sampling constructs in Scenic code). + This option can be configured from the Python API using `scenic.setSeed`. + .. option:: --scenario If the given Scenic file defines multiple scenarios, select which one to run. diff --git a/src/scenic/__init__.py b/src/scenic/__init__.py index ac15ea073..589730ef2 100644 --- a/src/scenic/__init__.py +++ b/src/scenic/__init__.py @@ -1,8 +1,31 @@ """A compiler and scene generator for the Scenic scenario description language.""" +import random as _random + +import numpy as _numpy + import scenic.core.errors as _errors from scenic.core.errors import setDebuggingOptions from scenic.syntax.translator import scenarioFromFile, scenarioFromString _errors.showInternalBacktrace = False # see comment in errors module del _errors + + +def setSeed(seed): + """Seed the random number generators Scenic uses for sampling. + + Seeds both the Python :mod:`random` module and :mod:`numpy.random`, which + are what Scenic's rejection sampler draws from. After calling this, + subsequent calls into Scenic (e.g. :func:`scenarioFromFile` / + :meth:`Scenario.generate`) will produce deterministic results for the + given seed. + + This is the programmatic equivalent of the ``-s``/``--seed`` + command-line option; see :option:`--seed`. + + Args: + seed (int): Seed value to pass to both RNGs. + """ + _random.seed(seed) + _numpy.random.seed(seed) diff --git a/src/scenic/__main__.py b/src/scenic/__main__.py index 05f527fba..2053b0ab4 100644 --- a/src/scenic/__main__.py +++ b/src/scenic/__main__.py @@ -3,13 +3,10 @@ import argparse from importlib import metadata -import random import sys import time import warnings -import numpy - import scenic from scenic.core.distributions import RejectionException import scenic.core.errors as errors @@ -185,8 +182,7 @@ if args.verbosity >= 1: print(f"Using random seed = {args.seed}") - random.seed(args.seed) - numpy.random.seed(args.seed) + scenic.setSeed(args.seed) # Load scenario from file if args.verbosity >= 1: diff --git a/tests/syntax/test_basic.py b/tests/syntax/test_basic.py index 8c3d01483..0b70ae6ce 100644 --- a/tests/syntax/test_basic.py +++ b/tests/syntax/test_basic.py @@ -249,6 +249,17 @@ def test_verbose(): setDebuggingOptions(verbosity=1) +def test_setSeed(): + scenic.setSeed(12345) + p1 = sampleParamPFrom("param p = Range(0, 1)") + scenic.setSeed(12345) + p2 = sampleParamPFrom("param p = Range(0, 1)") + assert p1 == p2 + scenic.setSeed(54321) + p3 = sampleParamPFrom("param p = Range(0, 1)") + assert p1 != p3 + + def test_dump_ast(): scenic.syntax.translator.dumpScenicAST = True try: