diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index a1a4bbb03..821fa26d6 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -17,7 +17,7 @@ jobs: uses: actions/checkout@v4 - name: Set up Python 3.12 - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: '3.12' diff --git a/.github/workflows/linting.yml b/.github/workflows/linting.yml index 9827b72c1..c8f761acd 100644 --- a/.github/workflows/linting.yml +++ b/.github/workflows/linting.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - name: Run lint diff --git a/.github/workflows/pypi.yml b/.github/workflows/pypi.yml index 57d65ffdf..e9ca3155b 100644 --- a/.github/workflows/pypi.yml +++ b/.github/workflows/pypi.yml @@ -10,7 +10,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: "3.12" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ca42a42cb..87927a78f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -16,7 +16,7 @@ jobs: steps: - uses: actions/checkout@v4 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v4 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python-version }} - uses: actions/cache@v2 diff --git a/docs/source/conf.py b/docs/source/conf.py index c912b295c..5549e3853 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -44,6 +44,7 @@ "matplotlib.sphinxext.plot_directive", "numpydoc", "myst_nb", + "sphinx_design", "sphinxcontrib.bibtex", "sphinx_togglebutton", "sphinx_exercise", diff --git a/docs/source/learn.md b/docs/source/learn.md index a18daa681..627eeb774 100644 --- a/docs/source/learn.md +++ b/docs/source/learn.md @@ -26,6 +26,7 @@ notebooks/1.3-Continuous_HGF.ipynb notebooks/2-Using_custom_response_functions.ipynb notebooks/3-Multilevel_HGF.ipynb notebooks/4-Parameter_recovery.ipynb +notebooks/5-Non_linear_value_coupling ``` ```{toctree} @@ -67,7 +68,7 @@ How the generative model of the Hierarchical Gaussian filter can be turned into :::{grid-item-card} Creating and manipulating networks of probabilistic nodes :link: probabilistic_networks :link-type: ref -:img-top: ./images/graph_networks.svg +:img-top: ./images/graph_network.svg How to create and manipulate a network of probabilistic nodes for reinforcement learning? Working at the intersection of graphs, neural networks and probabilistic frameworks. ::: diff --git a/docs/source/notebooks/0.1-Theory.ipynb b/docs/source/notebooks/0.1-Theory.ipynb index 6c5bcbff0..5fea8112b 100644 --- a/docs/source/notebooks/0.1-Theory.ipynb +++ b/docs/source/notebooks/0.1-Theory.ipynb @@ -34,10 +34,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " ! pip install watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install watermark" ] }, { diff --git a/docs/source/notebooks/0.2-Creating_networks.ipynb b/docs/source/notebooks/0.2-Creating_networks.ipynb index 666790128..743923905 100644 --- a/docs/source/notebooks/0.2-Creating_networks.ipynb +++ b/docs/source/notebooks/0.2-Creating_networks.ipynb @@ -42,11 +42,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -131,7 +132,7 @@ "\n", "This list describes the sequence of function-to-nodes instructions that are executed during the inference and update processes.\n", "\n", - "![graph_networks](../images/graph_networks.svg)\n", + "![graph_network](../images/graph_network.svg)\n", "\n", "```{tip} Compatibility with JAX transformations\n", "One of the advantages of reasoning this way is that it dissociates variables that are transparent to the JAX framework and can be expressed as \"PyTress\" from variables that should be filtered before transformations. The variable `attributes` ($\\theta$) is typically expressed as a PyTree while the other variables that contain parametrized functions are filtered. See [the documattion](https://jax.readthedocs.io/en/latest/notebooks/thinking_in_jax.html#jit-mechanics-tracing-and-static-variables) for further details on JAX transformations.\n", diff --git a/docs/source/notebooks/0.3-Generalised_filtering.ipynb b/docs/source/notebooks/0.3-Generalised_filtering.ipynb index 21a805b0c..2fb15c17e 100644 --- a/docs/source/notebooks/0.3-Generalised_filtering.ipynb +++ b/docs/source/notebooks/0.3-Generalised_filtering.ipynb @@ -17,6 +17,29 @@ "[![Open In Colab](https://colab.research.google.com/assets/colab-badge.svg)](https://colab.research.google.com/github/ilabcode/pyhgf/blob/master/docs/source/notebooks/0.3-Generalised_filtering.ipynb)" ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "31b80846", + "metadata": { + "editable": true, + "slideshow": { + "slide_type": "" + }, + "tags": [ + "hide-cell" + ] + }, + "outputs": [], + "source": [ + "import sys\n", + "from IPython.utils import io\n", + "if 'google.colab' in sys.modules:\n", + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" + ] + }, { "cell_type": "code", "execution_count": 1, diff --git a/docs/source/notebooks/1.1-Binary_HGF.ipynb b/docs/source/notebooks/1.1-Binary_HGF.ipynb index 13c70e55c..83d2814b2 100644 --- a/docs/source/notebooks/1.1-Binary_HGF.ipynb +++ b/docs/source/notebooks/1.1-Binary_HGF.ipynb @@ -38,11 +38,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -870,7 +871,7 @@ ], "source": [ "with two_levels_binary_hgf:\n", - " two_level_hgf_idata = pm.sample(chains=4, cores=1)" + " two_level_hgf_idata = pm.sample(chains=2, cores=1)" ] }, { @@ -1299,7 +1300,7 @@ ], "source": [ "with three_levels_binary_hgf:\n", - " three_level_hgf_idata = pm.sample(chains=4, cores=1)" + " three_level_hgf_idata = pm.sample(chains=2, cores=1)" ] }, { diff --git a/docs/source/notebooks/1.2-Categorical_HGF.ipynb b/docs/source/notebooks/1.2-Categorical_HGF.ipynb index 7375600d1..acb11ad7c 100644 --- a/docs/source/notebooks/1.2-Categorical_HGF.ipynb +++ b/docs/source/notebooks/1.2-Categorical_HGF.ipynb @@ -36,11 +36,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { diff --git a/docs/source/notebooks/1.3-Continuous_HGF.ipynb b/docs/source/notebooks/1.3-Continuous_HGF.ipynb index 4b7b4d153..dd7935c75 100644 --- a/docs/source/notebooks/1.3-Continuous_HGF.ipynb +++ b/docs/source/notebooks/1.3-Continuous_HGF.ipynb @@ -44,10 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -959,7 +961,7 @@ ], "source": [ "with two_level_hgf:\n", - " two_level_hgf_idata = pm.sample(chains=4, cores=1)" + " two_level_hgf_idata = pm.sample(chains=2, cores=1)" ] }, { @@ -1353,7 +1355,7 @@ ], "source": [ "with three_level_hgf:\n", - " three_level_hgf_idata = pm.sample(chains=4, cores=1)" + " three_level_hgf_idata = pm.sample(chains=2, cores=1)" ] }, { diff --git a/docs/source/notebooks/2-Using_custom_response_functions.ipynb b/docs/source/notebooks/2-Using_custom_response_functions.ipynb index 7baa7caf9..a004366b7 100644 --- a/docs/source/notebooks/2-Using_custom_response_functions.ipynb +++ b/docs/source/notebooks/2-Using_custom_response_functions.ipynb @@ -44,10 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -907,7 +909,7 @@ ], "source": [ "with sigmoid_hgf:\n", - " sigmoid_hgf_idata = pm.sample(chains=4, cores=1)" + " sigmoid_hgf_idata = pm.sample(chains=2, cores=1)" ] }, { diff --git a/docs/source/notebooks/3-Multilevel_HGF.ipynb b/docs/source/notebooks/3-Multilevel_HGF.ipynb index f892e3761..e3623b03b 100644 --- a/docs/source/notebooks/3-Multilevel_HGF.ipynb +++ b/docs/source/notebooks/3-Multilevel_HGF.ipynb @@ -50,11 +50,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -81,7 +82,6 @@ ], "source": [ "import arviz as az\n", - "import jax.numpy as jnp\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", "import pymc as pm\n", diff --git a/docs/source/notebooks/4-Parameter_recovery.ipynb b/docs/source/notebooks/4-Parameter_recovery.ipynb index 0d71eabd3..fc2fb6715 100644 --- a/docs/source/notebooks/4-Parameter_recovery.ipynb +++ b/docs/source/notebooks/4-Parameter_recovery.ipynb @@ -44,11 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { diff --git a/docs/source/notebooks/5-Non_linear_value_coupling.ipynb b/docs/source/notebooks/5-Non_linear_value_coupling.ipynb index 56e636272..0d1425f1f 100644 --- a/docs/source/notebooks/5-Non_linear_value_coupling.ipynb +++ b/docs/source/notebooks/5-Non_linear_value_coupling.ipynb @@ -32,11 +32,12 @@ "metadata": {}, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -56,7 +57,7 @@ "import jax.numpy as jnp\n", "import matplotlib.pyplot as plt\n", "import numpy as np\n", - "from jax import jit, lax\n", + "from jax import lax\n", "from pyhgf.model import Network\n", "import seaborn as sns\n", "\n", diff --git a/docs/source/notebooks/Example_1_Heart_rate_variability.ipynb b/docs/source/notebooks/Example_1_Heart_rate_variability.ipynb index 6a0e9ed4e..15d9160ed 100644 --- a/docs/source/notebooks/Example_1_Heart_rate_variability.ipynb +++ b/docs/source/notebooks/Example_1_Heart_rate_variability.ipynb @@ -44,10 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf systole watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark systole" ] }, { diff --git a/docs/source/notebooks/Example_2_Input_node_volatility_coupling.ipynb b/docs/source/notebooks/Example_2_Input_node_volatility_coupling.ipynb index e375b68a5..a8c8cd6ad 100644 --- a/docs/source/notebooks/Example_2_Input_node_volatility_coupling.ipynb +++ b/docs/source/notebooks/Example_2_Input_node_volatility_coupling.ipynb @@ -44,10 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { diff --git a/docs/source/notebooks/Example_3_Multi_armed_bandit.ipynb b/docs/source/notebooks/Example_3_Multi_armed_bandit.ipynb index efd4c74f1..f4b17c853 100644 --- a/docs/source/notebooks/Example_3_Multi_armed_bandit.ipynb +++ b/docs/source/notebooks/Example_3_Multi_armed_bandit.ipynb @@ -44,11 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " ! pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { diff --git a/docs/source/notebooks/Exercise_1_Introduction_to_the_generalised_hierarchical_gaussian_filter.ipynb b/docs/source/notebooks/Exercise_1_Introduction_to_the_generalised_hierarchical_gaussian_filter.ipynb index 02cd13bc4..2949e3c40 100644 --- a/docs/source/notebooks/Exercise_1_Introduction_to_the_generalised_hierarchical_gaussian_filter.ipynb +++ b/docs/source/notebooks/Exercise_1_Introduction_to_the_generalised_hierarchical_gaussian_filter.ipynb @@ -44,11 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -127,7 +128,7 @@ "where $\\sigma^2$ is the fixed variance of the distribution. You can find more details on this as well as some code to get started with the simulations in the first tutorial on the [PyHGF documentation](https://ilabcode.github.io/pyhgf/notebooks/0.1-Theory.html#).\n", "\n", "```{exercise}\n", - ":label: exercise1\n", + ":label: exercise1.1\n", "\n", "Using the equation above, write a Python code that implements a Gaussian random walk using the following parameters: $\\sigma^2 = 1$ and $x_1^{(0)} = 0$.\n", "```" @@ -237,7 +238,7 @@ }, "source": [ "```{exercise}\n", - ":label: exercise2\n", + ":label: exercise1.2\n", "\n", "Write a Python code that generates values for $x_1$ using the value coupling equation above with $\\mu_1^{0} = 0.0$ and $\\sigma_1 = 1.5$.\n", "```" @@ -305,7 +306,7 @@ }, "source": [ "```{exercise}\n", - ":label: exercise3\n", + ":label: exercise1.3\n", "\n", "- Using the equation above and your previous implementation, write a Python code that generates values for $x_1$ with $\\omega_1 = -6.0$, $\\mu_1^{(0)} = 0.0$,\n", "\n", @@ -346,7 +347,7 @@ "\n", "But this is not the only thing it does. So far we have simulated observations from the generative model, going for the leaves to the root of the network. This describes how we expect the environment to behave, but not how an agent would learn from it. To do so we need to invert this model: we want to provide the observations, and let the network update the nodes accordingly, so the beliefs always reflect the current state of the environment. This process requires propagating precision prediction errors from the root to the leaves of the network, and this is where most of PyHGF's dark magic is most useful.\n", "\n", - "```{figure} https://raw.githubusercontent.com/ilabcode/pyhgf/master/docs/source/images/graph_networks.svg\n", + "```{figure} https://raw.githubusercontent.com/ilabcode/pyhgf/master/docs/source/images/graph_network.svg\n", "---\n", "name: networks\n", "---\n", @@ -526,7 +527,7 @@ }, "source": [ "```{exercise}\n", - ":label: exercise4\n", + ":label: exercise1.4\n", "\n", "Each state node comes with parameters `mean`, `precision` and `tonic_volatility` that can be provided when creating the network. Using the function above, try to change these values. How does this influence the belief trajectories? \n", "\n", @@ -808,7 +809,7 @@ }, "source": [ "```{exercise}\n", - ":label: exercise4\n", + ":label: exercise1.5\n", "\n", "What quantity are we measuring in the code cell above? What does this represent?\n", "```" @@ -826,7 +827,7 @@ }, "source": [ "```{exercise}\n", - ":label: exercise5\n", + ":label: exercise1.6\n", "\n", "- Select a city and download a recording OR use the data frame loaded above.\n", "- Fit a network using one of the variables and estimate the total Gaussian surprise.\n", @@ -847,8 +848,8 @@ "source": [ "# Solutions\n", "\n", - "````{solution} exercise1\n", - ":label: solution-exercise1\n", + "````{solution} exercise1.1\n", + ":label: solution-exercise1.1\n", "\n", "We can simulate a simple Gaussian Random Walk in Python either using a for loop and a list:\n", "\n", @@ -902,8 +903,8 @@ "tags": [] }, "source": [ - "````{solution} exercise2\n", - ":label: solution-exercise2\n", + "````{solution} exercise1.2\n", + ":label: solution-exercise1.2\n", "\n", "We can simulate values from $x_1$ using a for loop:\n", "\n", @@ -932,8 +933,8 @@ "tags": [] }, "source": [ - "````{solution} exercise3\n", - ":label: solution-exercise3\n", + "````{solution} exercise1.3\n", + ":label: solution-exercise1.3\n", "\n", "We can simulate values from $x_1$ using a for loop:\n", "\n", @@ -962,8 +963,8 @@ "tags": [] }, "source": [ - "````{solution} exercise4\n", - ":label: solution-exercise4\n", + "````{solution} exercise1.4\n", + ":label: solution-exercise1.4\n", "\n", "The code below will change mean, precision and tonic volatility at the first level. Here we use Python's OOP features to express model creation, input data and plotting by chaining method call.\n", "\n", @@ -994,8 +995,8 @@ "tags": [] }, "source": [ - "````{solution} exercise4\n", - ":label: solution-exercise4\n", + "````{solution} exercise1.5\n", + ":label: solution-exercise1.5\n", "\n", "This method return the Gaussian surprise at each time point, which are then summed. The sum of the Gaussian surprise reflect the performances of the model at predicting the next value, larger values pointing to a model more surprise by its inputs, therefore poor performances. The surprise is define as $s = -log(p)$, this is thus the negative of the log probability function. Log probability functions are commonly used by sampling algorithm, it is thus straigthforwars to sample a model parameters when this function is available. There are an infinity of response possible functions - just like there is an infinity of possible networks - for more details on how to use tem, you can refer to the [tutorial on custom response models](https://ilabcode.github.io/pyhgf/notebooks/2-Using_custom_response_functions.html).\n", "\n", diff --git a/docs/source/notebooks/Exercise_2_Bayesian_reinforcement_learning.ipynb b/docs/source/notebooks/Exercise_2_Bayesian_reinforcement_learning.ipynb index 8abc4eb80..af0d6823e 100644 --- a/docs/source/notebooks/Exercise_2_Bayesian_reinforcement_learning.ipynb +++ b/docs/source/notebooks/Exercise_2_Bayesian_reinforcement_learning.ipynb @@ -44,11 +44,12 @@ }, "outputs": [], "source": [ - "%%capture\n", "import sys\n", - "\n", + "from IPython.utils import io\n", "if 'google.colab' in sys.modules:\n", - " !pip install pyhgf watermark" + "\n", + " with io.capture_output() as captured:\n", + " ! pip install pyhgf watermark" ] }, { @@ -135,7 +136,7 @@ "metadata": {}, "source": [ "```{exercise}\n", - ":label: exercise1\n", + ":label: exercise2.1\n", "\n", "Create a two-level binary Hierarchical Gaussian Filter using the `Network` class and plot the network structure.\n", "\n", @@ -363,7 +364,7 @@ }, "source": [ "```{exercise}\n", - ":label: exercise2\n", + ":label: exercise2.2\n", "\n", "- Using the examples above, can you diagnose the performances of the agent?\n", "- What could make it better? Can you try to change the parameters and get an agent with better performances (i.e. minimizing the surprise)?\n", @@ -646,7 +647,7 @@ ], "source": [ "with two_levels_binary_hgf:\n", - " two_level_hgf_idata = pm.sample(chains=4, cores=1)" + " two_level_hgf_idata = pm.sample(chains=2, cores=1)" ] }, { @@ -946,7 +947,7 @@ "\n", " # sample from this model\n", " biased_random_idata = pm.sample(\n", - " chains=4, cores=1, idata_kwargs={\"log_likelihood\": True}\n", + " chains=2, cores=1, idata_kwargs={\"log_likelihood\": True}\n", " )" ] }, @@ -1121,7 +1122,7 @@ ], "source": [ "with rw_model:\n", - " rw_idata = pm.sample(chains=4, cores=4, idata_kwargs={\"log_likelihood\": True})" + " rw_idata = pm.sample(chains=2, cores=4, idata_kwargs={\"log_likelihood\": True})" ] }, { @@ -1455,7 +1456,7 @@ " pm.Deterministic('pointwise_loglikelihood', logp_pointwise(tonic_volatility_2))\n", "\n", " # sample the model\n", - " two_levels_idata = pm.sample(chains=4, cores=1)" + " two_levels_idata = pm.sample(chains=2, cores=1)" ] }, { @@ -1789,7 +1790,7 @@ " # save the pointwise log probabilities for model comparison\n", " pm.Deterministic('pointwise_loglikelihood', logp_pointwise(tonic_volatility_2))\n", "\n", - " three_levels_idata = pm.sample(chains=4, cores=1)" + " three_levels_idata = pm.sample(chains=2, cores=1)" ] }, { @@ -2227,8 +2228,8 @@ "tags": [] }, "source": [ - "````{solution} exercise1\n", - ":label: solution-exercise1\n", + "````{solution} exercise2.1\n", + ":label: solution-exercise2.1\n", "\n", "Here's how we can create the network.\n", "\n", @@ -2255,8 +2256,8 @@ "id": "6fbdf386-16a3-44af-b015-60cd887fa997", "metadata": {}, "source": [ - "````{solution} exercise2\n", - ":label: solution-exercise2\n", + "````{solution} exercise2.2\n", + ":label: solution-exercise2.2\n", "\n", "- Looking at the trajectories, both at the first and the second level (remember that the binary state node and the continuous state node are encoding the sa values using different scales), we can see that the agent is rather slow at changing its mind when the observed associations between the stimuli and the outcomes are switching. \n", "\n",