--- jupytext: text_representation: extension: .md format_name: myst format_version: 0.13 jupytext_version: 1.17.1 kernelspec: name: python3 display_name: Python 3 (ipykernel) language: python --- # Getting the state of an experiment bench ```{code-cell} ipython3 :tags: [remove-cell] from pathlib import Path from pprint import pprint from phileas import ExperimentFactory from phileas.factory import BenchState import phileas.mock_instruments ``` To replicate an experiment, it is useful to compare the state of the original and the current experiment bench, ensuring they are as close as possible. Phileas has different tools that allow to do this. ## Example bench Let us work with the following simple experiment bench: ```{code-cell} ipython3 bench = """ simulated-motors: loader: phileas-mock_motors-phileas simulated-oscilloscope: loader: phileas-mock_oscilloscope-phileas probe: electric-field-probe motors: simulated-motors """ experiment = """ oscilloscope: interface: oscilloscope amplitude: !range start: 0.1 end: 10 steps: 5 iteration: !sequence [1, 2] """ factory = ExperimentFactory(bench, experiment) factory.initiate_connections() ``` ## Verifying the effectiveness of an applied configuration After having configured an instrument, it is possible that its effective configuration differs from the required one. You can use {py:meth}`~phileas.factory.ExperimentFactory.get_effective_instrument_configuration` and {py:meth}`~phileas.factory.ExperimentFactory.get_effective_experiment_configuration` to know the exact state of the instrument. In our case, the amplitude of the oscilloscope can only be a power of 10. Configuring it, and getting its effective configuration, can be done with: ```{code-cell} ipython3 for conf in factory.experiment_config: factory.configure_experiment(conf) effective_conf = factory.get_effective_instrument_configuration("oscilloscope") required_amp = conf["oscilloscope"]["amplitude"] effective_amp = effective_conf["amplitude"] print(f"Configuration: {conf}") print(f"Amplitude, required: {required_amp}\tEffective: {effective_amp}\n") ``` Let us now consider the case where we set the `lazy` and `snake` parameters of the configuration: ```{code-cell} ipython3 exp_conf = factory.experiment_config.with_params(lazy=True, snake=True) for conf in exp_conf: print(conf) ``` The amplitude of the oscilloscope is not always configured. Thus, it is not efficient to retrieve its configuration at each step of the loop. In this case, we can give {py:meth}`~phileas.factory.ExperimentFactory.get_effective_experiment_configuration` an empty configuration that it then fills, ignoring missing fields: ```{code-cell} ipython3 for conf in exp_conf: factory.configure_experiment(conf) effective_conf = factory.get_effective_experiment_configuration(conf) print(f"Required: {conf}\nEffective: {effective_conf}\n") ``` :::{note} Not all loaders implement {py:meth}`~phileas.factory.Loader.get_effective_configuration`. They are ignored by the methods which retrieve the effective configuration of an instrument or an entire bench. ::: ## Dumping the complete state of an experiment bench Retrieving the configuration of an instrument is not sufficient to guarantee that its state is the same as in another experiment. The experiment factory provides the {py:meth}`~phileas.factory.ExperimentFactory.dump_bench_state` method, whose purpose is to build the most complete representation of the state of an experiment bench. ```{code-cell} ipython3 pprint(factory.dump_bench_state()) ``` You can use the state as is. Alternatively, you can export it to YAML, allowing to save it or compare it to another saved state: ```{code-cell} ipython3 saved_state = factory.dump_bench_state().to_yaml() print(saved_state) ``` {py:meth}`~phileas.factory.BenchState.to_yaml` also takes an optional argument, which is the path of a file where the state can be stored. ```python factory.dump_bench_state().to_yaml(Path("state.yaml")) ``` If you have a bench and an experiment configuration file, and a script which registers the required loaders, you can use the CLI to dump the state of the bench: ```sh $ python -m phileas --bench bench.yaml --experiment experiment.yaml --script script.py version: '0.5' instruments: simulated-motors: loader: phileas-mock_motors-phileas interfaces: - 2d-motors configuration: {} id: mock-motors-driver:1 state: simulated-oscilloscope: loader: phileas-mock_oscilloscope-phileas interfaces: - oscilloscope configuration: probe: electric-field-probe motors: simulated-motors id: mock-oscilloscope-driver:1 state: ``` :::{note} You can use exported states to compare them, with `diff` for example. ::: Once you have stored a state, you can use it later to restore the bench: ```{code-cell} ipython3 state = BenchState.from_yaml(saved_state) factory.restore_bench_state(state) ``` {py:meth}`~phileas.factory.ExperimentFactory.restore_bench_state` has an optional argument `strict` which, when set, prevents restoring states whose version, instrument names, loaders, interfaces and ids do not exactly match. ```{code-cell} ipython3 :tags: [raises-exception] state_with_invalid_id = BenchState.from_yaml(""" version: '0.4' instruments: simulated-motors: loader: phileas-mock_motors-phileas interfaces: - 2d-motors configuration: {} id: mock-motors-driver:2 state: x: 0.0 y: 0.0 """) factory.restore_bench_state(state_with_invalid_id) ``` ## Replication of the software environment of an experiment Given properly implemented loaders, with the same bench and experiment configuration files, Phileas can guarantee the proper replication of an experiment. However, the user is in charge of the replication of the software environment of the experiment. For this, different tools exist, such as `conda` or `poetry`. It is recommended that you use them, and store their lock files alongside `state.yaml`.