Experiment configuration file#

The experiment configuration file describes the instruments required by an experiment, their parameter space and how to iterate through it.

Typical experiment.yaml content#
description: This experiment does this and that

instrument1:
    interface: actuator1
    parameter1: value1
    parameter2: value2

instrument2:
    interface: sensor1
    parameter: value

    connections:
        - instrument3

instrument3:
    parameter: value

Purpose#

This file should contain most of the parameters required by an experiment, be it to do it for the first time or to replicate or share it. This includes the parameters of the instruments used on the bench, how they are connected together, and any other information useful for the experimenter (description, expected results, manual configurations, etc.).

Syntax#

It is a YAML file, containing a top level mapping. During the initialization of an ExperimentFactory, it is parsed to an IterationTree which is then stored in its experiment_config attribute.

In order to ease the representation of the different iteration methods and leaves, custom YAML types are supported. Whereas YAML only supports lists, mapping, integer, floats, booleans, strings and none values, this file can also represent Phileas objects. They are identified by a ! prefix. For example, a sequence of values can be represented by !sequence [1, 2, 3].

See also

See Custom YAML types for more details about the supported types.

Instrument entries#

Each of the mapping entries with an interface key represents an experiment instrument. Its value is matched with the interfaces values of bench instruments’ loaders. Think of it this way:

  • in a bench configuration file, the set of available instruments is listed;

  • in the experiment configuration file, you list the requirements of an instrument (eg. it must be an oscilloscope);

  • Phileas takes care of providing you with a suitable instrument.

The other non-reserved entries of the instrument represent an IterationTree which contains the set of its configurations. You can use the methods configure_instrument() and configure_experiment() of ExperimentFactory to apply them. Internally, they use phileas.factory.Loader.configure().

Bench instrument filtering#

Multiple bench instruments can have the same interface. For example, there might be multiple motors or lights on the same bench. In this case, they would all expose the same interfaces, which would prevent Phileas from knowing which instrument to provide you. This issue can be solved by the filter entry. It contains a set of attributes and values to be matched against the bench instruments attributes.

Consider the following bench file:

bench.yaml with instruments exposing the same interfaces#
light-motor:
    loader: brandXX-modelXX
    moves: light

probe-motor:
    loader: brandYY-modelYY
    moves: probe

Let us say that only the probe motor is used, while the light remains stationary. Both loaders brandXX-modelXX and brandYY-modelYY expose the same motor interface. Using filters, this experiment configuration file selects only probe-motor:

experiment.yaml using filters to chose a bench instrument#
probe-position:
    interface: motor
    filter:
        moves: probe

    x: 0.5
    y: 1.2
    z: 0

probe-position is matched to probe-motor, because it is the only suitable bench instrument with the attribute moves = probe.

Connections#

An experiment is represented by its instruments, how they are configured, but also how they interact together. An experiment configuration file can represent those interactions with so-called connections, which are the edges in the global interaction graph of the experiment, where instruments are nodes.

The optional top-level connections entry contains the list of the interaction graph edges. Each edge contains:

  • a mandatory origin, stored in the from entry,

  • a mandatory destination, stored in the to entry,

  • and, optionally, arbitrary data stored in its attributes entry.

The origin and destination of an edge are usually instruments. You can suffix them with ports, separating them with dots. For example, oscilloscope.chA states that the connections involves the A channel of the oscilloscope.

Global connections definition#
connections:
    - from: probe
      to: oscilloscope.chA
    - from: holder
      to: probe
      attributes: holds

In this example, the probe is connected to the channel A of the oscilloscope, and is hold by holder.

However, it is not always convenient to define the experiment graph in a distinct entry, separating it from the instruments. In these cases, you can define interactions directly in the entry of an instrument. The instrument entry connections can be used similarly as the top-level entry. However, you can use a simplified notation: if the origin or destination of an edge is the instrument itself, you can simply omit the instrument name, starting directly at its first port.

Instrument connections definition#
temperature-measurement:
    interface: oscilloscope
    connections:
        - from: chA
          to: temperature-probe

This examples states that channel A of the oscilloscope is connected to the temperature probe.

