Spawn Input File Definition¶
Spawn input is a hierarchical structure of branching nodes which allows large numbers of parameter sets (referred to as “specification nodes”) to be specified in a declarative manner. The input is defined in JSON format (see http://json.org for the JSON standard). JSON editors are widely available, or you can use your favourite text editor to write Spawn input files.
Getting Started¶
The specification is defined in an object named "spec"
. Each name/value pair within this object is a parameter name and its value. The following generates a single specification node with one parameter, named "alpha"
with a value of 4:
{
"spec": {
"alpha": 4
}
}
Sibling name/value pairs are simultaneous (i.e. occur on the same node). The following generates a single node with two simultaneous parameters - "alpha"
with a value of 4, and “beta” with a value of “tadpole”:
{
"spec": {
"alpha": 4,
"beta": "tadpole"
}
}
Separate nodes can be created by separating parameters into different JSON nodes. Parameters defined outside of the object are also applied on each node. The following creates two nodes, both with parameters named "alpha"
and "beta"
, where the first node has parameter values of 4 and “tadpole” respectively and the second has values of 6 and “tadpole” respectively. Note that the names of the sub-objects ("blah"
and "blo"
) do not contribute to the parameter specification:
{
"spec": {
"beta": "tadpole",
"blah": { "alpha": 4},
"blo": { "alpha": 6}
}
}
An identical specification could be written (less concisely) as:
{
"spec": {
"blah": { "alpha": 4, "beta": "tadpole"},
"blo": { "alpha": 6, "beta": "tadpole"}
}
}
Avoiding repetitive definition and enabling concise and readable but complex specifications is one of the key aims of Spawn.
Arrays¶
Multiple specification nodes where one parameter is varied can be created by using the array property of JSON. The same specification as at the end of the last section (two nodes, both with parameter “beta” of “tadpole” and parameter “alpha with values of 4 and 6) can be created by:
{
"spec": {
"alpha": [4, 6],
"beta": "tadpole"
}
}
Cartesian Product¶
The automatic behaviour of multiple sibling arrays is to create all the parameter combinations of them (i.e. apply Cartesian Product). The following will create 6 (3*2) nodes (2D product):
{
"spec": {
"alpha": [3, 5, 8],
"beta": ["tadpole", "frog"]
}
}
Additional sibling arrays will add additional dimensions to the Cartesian product, and there is no limit. In this manner, a very large number of nodes can be created with just a few lines.
Zip¶
To apply a one-one mapping between, we apply the zip “combinator” on the two arrays. A “combinator” is a name/object pair where the name determines the combination to be performed. The name starts with a #
to differentiate it from other name/object pairs. The following generates three nodes ((3, “egg”), (5, “tadpole”), (8, “frog”)):
{
"spec": {
"combine:zip": {
"alpha": [3, 5, 8],
"beta": ["egg", "tadpole", "frog"]
}
}
}
There is no limit to the number of sibling arrays, but they must all have equal size.
Value Proxies¶
The value of parameter/value pairs can be represented by a proxy. The proxy is a string that starts with either a type identifier followed by a colon (longhand) or a special character (shorthand) to determine which type of value proxy it is. The parser then replaces the proxy when the specification is resolved. The types of value proxies are as follows:
Type |
Longhand |
Shorthand |
Description |
---|---|---|---|
Macro |
|
|
Direct substitution of a previously declared value |
Generator |
|
|
Generates a (in general different) value each time it is resolved |
Evaluator |
|
|
Evaluates a value based on a deterministic function which can take arguments |
Macros¶
Macros are declared alongside the spec and can then be used repeatedly. The name of the name/value pairs in the macros
object determines the name of the macro that can be used in the spec
object (where it must be prefixed). They can be a single value, array or object. The following will produce six nodes (three values of "alpha"
with "beta"
parameter specified, and three more with "gamma"
parameter specified):
{
"macros": {
"Alphas": [3, 5, 8]
},
"spec": {
"a": {
"alpha": "macro:Alphas",
"beta": "tadpole"
},
"b": {
"alpha": "$Alphas",
"gamma": 4.2
}
}
}
Generators¶
Generators generate a value each time they are resolved in the specification. The main uses of generators is providing random variates and counters. Generators are declared in an object named generators
. Each generator is a name/object pair, where the name specifies the name of the generator to be used in the spec
object. Each generator object must specify its method and arguments to its constructor. These are the inbuilt constructors:
|
Argument names (default value) |
Description |
---|---|---|
|
|
An incrementing integer starting at |
|
|
A random integer between |
The following example generates a value of 4 for “alpha” via the “a” object and a value of 5 via the “b” node:
{
"generators": {
"Counter": {
"method": "IncrementalInt",
"start": 4
}
},
"spec": {
"a": {
"alpha": "@Counter",
"beta": "tadpole"
},
"b": {
"alpha": "gen:Counter",
"gamma": 4.2
}
}
}
Evaluators¶
Evaluators allow function-style syntax to evaluate expressions with arguments. Arithmetic operations are supported as well as inbuilt evaluators range
, which produces an evenly spaced array, and repeat
, which repeats a particular value. Unlike macros and generators, evaluators do not need an object defined alongside the spec
. Some examples:
Example |
Resolution |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Note that the repeat
can be used with a generator as argument and therefore generate a different value for each element of the array. Evaluators can also take other parameters simultaneously present in the specification if they are prefixed by !
. They do not need to be in the same object, but if not they must be defined higher up the object tree (i.e. they are not referenceable if in sub-objects). The following resolves "gamma"
into the list [3, 4]
:
{
"spec": {
"alpha": 3,
"blah": {
"beta": 5,
"gamma": "#range(!alpha, !beta)"
}
}
}
When referencing a parameter in an arithmetic operation, the #
is no longer needed (but the !
is required):
{
"spec": {
"alpha": 4,
"beta": "!alpha + 3"
}
}
Literals¶
There are cases in which it is desired that specification value does not take on its default spawn interpretation. For this, there is the concept of a literal. This is done by prefixing the parameter name with ~
. The following will generate a single node where alpha
is an array (passed through to the spawner as a list) and beta
is a string starting with $
(rather than looking up a macro). Arrays, objects, all (apparent) value proxies and equations can be taken as literal and therefore not expanded or looked up:
{
"spec": {
"~alpha": ["egg", "tadpole", "frog"],
"~beta": "$NotAMacro"
}
}
Literals can also be specified on the value-side. This can be particularly useful when it is desired to expand literals as part of an expansion. In this case, the value must always be a string, but if the string succeeding the literal prefix is JSON serialisable it will be serialised as such, otherwise the value will remain a string. For example, the following produces three nodes, each with an array as the value of the alpha
parameter:
{
"spec": {
"alpha": ["~[1, 2]", "~[3, 4]", "~[5, 6, 7]"]
}
}
Policies¶
Policies do not generate parameters but provide additional information for the spawner to work with. The use of the end policy is determined by the spawner. The only policy at current is the path policy
Path¶
This may generally be interpreted as a file path but could also be for example a URL endpoint or any other kind of path. All specification nodes have a path associated with them, whether specified by the user or not. In the case of simulations, this path can be used to determine where the output of the simulation is saved. The following produces one specification node with the path my_path
{
"spec": {
"policy:path": "my_path",
"alpha": "tadpole"
}
}
Paths that are declared at different levels of the tree are appended as sub-folders. For example the following will produce a single node with the path my/path
:
{
"spec": {
"policy:path": "my",
"alpha": "tadpole",
"blah": {
"policy:path": "path",
"beta": 2
}
}
}
Distinct nodes in the tree always have different paths. If there are two nodes that resolve to the same path, then they will be put into lettered sub-folders a
, b
, c
etc… For example, the following produces the paths my_path/a
, my_path/b
, my_path/c
:
{
"spec": {
"policy:path": "my_path",
"alpha": ["egg", "tadpole", "frog"]
}
}
In order to make more meaningful paths, parameter values (of any type that is convertible to string) can also be inserted in the path, by using {name}
syntax in the path. For example, the following produces the paths a_egg
, a_tadpole
, a_frog
:
{
"spec": {
"policy:path": "a_{alpha}",
"alpha": ["egg", "tadpole", "frog"]
}
}
Sub-folders can also be assigned in a single policy:path
value by adding /
in the path:
{
"spec": {
"policy:path": "{alpha}/{beta}",
"alpha": ["egg", "tadpole", "frog"],
"beta": [1, 2, 3]
}
}
Rather than using the value of a parameter in the path, an incremental digit or letter can be used instead by introducing the syntax {name:1}
or {name:a}
respectively. For example, the following produces the paths alpha_1
, alpha_2
, alpha_3
:
{
"spec": {
"policy:path": "alpha_{alpha:1}",
"alpha": ["egg", "tadpole", "frog"]
}
}
Sequences can also be made to start at other letters/digits by using the syntax {name:4}
(which would start at 4) for example. Here is a full table the alphanumeric indicators that can be used:
Identifier |
Sequence |
---|---|
|
a, b, c, … aa, ab, … |
|
f, g, h, … aa, ab, … |
|
1, 2, 3, … 10, 11, … |
|
5, 6, 7, … 10, 11, … |
|
aa, ab, … ba, bb, … |
|
01, 02, … 10, 11, … |
Indexing¶
Spawners may define certain parameters as arrays. In order to set the values of elements in an array parameter, the specification allows indexing with use of square brackets suffixing the name parameter pair. For example, "alpha[1]": 3
sets the first element of the "alpha"
parameter array to 3. Whether the index is 0-based or 1-based is ultimately the decision of the spawner (which gets passed the index) but by convention is generally 1-based.