Skip to content
Alex Komoroske edited this page Nov 14, 2021 · 31 revisions

Basic Design

Simulations consist of input parameters, which are fed to a simulation function that then splits out an array of runs. Each run is one run of the simulation, and consists of one or more frames. Frames are the individually renderable frames that might plausibly be output in an animation.

A simulation has a few things it does:

  • A generator function which takes a previousFrames array, simOptions object, a seedValue, a runIndex and produces the next frame. Given the same inputs it should deterministically produce the same resulting run. The harness will call this n * m times to generate m frames for n runs. It will generate a seed based on concatenating the overall options object's seed with the run number and frame number. If the overall options object has an undefined seed then it will generate a new seed at the start of every sim based on the current time. The frame output is a plain old javascript object, and its shape will be specific to this simulator.
  • A renderer, which is a custom element that will be provided its frame object via a frame property, and is responsible for rendering the graphics for that frame
  • A frame scorer, which given a frame returns the 'score' of that frame. The score is an array of numbers that should vary between 0 and 1.0 (the specific length of the array is specific to the simulation, but will typically be 1). The score for the final frame is the score for the overall run.
  • A success scorer, which takes a run score and decides if it should be considered success, failure, or indeterminate. It does this by returning a number. 0.0 is considered full failure, 1.0 is considered full success, and negative numbers are considered indeterminate. The harness will consider any run that is not 1.0 to be a failure.
  • An options validator, which is given the simOptions and returns an array of strings describing problems in the configuration, or null or an empty array if the simOptions are valid
  • A frame validator, which given a frame and the simOptions returns an array of strings describing problems in the frame, or null or an empty array if the frame is valid
  • An optionsExpander, which is an array of functions that are passed a simOptions and either return a new, modified object, or null if it is a no-op. This can be used, for example, for shorthands that expand to specific configurations. The harness will run all of these until all of them return null.
  • A simOptions config that returns the names, allowed values, and description of each field for use in rendering the GUI to control sim parameters

The harness renders additional UI, for example controls to vary the inputs to the simulation. It also provides affordances to snapshot runs of the simulation into pngs and gifs, and renders each run in a summary pane with animation controls for it.

[
{
  "name": "short-name",
  "description": "Human readable description",
  "seed": "abc",
  "size": [350, 400],
  "runs": 10,
  "colors": {
    "primary": "#CCFF00",
    "secondary": "rgba(255,200,100,0.1)",
    "background": "transparent",
  },
  "sim": "schelling-org",
  "simOptions": {
    //Simulator specific options
  }
}
]

The settings objects are in an array. The Name is used by the harness to render which settings config is active.

schelling-org

The first simulator type is schelling-org. It renders orange boxes for options, each with an error bar, and either a web of individuals, each of whom makes a choice, or a flat list of individuals with walls between. The individuals are rendered as emojis.

The simOptions for schelling-org look like:

{
  //If communication is falsely, no communication is allowed, and its one shot
  "communication": {
    //how many rounds of pairwise communication to allow before requiring every collaborator to choose
    "rounds": 10,
  },
  "collaborators": {
    "seed": 345,
    "count": 8,
    //if individuals is less than count it will be expanded to that size, then any null items will be auto-generated
    "individuals": [
      null,
      {
        //beliefs must be projects.count in length. It is their belief of the value of each. When they decide at the end, they will sample based on that probability distribution. If every belief is the same then ties are broken based on which of the items is marked.
        "beliefs": [0.0, 0.1, 1.0, 0.1]
      }
    ]
  },
  "projects": {
    "seed": "abcd",
    "count": 4,
    "averageValue": 0.5,
    "averageError": 0.25,
    //if value is less than count, it will be expanded to n slots. averageValue and averageError will be used to create any null slots
    "individuals": [
      null,
      null,
      {
        //1.0 is as wide as it is tall. (that is, the height is in multiples of the width
        "value": 1.0,
        //Same unit as value
        "error": 1.0,
        "marked": true,
      }
    ]
  }
}

Collaborators and projects can have a seed parameter. If set, then instead of using the global generator seed provided it will use that seed. This is useful for randomizing other things but keeping the initial bit of the projects and collaborators consistent from run to run.

Renderers

The renderers should be factored nicely to make common things (like webs of individuals with some state) easy to render.

The harness also creates css-variables with certain colors, e.g. --primary-color and --secondary-color

The harness also renders out controls that allow changing all options of the options object,including simOptions. If there is a "seed" property it renders as a dice roll icon. It also allows saving and restoring simulation values (perhaps with some link-encoding)

Clone this wiki locally