This experiment graph is built by Phileas, but it does not use it. You can access it through get_experiment_graph().

Documentation#

Phileas ignores all the top-level entries which are not mappings with key interface. It is advised to document the experiment with them. For example, you can add a description entry, which gives a unique name to the experiment, describes the tested hypothesis, its measurements and the expected-results.

This whole file is parsed and made available in the factory experiment_config. You can then retrieve that information in simple nested Python dict and list by using to_pseudo_data_tree().

Custom YAML types#

The experiment configuration file is parsed to an IterationTree, which is built using the different Python classes defined in iteration. Custom YAML data types can be used to represent these. They are identified by a ! prefix.

Cartesian product#

By default, a YAML mapping or sequence is parsed as a cartesian product:

Hide code cell source

1content = """
2parameter1: !sequence [1, 2, 3]
3parameter2: !sequence [a, b, c]
4"""
5tree = load_iteration_tree_from_yaml_file(content)
6print(f"{content}\n\t\t|\n\t\tv\n")
7pprint(tree)
parameter1: !sequence [1, 2, 3]
parameter2: !sequence [a, b, c]

		|
		v

CartesianProduct(children={'parameter1': Sequence(elements=[1, 2, 3],
                                                  default_value=_NoDefault()),
                           'parameter2': Sequence(elements=['a', 'b', 'c'],
                                                  default_value=_NoDefault())},
                 order=None,
                 lazy=False,
                 snake=False)

You can also directly invoke !product, which allows to set the node parameters, prefixed by an underscore:

Hide code cell source

 1content = """
 2!product
 3    _lazy: true
 4    _snake: true
 5    parameter1: !sequence [1, 2, 3]
 6    parameter2: !sequence [a, b, c]
 7"""
 8tree = load_iteration_tree_from_yaml_file(content)
 9print(f"{content}\n\t\t|\n\t\tv\n")
10pprint(tree)
!product
    _lazy: true
    _snake: true
    parameter1: !sequence [1, 2, 3]
    parameter2: !sequence [a, b, c]

		|
		v

CartesianProduct(children={'parameter1': Sequence(elements=[1, 2, 3],
                                                  default_value=_NoDefault()),
                           'parameter2': Sequence(elements=['a', 'b', 'c'],
                                                  default_value=_NoDefault())},
                 order=None,
                 lazy=True,
                 snake=True)

Other iteration methods#

Similarly, you can use !union, !pick and !configurations to build Union, Pick and Configurations.

Unary nodes#

!shuffle builds Shuffle node, and requires its only child to be stored in the child key:

Hide code cell source

1content = """
2!shuffle
3    child: !sequence [1, 2, 3]
4"""
5tree = load_iteration_tree_from_yaml_file(content)
6print(f"{content}\n\t\t|\n\t\tv\n")
7pprint(tree)
!shuffle
    child: !sequence [1, 2, 3]

		|
		v

Shuffle(child=Sequence(elements=[1, 2, 3], default_value=_NoDefault()),
        seed=None)

For now, transform nodes are not supported. You have to add them to the iteration tree manually, from a Python script.

Iteration leaves#

!sequence builds Sequence nodes. It can be used in the short form !sequence [a, b, c], or its long form

!sequence
    elements: [a, b, c]
    default: z

!range is often used, as it represents different kinds of numeric ranges. It expects a start and end parameters, and either

  • steps, the total number of points in the range, or

  • resolution, the maximum distance between two points in the range.

!random represent random distribution, and is parsed to NumpyRNG. It expects a distribution argument, which is the string name of a numpy distribution. You can find them in numpy.random.Generator. Its parameters are given in the parameters field, as a mapping. You can additionally give it a finite size, and a default value.

If you want to generate arbitrarily big integers, you can’t use !random, as Numpy does not support generating them. Instead, you can use !random_uniform_bigint to generate uniformly sampled integers. It takes two mandatory arguments low and high, which are the inclusive bounds of the generated interval.

You can generate prime numbers with !random_prime, which has the same arguments as !random_uniform_bigint.

Template generation#

A bench template file can be generated automatically by Phileas. See python -m phileas generate experiment -h for the available options.