diff --git a/docs/The State Mechanism.ipynb b/docs/The State Mechanism.ipynb
new file mode 100644
index 00000000..d3393cc6
--- /dev/null
+++ b/docs/The State Mechanism.ipynb
@@ -0,0 +1,618 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "# The `State` mechanism"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "A `State` is an object representing data from an experiment, like the conditions, observed experiment data and models. \n",
+ "In the AutoRA framework, experimentalists, experiment runners and theorists are functions which \n",
+ "- operate on `States` and \n",
+ "- return `States`.\n",
+ "\n",
+ "The `autora.state` submodule provides classes and functions to help build these functions. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Core Principle: every procedure accepts a `State` and returns a `State`\n",
+ "\n",
+ "The AutoRA `State` mechanism is an implementation of the functional programming paradigm. It distinguishes between:\n",
+ "- Data – stored as an immutable `State`\n",
+ "- Procedures – functions which act on `State` objects to add new data and return a new `State`.\n",
+ "\n",
+ "Procedures generate data. Some common procedures which appear in AutoRA experiments, and the data they produce are:\n",
+ "\n",
+ "| Procedure | Data |\n",
+ "|-------------------|-----------------|\n",
+ "| Experimentalist | Conditions |\n",
+ "| Experiment Runner | Experiment Data |\n",
+ "| Theorist | Model |\n",
+ "\n",
+ "The data produced by each procedure $f$ can be seen as additions to the existing data. Each procedure $f$:\n",
+ "- Takes in existing Data in a `State` $S$\n",
+ "- Adds new data $\\Delta S$\n",
+ "- Returns an updated `State` $S^\\prime$ \n",
+ "\n",
+ "$$\n",
+ "\\begin{aligned}\n",
+ "f(S) &= S + \\Delta S \\\\\n",
+ " &= S^\\prime\n",
+ "\\end{aligned}\n",
+ "$$\n",
+ "\n",
+ "AutoRA includes:\n",
+ "- Classes to represent the Data $S$ – the `State` object (and the derived `StandardState` – a pre-defined version \n",
+ "with the common fields needed for cyclical experiments) \n",
+ "- Functions to make it easier to write procedures of the form $f(S) = S^\\prime$"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from dataclasses import dataclass, field\n",
+ "\n",
+ "import numpy as np\n",
+ "import pandas as pd\n",
+ "import autora.state\n",
+ "from autora.variable import VariableCollection, Variable"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## `State` objects\n",
+ "\n",
+ "`State` objects contain metadata describing an experiment, and the data gathered during an experiment. Any `State` \n",
+ "object used in an AutoRA cycle will be a subclass of the `autora.state.State`, with the necessary fields specified. \n",
+ "(The `autora.state.StandardState` provides some sensible defaults.)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "@dataclass(frozen=True)\n",
+ "class BasicState(autora.state.State):\n",
+ " data: pd.DataFrame = field(default_factory=pd.DataFrame, metadata={\"delta\": \"extend\"})\n",
+ " \n",
+ "s = BasicState()"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Because it is a python dataclass, the `State` fields can be accessed using attribute notation, for example:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "
\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ "Empty DataFrame\n",
+ "Columns: []\n",
+ "Index: []"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "s.data # an empty DataFrame with a column \"x\""
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "`State` objects can be updated by adding `Delta` objects. A `Delta` represents new data, and is combined with the \n",
+ "existing data in the `State` object. The `State` itself is immutable by design, so adding a `Delta` to it creates a new \n",
+ "`State`."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "BasicState(data= x y\n",
+ "0 1 1)"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "s + autora.state.Delta(data=pd.DataFrame({\"x\":[1], \"y\":[1]}))"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "When carrying out this \"addition\", `s`: \n",
+ "- inspects the `Delta` it has been passed and finds any field names matching fields on `s`, in this case \n",
+ "`data`.\n",
+ "- For each matching field it combines the data in a way determined by the field's metadata. The key options are:\n",
+ " - \"replace\" means that the data in the `Delta` object completely replace the data in the `State`,\n",
+ " - \"extend\" means that the data in the `Delta` object are combined – for pandas DataFrames this means that the new\n",
+ " data are concatenated to the bottom of the existing DataFrame.\n",
+ " \n",
+ " For full details on which options are available, see the documentation for the `autora.state` module. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " x | \n",
+ " y | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 1 | \n",
+ " 1 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 2 | \n",
+ " 2 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " x y\n",
+ "0 1 1\n",
+ "1 2 2"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "(s + \n",
+ " autora.state.Delta(data=pd.DataFrame({\"x\":[1], \"y\":[1]})) + \n",
+ " autora.state.Delta(data=pd.DataFrame({\"x\":[2], \"y\":[2]}))\n",
+ " ).data # Access just the experiment_data on the updated State"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### `StandardState`\n",
+ "\n",
+ "For typical AutoRA experiments, you can use the `autora.state.StandardState` object, which has fields for variables, \n",
+ "conditions, experiment data and models. You can initialize a `StandardState` object like this:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "s_0 = autora.state.StandardState(\n",
+ " variables=VariableCollection(\n",
+ " independent_variables=[Variable(\"x\", value_range=(-10, 10))],\n",
+ " dependent_variables=[Variable(\"y\")]\n",
+ " ),\n",
+ " conditions=pd.DataFrame({\"x\":[]}),\n",
+ " experiment_data=pd.DataFrame({\"x\":[], \"y\":[]}),\n",
+ " models=[]\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "## Making a function of the correct form\n",
+ "\n",
+ "There are several equivalent ways to make a function of the form $f(S) = S^\\prime$. These are (from \n",
+ "simplest but most restrictive, to most complex but with the greatest flexibility):\n",
+ "- Use the `autora.state.on_state` decorator\n",
+ "- Modify `generate_conditions` to accept a `StandardState` and update this with a `Delta`\n",
+ "\n",
+ "There are also special cases, like the `autora.state.estimator_on_state` wrapper for `scikit-learn` estimators. \n",
+ "\n",
+ "Say you have a function to generate new experimental conditions, given some variables. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "def generate_conditions(variables, num_samples=5, random_state=42):\n",
+ " rng = np.random.default_rng(random_state) # Initialize a random number generator\n",
+ " conditions = pd.DataFrame() # Create a DataFrame to hold the results \n",
+ " for iv in variables.independent_variables: # Loop through the independent variables\n",
+ " c = rng.uniform(*iv.value_range, size=num_samples) # - Generate a uniform sample from the range\n",
+ " conditions[iv.name] = c # - Save the new values to the DataFrame\n",
+ " return conditions"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "We'll look at each of the ways you can make this into a function of the required form. "
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Use the `autora.state.on_state` decorator\n",
+ "\n",
+ "`autora.state.on_state` is a wrapper for functions which allows them to accept `State` objects as the first argument.\n",
+ "\n",
+ "The most concise way to use it is as a decorator on the function where it is defined. You can specify how the \n",
+ "returned values should be mapped to fields on the `State` using the `@autora.state.on_state(output=...)` argument."
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "StandardState(variables=VariableCollection(independent_variables=[Variable(name='x', value_range=(-10, 10), allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], dependent_variables=[Variable(name='y', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], covariates=[]), conditions= x\n",
+ "0 5.479121\n",
+ "1 -1.222431\n",
+ "2 7.171958\n",
+ "3 3.947361\n",
+ "4 -8.116453, experiment_data=Empty DataFrame\n",
+ "Columns: [x, y]\n",
+ "Index: [], models=[])"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "@autora.state.on_state(output=[\"conditions\"])\n",
+ "def generate_conditions(variables, num_samples=5, random_state=42):\n",
+ " rng = np.random.default_rng(random_state) # Initialize a random number generator\n",
+ " conditions = pd.DataFrame() # Create a DataFrame to hold the results \n",
+ " for iv in variables.independent_variables: # Loop through the independent variables\n",
+ " c = rng.uniform(*iv.value_range, size=num_samples) # - Generate a uniform sample from the range\n",
+ " conditions[iv.name] = c # - Save the new values to the DataFrame\n",
+ " return conditions\n",
+ "\n",
+ "# Example\n",
+ "generate_conditions(s_0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Fully equivalently, you can modify `generate_conditions` to return a Delta of values with the appropriate field \n",
+ "names from `State`: "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "StandardState(variables=VariableCollection(independent_variables=[Variable(name='x', value_range=(-10, 10), allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], dependent_variables=[Variable(name='y', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], covariates=[]), conditions= x\n",
+ "0 5.479121\n",
+ "1 -1.222431\n",
+ "2 7.171958\n",
+ "3 3.947361\n",
+ "4 -8.116453, experiment_data=Empty DataFrame\n",
+ "Columns: [x, y]\n",
+ "Index: [], models=[])"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "@autora.state.on_state\n",
+ "def generate_conditions(variables, num_samples=5, random_state=42):\n",
+ " rng = np.random.default_rng(random_state) # Initialize a random number generator\n",
+ " conditions = pd.DataFrame() # Create a DataFrame to hold the results \n",
+ " for iv in variables.independent_variables: # Loop through the independent variables\n",
+ " c = rng.uniform(*iv.value_range, size=num_samples) # - Generate a uniform sample from the range\n",
+ " conditions[iv.name] = c # - Save the new values to the DataFrame\n",
+ " return autora.state.Delta(conditions=conditions) # Return a Delta with the appropriate names\n",
+ " # return {\"conditions\": conditions} # Returning a dictionary is equivalent\n",
+ "\n",
+ "# Example\n",
+ "generate_conditions(s_0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "#### Deep dive: `autora.state_on_state`\n",
+ "The decorator notation is equivalent to the following:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "StandardState(variables=VariableCollection(independent_variables=[Variable(name='x', value_range=(-10, 10), allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], dependent_variables=[Variable(name='y', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], covariates=[]), conditions= x\n",
+ "0 1.521127\n",
+ "1 3.362120\n",
+ "2 1.065391\n",
+ "3 -5.844244\n",
+ "4 -6.444732, experiment_data=Empty DataFrame\n",
+ "Columns: [x, y]\n",
+ "Index: [], models=[])"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def generate_conditions_inner(variables, num_samples=5, random_state=42):\n",
+ " rng = np.random.default_rng(random_state) # Initialize a random number generator\n",
+ " result = pd.DataFrame() # Create a DataFrame to hold the results \n",
+ " for iv in variables.independent_variables: # Loop through the independent variables\n",
+ " c = rng.uniform(*iv.value_range, size=num_samples) # - Generate a uniform sample from the range\n",
+ " result[iv.name] = c # - Save the new values to the DataFrame\n",
+ " return result\n",
+ "\n",
+ "generate_conditions = autora.state.on_state(generate_conditions_inner, output=[\"conditions\"])\n",
+ "\n",
+ "# Example\n",
+ "generate_conditions(s_0, random_state=180)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "During the `generate_conditions(s_0, random_state=180)` call, `autora.state.on_state` does the following:\n",
+ "- Inspects the signature of `generate_conditions_inner` to see which variables are required – in this case:\n",
+ " - `variables`, \n",
+ " - `num_samples` and \n",
+ " - `random_state`.\n",
+ "- Looks for fields with those names on `s_0`:\n",
+ " - Finds a field called `variables`.\n",
+ "- Calls `generate_conditions_inner` with those fields as arguments, plus any arguments specified in the \n",
+ "`generate_conditions` call (here just `random_state`)\n",
+ "- Converts the returned value `result` into `Delta(conditions=result)` using the name specified in `output=[\"conditions\"]`\n",
+ "- Returns `s_0 + Delta(conditions=result)`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Modify `generate_conditions` to accept a `StandardState` and update this with a `Delta`"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "Fully equivalently to using the `autora.state.on_state` wrapper, you can construct a function which takes and returns \n",
+ "`State` objects. "
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "StandardState(variables=VariableCollection(independent_variables=[Variable(name='x', value_range=(-10, 10), allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], dependent_variables=[Variable(name='y', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], covariates=[]), conditions= x\n",
+ "0 5.479121\n",
+ "1 -1.222431\n",
+ "2 7.171958\n",
+ "3 3.947361\n",
+ "4 -8.116453, experiment_data=Empty DataFrame\n",
+ "Columns: [x, y]\n",
+ "Index: [], models=[])"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "def generate_conditions(state: autora.state.StandardState, num_samples=5, random_state=42):\n",
+ " rng = np.random.default_rng(random_state) # Initialize a random number generator\n",
+ " conditions = pd.DataFrame() # Create a DataFrame to hold the results \n",
+ " for iv in state.variables.independent_variables: # Loop through the independent variables\n",
+ " c = rng.uniform(*iv.value_range, size=num_samples) # - Generate a uniform sample from the range\n",
+ " conditions[iv.name] = c # - Save the new values to the DataFrame\n",
+ " delta = autora.state.Delta(conditions=conditions) # Construct a new Delta representing the updated data\n",
+ " new_state = state + delta # Construct a new state, \"adding\" the Delta\n",
+ " return new_state\n",
+ "\n",
+ "# Example\n",
+ "generate_conditions(s_0)"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "### Special case: `autora.state.estimator_on_state` for `scikit-learn` estimators\n",
+ "\n",
+ "The \"theorist\" component in an AutoRA cycle is often a `scikit-learn` compatible estimator which implements a curve \n",
+ "fitting function like a linear, logistic or symbolic regression. `scikit-learn` estimators are classes, and they have\n",
+ " a specific wrapper: `autora.state.estimator_on_state`, used as follows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [
+ {
+ "name": "stdout",
+ "output_type": "stream",
+ "text": [
+ "Returned models: [LinearRegression()]\n",
+ "Last model's coefficients: y = [3.49729147] x + [1.99930059]\n"
+ ]
+ }
+ ],
+ "source": [
+ "from sklearn.linear_model import LinearRegression\n",
+ "\n",
+ "\n",
+ "estimator = LinearRegression(fit_intercept=True) # Initialize the regressor with all its parameters\n",
+ "theorist = autora.state.estimator_on_state(estimator) # Wrap the estimator\n",
+ "\n",
+ "\n",
+ "# Example\n",
+ "variables = s_0.variables # Reuse the variables from before \n",
+ "xs = np.linspace(-10, 10, 101) # Make an array of x-values \n",
+ "noise = np.random.default_rng(179).normal(0., 0.5, xs.shape) # Gaussian noise\n",
+ "ys = (3.5 * xs + 2. + noise) # Calculate y = 3.5 x + 2 + noise \n",
+ "\n",
+ "s_1 = autora.state.StandardState( # Initialize the State with those data\n",
+ " variables=variables,\n",
+ " experiment_data=pd.DataFrame({\"x\":xs, \"y\":ys}),\n",
+ ")\n",
+ "s_1_prime = theorist(s_1) # Run the theorist\n",
+ "print(f\"Returned models: \"\n",
+ " f\"{s_1_prime.models}\") \n",
+ "print(f\"Last model's coefficients: \"\n",
+ " f\"y = {s_1_prime.models[-1].coef_[0]} x + {s_1_prime.models[-1].intercept_}\")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "metadata": {},
+ "source": [
+ "During the `theorist(s_1)` call, `autora.state.estimator_on_state` does the following:\n",
+ "- Gets the names of the independent and dependent variables from the `s_1.variables`\n",
+ "- Gathers the values of those variables from `s_1.experiment_data`\n",
+ "- Passes those values to the `LinearRegression().fit(x, y)` method\n",
+ "- Constructs `Delta(models=[LinearRegression()])` with the fitted regressor\n",
+ "- Returns `s_1 + Delta(models=[LinearRegression()])`"
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3 (ipykernel)",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 3
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython3"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 1
+}
diff --git a/docs/Variable.ipynb b/docs/Variable.ipynb
new file mode 100644
index 00000000..c3ff1058
--- /dev/null
+++ b/docs/Variable.ipynb
@@ -0,0 +1,327 @@
+{
+ "cells": [
+ {
+ "cell_type": "markdown",
+ "id": "6f464ab4d943192c",
+ "metadata": {},
+ "source": [
+ "# `autora.variable`: `Variable` and `VariableCollection`\n",
+ "\n",
+ "`autora.variable.Variable` represents an experimental variable: \n",
+ "- an independent variable, or\n",
+ "- dependent variable.\n",
+ "\n",
+ "They can be initialized as follows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "c2bfbd97b0a14547",
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "from autora.variable import Variable\n",
+ "\n",
+ "x1 = Variable(\n",
+ " name=\"x1\",\n",
+ ")\n",
+ "x2 = Variable(\n",
+ " name=\"x2\",\n",
+ ")\n",
+ "y = Variable(\n",
+ " name=\"y\",\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3d195cbb145dcd58",
+ "metadata": {},
+ "source": [
+ "A group of `Variables` representing the domain of an experiment is a `autora.variable.VariableCollection`. \n",
+ "\n",
+ "They can be initialized as follows:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "8f1dce3b50b7984c",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/plain": [
+ "VariableCollection(independent_variables=[Variable(name='x1', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False), Variable(name='x2', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], dependent_variables=[Variable(name='y', value_range=None, allowed_values=None, units='', type=, variable_label='', rescale=1, is_covariate=False)], covariates=[])"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from autora.variable import VariableCollection\n",
+ "\n",
+ "VariableCollection(\n",
+ " independent_variables=[x1, x2],\n",
+ " dependent_variables=[y]\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "80e85b4c6997a5fe",
+ "metadata": {},
+ "source": [
+ "For the full list of arguments, see the documentation in the `autora.variable` submodule.\n",
+ "\n",
+ "Some functions included in AutoRA use specific values stored on the Variable objects. For instance, the \n",
+ "`autora.experimentalist.grid.pool` uses the `allowed_values` field to create a grid of conditions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "6eb32ff49345119e",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " x1 | \n",
+ " x2 | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " -1 | \n",
+ " 11 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " -1 | \n",
+ " 12 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " -1 | \n",
+ " 13 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " -2 | \n",
+ " 11 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " -2 | \n",
+ " 12 | \n",
+ "
\n",
+ " \n",
+ " 5 | \n",
+ " -2 | \n",
+ " 13 | \n",
+ "
\n",
+ " \n",
+ " 6 | \n",
+ " -3 | \n",
+ " 11 | \n",
+ "
\n",
+ " \n",
+ " 7 | \n",
+ " -3 | \n",
+ " 12 | \n",
+ "
\n",
+ " \n",
+ " 8 | \n",
+ " -3 | \n",
+ " 13 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " x1 x2\n",
+ "0 -1 11\n",
+ "1 -1 12\n",
+ "2 -1 13\n",
+ "3 -2 11\n",
+ "4 -2 12\n",
+ "5 -2 13\n",
+ "6 -3 11\n",
+ "7 -3 12\n",
+ "8 -3 13"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from autora.experimentalist.grid import grid_pool\n",
+ "\n",
+ "grid_pool(\n",
+ " VariableCollection(independent_variables=[\n",
+ " Variable(name=\"x1\", allowed_values=[-1, -2, -3]),\n",
+ " Variable(name=\"x2\", allowed_values=[11, 12, 13])\n",
+ " ])\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "6f3f12554ba12ad",
+ "metadata": {},
+ "source": [
+ "The `autora.experimentalist.random.pool` uses the `value_range` field to sample conditions:"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "id": "f890f05dd5c601ab",
+ "metadata": {},
+ "outputs": [
+ {
+ "data": {
+ "text/html": [
+ "\n",
+ "\n",
+ "
\n",
+ " \n",
+ " \n",
+ " | \n",
+ " x1 | \n",
+ " x2 | \n",
+ "
\n",
+ " \n",
+ " \n",
+ " \n",
+ " 0 | \n",
+ " 0.456338 | \n",
+ " 101.527294 | \n",
+ "
\n",
+ " \n",
+ " 1 | \n",
+ " 1.008636 | \n",
+ " 101.297280 | \n",
+ "
\n",
+ " \n",
+ " 2 | \n",
+ " 0.319617 | \n",
+ " 101.962166 | \n",
+ "
\n",
+ " \n",
+ " 3 | \n",
+ " -1.753273 | \n",
+ " 101.859696 | \n",
+ "
\n",
+ " \n",
+ " 4 | \n",
+ " -1.933420 | \n",
+ " 101.201565 | \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "text/plain": [
+ " x1 x2\n",
+ "0 0.456338 101.527294\n",
+ "1 1.008636 101.297280\n",
+ "2 0.319617 101.962166\n",
+ "3 -1.753273 101.859696\n",
+ "4 -1.933420 101.201565"
+ ]
+ },
+ "execution_count": null,
+ "metadata": {},
+ "output_type": "execute_result"
+ }
+ ],
+ "source": [
+ "from autora.experimentalist.random import random_pool\n",
+ "\n",
+ "random_pool(\n",
+ " VariableCollection(independent_variables=[\n",
+ " Variable(name=\"x1\", value_range=(-3, 3)),\n",
+ " Variable(name=\"x2\", value_range=(101, 102))\n",
+ " ]), \n",
+ " random_state=180\n",
+ ")"
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "f4ab2b25903f40a7",
+ "metadata": {},
+ "source": [
+ "The `autora.state.estimator_from_state` function uses the `names` of the variables to pass the correct columns to a \n",
+ "`scikit-learn` compatible estimator for curve fitting."
+ ]
+ },
+ {
+ "cell_type": "markdown",
+ "id": "3f4d28f5979fe9cb",
+ "metadata": {},
+ "source": [
+ "Check the documentation for any functions you are using to determine whether you need to include specific metadata."
+ ]
+ }
+ ],
+ "metadata": {
+ "kernelspec": {
+ "display_name": "Python 3",
+ "language": "python",
+ "name": "python3"
+ },
+ "language_info": {
+ "codemirror_mode": {
+ "name": "ipython",
+ "version": 2
+ },
+ "file_extension": ".py",
+ "mimetype": "text/x-python",
+ "name": "python",
+ "nbconvert_exporter": "python",
+ "pygments_lexer": "ipython2"
+ }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 5
+}
diff --git a/docs/index.md b/docs/index.md
index 0c6266a3..3602a90e 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,3 +1,14 @@
# Core Functionality
-AutoRA includes core functionality for running AutoRA experiments.
+AutoRA includes core functionality for running AutoRA experiments organized into these submodules:
+
+- `autora.state`, which underpins the unified `State` interface for writing experimentalists, experiment runners and
+ theorists
+- `autora.serializer`, utilities for saving and loading `States`
+- `autora.workflow`, command line tools for running experimentalists, experiment runners and theorists
+- `autora.variable`, for representing experimental metadata describing the type and domain of variables
+- `autora.utils`, utilities and helper functions not linked to any specific core functionality
+
+It also provides some basic experimentalists in the `autora.experimentalist` submodule. However, most
+genuinely useful experimentalists and theorists are provided as optional dependencies to the `autora` package.
+
diff --git a/mkdocs.yml b/mkdocs.yml
index 5aee1786..71011cd2 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -12,6 +12,8 @@ theme:
- content.code.copy
nav:
- Home: 'index.md'
+- State: 'The State Mechanism.ipynb'
+- Variable: 'Variable.ipynb'
- Experimentalist Pipeline: 'pipeline/Experimentalist Pipeline Examples.ipynb'
- Experimentalists:
- Pooler: