OSCR

pyhgf: A neural network library for predictive coding.

Code ↔ Paper

4 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 4 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § 2 Design and implementation › 2.2 Optimisation and inference ↔ dynamax/ssm.py, lines 37–89 · score 0.63 · gradient descent, state space, maximizing, easily, likelihood, smoothly
  2. [2] § 2 Design and implementation › 2.1 Computational framework ↔ pyhgf/updates/posterior/continuous/posterior_update_mean_continuous_node.py, lines 10–94 · score 0.54 · field approximations, parent nodes, adjacent, prediction error, linear, child
  3. [3] § 2 Design and implementation › 2.2 Optimisation and inference ↔ pyhgf/model/network.py, lines 80–149 · score 0.51 · HGF toolbox, NUTS, neural network, space, backend, optimisation
  4. [4] § 2 Design and implementation › 2.1 Computational framework ↔ pyhgf/utils/beliefs_propagation.py, the whole file · a weak match · score 0.50 · belief propagation, Update sequences, posterior updates, roots, transformations, prediction errors

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

Python · 506 lines · 20 KB · MIT · 1 match

  1. """
  2. Base classes for state space models (SSMs).
  3. """
  4. import jax.numpy as jnp
  5. import jax.random as jr
  6. import optax
  7. from abc import ABC
  8. from abc import abstractmethod
  9. from fastprogress.fastprogress import progress_bar
  10. from functools import partial
  11. from jax import jit, lax, vmap
  12. from jax.tree_util import tree_map
  13. from jaxtyping import Array, Float, Real
  14. from numbers import Integral
  15. from tensorflow_probability.substrates.jax import distributions as tfd
  16. from typing import Optional, Union, Tuple, Any, runtime_checkable
  17. from typing_extensions import Protocol
  18. from dynamax.parameters import to_unconstrained, from_unconstrained
  19. from dynamax.parameters import ParameterSet, PropertySet
  20. from dynamax.types import PRNGKeyT, Scalar
  21. from dynamax.utils.optimize import run_sgd
  22. from dynamax.utils.utils import ensure_array_has_batch_dim
  23. @runtime_checkable
  24. class Posterior(Protocol):
  25. """A :class:`NamedTuple` with parameters stored as :class:`jax.DeviceArray` in the leaf nodes."""
  26. pass
  27. @runtime_checkable
  28. class SuffStatsSSM(Protocol):
  29. """A :class:`NamedTuple` with sufficient statics stored as :class:`jax.DeviceArray` in the leaf nodes."""
  30. pass
  31. class SSM(ABC):
  32. r"""A base class for state space models. Such models consist of parameters, which
  33. we may learn, as well as hyperparameters, which specify static properties of the
  34. model. This base class allows parameters to be indicated a standardized way
  35. so that they can easily be converted to/from unconstrained form for optimization.
  36. **Abstract Methods**
  37. Models that inherit from `SSM` must implement a few key functions and properties:
  38. * :meth:`initial_distribution` returns the distribution over the initial state given parameters
  39. * :meth:`transition_distribution` returns the conditional distribution over the next state given the current state and parameters
  40. * :meth:`emission_distribution` returns the conditional distribution over the emission given the current state and parameters
  41. * :meth:`log_prior` (optional) returns the log prior probability of the parameters
  42. * :attr:`emission_shape` returns a tuple specification of the emission shape
  43. * :attr:`inputs_shape` returns a tuple specification of the input shape, or `None` if there are no inputs.
  44. The shape properties are required for properly handling batches of data.
  45. **Sampling and Computing Log Probabilities**
  46. Once these have been implemented, subclasses will inherit the ability to sample
  47. and compute log joint probabilities from the base class functions:
  48. * :meth:`sample` draws samples of the states and emissions for given parameters
  49. * :meth:`log_prob` computes the log joint probability of the states and emissions for given parameters
  50. **Inference**
  51. Many subclasses of SSMs expose basic functions for performing state inference.
  52. * :meth:`marginal_log_prob` computes the marginal log probability of the emissions, summing over latent states
  53. * :meth:`filter` computes the filtered posteriors
  54. * :meth:`smoother` computes the smoothed posteriors
  55. **Learning**
  56. Likewise, many SSMs will support learning with expectation-maximization (EM) or stochastic gradient descent (SGD).
  57. For expectation-maximization, subclasses must implement the E- and M-steps.
  58. * :meth:`e_step` computes the expected sufficient statistics for a sequence of emissions, given parameters
  59. * :meth:`m_step` finds new parameters that maximize the expected log joint probability
  60. Once these are implemented, the generic SSM class allows to fit the model with EM
  61. * :meth:`fit_em` run EM to find parameters that maximize the likelihood (or posterior) probability.
  62. For SGD, any subclass that implements :meth:`marginal_log_prob` inherits the base class fitting function
  63. * :meth:`fit_sgd` run SGD to minimize the *negative* marginal log probability.
  64. """
  65. @abstractmethod
  66. def initial_distribution(
  67. self,
  68. params: ParameterSet,
  69. inputs: Optional[Float[Array, " input_dim"]]
  70. ) -> tfd.Distribution:
  71. r"""Return an initial distribution over latent states.
  72. Args:
  73. params: model parameters $\theta$
  74. inputs: optional inputs $u_t$
  75. Returns:
  76. distribution over initial latent state, $p(z_1 \mid \theta)$.
  77. """
  78. raise NotImplementedError
  79. @abstractmethod
  80. def transition_distribution(
  81. self,
  82. params: ParameterSet,
  83. state: Float[Array, " state_dim"],
  84. inputs: Optional[Float[Array, " input_dim"]]
  85. ) -> tfd.Distribution:
  86. r"""Return a distribution over next latent state given current state.
  87. Args:
  88. params: model parameters $\theta$
  89. state: current latent state $z_t$
  90. inputs: current inputs $u_t$
  91. Returns:
  92. conditional distribution of next latent state $p(z_{t+1} \mid z_t, u_t, \theta)$.
  93. """
  94. raise NotImplementedError
  95. @abstractmethod
  96. def emission_distribution(
  97. self,
  98. params: ParameterSet,
  99. state: Float[Array, " state_dim"],
  100. inputs: Optional[Float[Array, " input_dim"]]=None
  101. ) -> tfd.Distribution:
  102. r"""Return a distribution over emissions given current state.
  103. Args:
  104. params: model parameters $\theta$
  105. state: current latent state $z_t$
  106. inputs: current inputs $u_t$
  107. Returns:
  108. conditional distribution of current emission $p(y_t \mid z_t, u_t, \theta)$
  109. """
  110. raise NotImplementedError
  111. def log_prior(
  112. self,
  113. params: ParameterSet
  114. ) -> Scalar:
  115. r"""Return the log prior probability of any model parameters.
  116. Returns:
  117. lp (Scalar): log prior probability.
  118. """
  119. return 0.0
  120. @property
  121. @abstractmethod
  122. def emission_shape(self) -> Tuple[int]:
  123. r"""Return a pytree matching the pytree of tuples specifying the shape of a single time step's emissions.
  124. For example, a `GaussianHMM` with $D$ dimensional emissions would return `(D,)`.
  125. """
  126. raise NotImplementedError
  127. @property
  128. def inputs_shape(self) -> Optional[Tuple[int]]:
  129. r"""Return a pytree matching the pytree of tuples specifying the shape of a single time step's inputs.
  130. """
  131. return None
  132. # All SSMs support sampling
  133. def sample(
  134. self,
  135. params: ParameterSet,
  136. key: PRNGKeyT,
  137. num_timesteps: int,
  138. inputs: Optional[Float[Array, "num_timesteps input_dim"]]=None
  139. ) -> Tuple[Float[Array, "num_timesteps state_dim"],
  140. Float[Array, "num_timesteps emission_dim"]]:
  141. r"""Sample states $z_{1:T}$ and emissions $y_{1:T}$ given parameters $\theta$ and (optionally) inputs $u_{1:T}$.
  142. Args:
  143. params: model parameters $\theta$
  144. key: random number generator
  145. num_timesteps: number of timesteps $T$
  146. inputs: inputs $u_{1:T}$
  147. Returns:
  148. latent states and emissions
  149. """
  150. def _step(prev_state, args):
  151. """Sample the next state and emission given the previous state and input."""
  152. key, inpt = args
  153. key1, key2 = jr.split(key, 2)
  154. state = self.transition_distribution(params, prev_state, inpt).sample(seed=key2)
  155. emission = self.emission_distribution(params, state, inpt).sample(seed=key1)
  156. return state, (state, emission)
  157. # Sample the initial state
  158. key1, key2, key = jr.split(key, 3)
  159. initial_input = tree_map(lambda x: x[0], inputs)
  160. initial_state = self.initial_distribution(params, initial_input).sample(seed=key1)
  161. initial_emission = self.emission_distribution(params, initial_state, initial_input).sample(seed=key2)
  162. # Sample the remaining emissions and states
  163. next_keys = jr.split(key, num_timesteps - 1)
  164. next_inputs = tree_map(lambda x: x[1:], inputs)
  165. _, (next_states, next_emissions) = lax.scan(_step, initial_state, (next_keys, next_inputs))
  166. # Concatenate the initial state and emission with the following ones
  167. expand_and_cat = lambda x0, x1T: jnp.concatenate((jnp.expand_dims(x0, 0), x1T))
  168. states = tree_map(expand_and_cat, initial_state, next_states)
  169. emissions = tree_map(expand_and_cat, initial_emission, next_emissions)
  170. return states, emissions
  171. def log_prob(
  172. self,
  173. params: ParameterSet,
  174. states: Float[Array, "num_timesteps state_dim"],
  175. emissions: Float[Array, "num_timesteps emission_dim"],
  176. inputs: Optional[Float[Array, "num_timesteps input_dim"]]=None
  177. ) -> Scalar:
  178. r"""Compute the log joint probability of the states and observations"""
  179. def _step(carry, args):
  180. """Compute the log probability of the next time step."""
  181. lp, prev_state = carry
  182. state, emission, inpt = args
  183. lp += self.transition_distribution(params, prev_state, inpt).log_prob(state)
  184. lp += self.emission_distribution(params, state, inpt).log_prob(emission)
  185. return (lp, state), None
  186. # Compute log prob of initial time step
  187. initial_state = tree_map(lambda x: x[0], states)
  188. initial_emission = tree_map(lambda x: x[0], emissions)
  189. initial_input = tree_map(lambda x: x[0], inputs)
  190. lp = self.initial_distribution(params, initial_input).log_prob(initial_state)
  191. lp += self.emission_distribution(params, initial_state, initial_input).log_prob(initial_emission)
  192. # Scan over remaining time steps
  193. next_states = tree_map(lambda x: x[1:], states)
  194. next_emissions = tree_map(lambda x: x[1:], emissions)
  195. next_inputs = tree_map(lambda x: x[1:], inputs)
  196. (lp, _), _ = lax.scan(_step, (lp, initial_state), (next_states, next_emissions, next_inputs))
  197. return lp
  198. # Some SSMs will implement these inference functions.
  199. def marginal_log_prob(
  200. self,
  201. params: ParameterSet,
  202. emissions: Float[Array, "ntime emission_dim"],
  203. inputs: Optional[Float[Array, "ntime input_dim"]]=None
  204. ) -> Scalar:
  205. r"""Compute log marginal likelihood of observations, $\log \sum_{z_{1:T}} p(y_{1:T}, z_{1:T} \mid \theta)$.
  206. Args:
  207. params: model parameters $\theta$
  208. state: current latent state $z_t$
  209. inputs: current inputs $u_t$
  210. Returns:
  211. marginal log probability
  212. """
  213. raise NotImplementedError
  214. def filter(
  215. self,
  216. params: ParameterSet,
  217. emissions: Float[Array, "ntime emission_dim"],
  218. inputs: Optional[Float[Array, "ntime input_dim"]]=None
  219. ) -> Posterior:
  220. r"""Compute filtering distributions, $p(z_t \mid y_{1:t}, u_{1:t}, \theta)$ for $t=1,\ldots,T$.
  221. Args:
  222. params: model parameters $\theta$
  223. state: current latent state $z_t$
  224. inputs: current inputs $u_t$
  225. Returns:
  226. filtering distributions
  227. """
  228. raise NotImplementedError
  229. def smoother(
  230. self,
  231. params: ParameterSet,
  232. emissions: Float[Array, "ntime emission_dim"],
  233. inputs: Optional[Float[Array, "ntime input_dim"]]=None
  234. ) -> Posterior:
  235. r"""Compute smoothing distribution, $p(z_t \mid y_{1:T}, u_{1:T}, \theta)$ for $t=1,\ldots,T$.
  236. Args:
  237. params: model parameters $\theta$
  238. state: current latent state $z_t$
  239. inputs: current inputs $u_t$
  240. Returns:
  241. smoothing distributions
  242. """
  243. raise NotImplementedError
  244. # Learning algorithms
  245. def e_step(
  246. self,
  247. params: ParameterSet,
  248. emissions: Float[Array, "num_timesteps emission_dim"],
  249. inputs: Optional[Float[Array, "num_timesteps input_dim"]]=None
  250. ) -> Tuple[SuffStatsSSM, Scalar]:
  251. r"""Perform an E-step to compute expected sufficient statistics under the posterior, $p(z_{1:T} \mid y_{1:T}, u_{1:T}, \theta)$.
  252. Args:
  253. params: model parameters $\theta$
  254. emissions: emissions $y_{1:T}$
  255. inputs: optional inputs $u_{1:T}$
  256. Returns:
  257. Expected sufficient statistics under the posterior.
  258. """
  259. raise NotImplementedError
  260. def m_step(
  261. self,
  262. params: ParameterSet,
  263. props: PropertySet,
  264. batch_stats: SuffStatsSSM,
  265. m_step_state: Any
  266. ) -> ParameterSet:
  267. r"""Perform an M-step to find parameters that maximize the expected log joint probability.
  268. Specifically, compute
  269. $$\theta^\star = \mathrm{argmax}_\theta \; \mathbb{E}_{p(z_{1:T} \mid y_{1:T}, u_{1:T}, \theta)} \big[\log p(y_{1:T}, z_{1:T}, \theta \mid u_{1:T}) \big]$$
  270. Args:
  271. params: model parameters $\theta$
  272. props: properties specifying which parameters should be learned
  273. batch_stats: sufficient statistics from each sequence
  274. m_step_state: any required state for optimizing the model parameters.
  275. Returns:
  276. new parameters
  277. """
  278. raise NotImplementedError
  279. def fit_em(
  280. self,
  281. params: ParameterSet,
  282. props: PropertySet,
  283. emissions: Union[Real[Array, "num_timesteps emission_dim"],
  284. Real[Array, "num_batches num_timesteps emission_dim"]],
  285. inputs: Optional[Union[Float[Array, "num_timesteps input_dim"],
  286. Float[Array, "num_batches num_timesteps input_dim"]]]=None,
  287. num_iters: int=50,
  288. verbose: bool=True,
  289. print_every: int=1,
  290. ) -> Tuple[ParameterSet, Float[Array, " num_iters"]]:
  291. r"""Compute parameter MLE/ MAP estimate using Expectation-Maximization (EM).
  292. EM aims to find parameters that maximize the marginal log probability,
  293. $$\theta^\star = \mathrm{argmax}_\theta \; \log p(y_{1:T}, \theta \mid u_{1:T})$$
  294. It does so by iteratively forming a lower bound (the "E-step") and then maximizing it (the "M-step").
  295. *Note:* ``emissions`` *and* ``inputs`` *can either be single sequences or batches of sequences.*
  296. Args:
  297. params: model parameters $\theta$. Parameters you supply yourself must have the
  298. same shapes and dtypes that ``initialize`` produces.
  299. props: properties specifying which parameters should be learned
  300. emissions: one or more sequences of emissions
  301. inputs: one or more sequences of corresponding inputs
  302. num_iters: number of iterations of EM to run
  303. verbose: whether or not to show a progress bar. Use ``False`` when calling
  304. ``fit_em`` inside ``jit`` or ``vmap``.
  305. print_every: number of EM iterations between progress bar updates. Ignored when
  306. ``verbose=False``.
  307. Returns:
  308. tuple of new parameters and log likelihoods over the course of EM iterations.
  309. """
  310. # Make sure the emissions and inputs have batch dimensions
  311. batch_emissions = ensure_array_has_batch_dim(emissions, self.emission_shape)
  312. batch_inputs = ensure_array_has_batch_dim(inputs, self.inputs_shape)
  313. def em_step(carry, _):
  314. """Perform one EM step."""
  315. params, m_step_state = carry
  316. batch_stats, lls = vmap(partial(self.e_step, params))(batch_emissions, batch_inputs)
  317. lp = self.log_prior(params) + lls.sum()
  318. params, m_step_state = self.m_step(params, props, batch_stats, m_step_state)
  319. return (params, m_step_state), lp
  320. @partial(jit, static_argnums=1)
  321. def run_em(carry, num_steps):
  322. """Run `num_steps` EM steps."""
  323. return lax.scan(em_step, carry, xs=None, length=num_steps)
  324. if num_iters <= 0:
  325. return params, jnp.array([])
  326. # The scan carries (params, m_step_state), so their structure, shapes and dtypes must
  327. # not change between iterations; initialize() produces parameters that match the M-step.
  328. carry = (params, self.initialize_m_step_state(params, props))
  329. if not verbose:
  330. (params, _), log_probs = run_em(carry, num_iters)
  331. return params, log_probs
  332. if not isinstance(print_every, Integral) or print_every < 1:
  333. raise ValueError(f"print_every must be a positive integer, got {print_every!r}")
  334. # Run `print_every` iterations per dispatch and update the bar in between. The bar counts
  335. # dispatched blocks without waiting for the device, so it can run ahead of the computation.
  336. log_probs = []
  337. pbar = progress_bar(range(num_iters))
  338. pbar.update(0)
  339. for start in range(0, num_iters, print_every):
  340. num_steps = min(print_every, num_iters - start)
  341. carry, chunk_log_probs = run_em(carry, num_steps)
  342. log_probs.append(chunk_log_probs)
  343. pbar.update(start + num_steps)
  344. return carry[0], jnp.concatenate(log_probs)
  345. def fit_sgd(
  346. self,
  347. params: ParameterSet,
  348. props: PropertySet,
  349. emissions: Union[Float[Array, "num_timesteps emission_dim"],
  350. Float[Array, "num_batches num_timesteps emission_dim"]],
  351. inputs: Optional[Union[Float[Array, "num_timesteps input_dim"],
  352. Float[Array, "num_batches num_timesteps input_dim"]]]=None,
  353. optimizer: optax.GradientTransformation=optax.adam(1e-3),
  354. batch_size: int=1,
  355. num_epochs: int=50,
  356. shuffle: bool=False,
  357. key: PRNGKeyT=jr.PRNGKey(0)
  358. ) -> Tuple[ParameterSet, Float[Array, " niter"]]:
  359. r"""Compute parameter MLE/ MAP estimate using Stochastic Gradient Descent (SGD).
  360. SGD aims to find parameters that maximize the marginal log probability,
  361. $$\theta^\star = \mathrm{argmax}_\theta \; \log p(y_{1:T}, \theta \mid u_{1:T})$$
  362. by minimizing the _negative_ of that quantity.
  363. *Note:* ``emissions`` *and* ``inputs`` *can either be single sequences or batches of sequences.*
  364. On each iteration, the algorithm grabs a *minibatch* of sequences and takes a gradient step.
  365. One pass through the entire set of sequences is called an *epoch*.
  366. Args:
  367. params: model parameters $\theta$
  368. props: properties specifying which parameters should be learned
  369. emissions: one or more sequences of emissions
  370. inputs: one or more sequences of corresponding inputs
  371. optimizer: an `optax` optimizer for minimization
  372. batch_size: number of sequences per minibatch
  373. num_epochs: number of epochs of SGD to run
  374. key: a random number generator for selecting minibatches
  375. verbose: whether or not to show a progress bar
  376. Returns:
  377. tuple of new parameters and losses (negative scaled marginal log probs) over the course of SGD iterations.
  378. """
  379. # Make sure the emissions and inputs have batch dimensions
  380. batch_emissions = ensure_array_has_batch_dim(emissions, self.emission_shape)
  381. batch_inputs = ensure_array_has_batch_dim(inputs, self.inputs_shape)
  382. unc_params = to_unconstrained(params, props)
  383. def _loss_fn(unc_params, minibatch):
  384. """Default objective function."""
  385. params = from_unconstrained(unc_params, props)
  386. minibatch_emissions, minibatch_inputs = minibatch
  387. scale = len(batch_emissions) / len(minibatch_emissions)
  388. minibatch_lls = vmap(partial(self.marginal_log_prob, params))(minibatch_emissions, minibatch_inputs)
  389. lp = self.log_prior(params) + minibatch_lls.sum() * scale
  390. return -lp / batch_emissions.size
  391. dataset = (batch_emissions, batch_inputs)
  392. unc_params, losses = run_sgd(_loss_fn,
  393. unc_params,
  394. dataset,
  395. optimizer=optimizer,
  396. batch_size=batch_size,
  397. num_epochs=num_epochs,
  398. shuffle=shuffle,
  399. key=key)
  400. params = from_unconstrained(unc_params, props)
  401. return params, losses

ssm.py at commit b58635f, under MIT · at the source

Overview

Authors: Nicolas Legrand1, Lilian Weber2, Peter Thestrup Waade1, Anna Hedvig Møller Daugaard1, Mojtaba Khodadadi3, Nace Mikuš1, Christoph Mathys1
  1. Interacting Minds Centre, Aarhus University, Aarhus, Denmark
  2. Department of Psychiatry, University of Oxford, Oxford, United Kingdom
  3. Scuola Internazionale Superiore di Studi Avanzati (SISSA), Trieste, Italy
Journal: PLoS computational biology, volume 22, issue 6, article e1014340
Dates: received 1 September 2025; accepted 18 May 2026; published online 22 June 2026
Type: Research article · Language: English
License: CC BY
Identifiers: DOI 10.1371/journal.pcbi.1014340 · PMID 42330057 · PMCID PMC13318038 · OpenAlex W4403564456
Open access: gold, a free copy (OpenAlex)
Status: code verified
Categories: human (organism)
Methods: Graphs, Machine learning
MeSH: Neural Networks, Computer*, Algorithms, Bayes Theorem, Cognition, Computational Biology, Humans, Models, Neurological, Software (* major topic)
Journal subjects: Software, Biology and Life Sciences, Computational Biology, Computational Neuroscience, Coding Mechanisms, Neuroscience, Computer and Information Sciences, Neural Networks, Artificial Intelligence, Artificial Neural Networks, Cognitive Science, Cognitive Psychology, Learning, Psychology, Social Sciences, Learning and Memory, Physical Sciences, Mathematics, Optimization, Data Management, Data Visualization, Infographics, Graphs, Machine Learning, Behavior
Topic: Neural Networks and Applications (Artificial Intelligence, Computer Science), according to OpenAlex
Citations: cited by 1 paper (Europe PMC); 61 references in the paper

Abstract

Bayesian models of cognition have gained considerable traction in computational neuroscience and psychiatry. Their scope is now expected to expand rapidly to artificial intelligence, providing general inference frameworks to support embodied, adaptable, and energy-efficient autonomous agents. A central theory in this domain is predictive coding, which posits that learning and behaviour are driven by hierarchical probabilistic inferences about the causes of sensory inputs. Biological realism constrains these networks to rely on simple local computations in the form of precision-weighted predictions and prediction errors. This can make this framework highly efficient, but its implementation comes with unique challenges on the software development side. Embedding such models in standard neural network libraries often becomes limiting, as these libraries’ compilation and differentiation backends can force a conceptual separation between optimisation algorithms and the systems being optimised. This critically departs from other biological principles such as self-monitoring, self-organisation, cellular growth, and functional plasticity. In this paper, we introduce pyhgf: a Python package backed by JAX and Rust for creating, manipulating, and sampling dynamic networks for predictive coding. We improve over other frameworks by enclosing the network components as transparent, modular, and malleable variables in the message-passing steps. The resulting graphs can implement arbitrary algorithms as belief propagation. Moreover, the transparency of core variables can also translate into inference processes that leverage self-organisation principles and express structure learning, meta-learning, or causal discovery as the consequence of network structural adaptation to surprising inputs. The main functions of the library are differentiable and seamlessly integrate into sampling or optimisation workflows. Additionally, we offer generalised Bayesian filtering and the hierarchical Gaussian filter as key examples of dynamic networks implemented in our library. The source code, tutorials, and documentation are hosted under the main repository at https://github.com/ComputationalPsychiatry/pyhgf.

Reproduced under the paper's license (CC BY), from the paper cited above.

Repositories

Its files are read in the Code ↔ Paper reader above, with 4 matches between paragraphs and lines of code.

ComputationalPsychiatry/pyhgf

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 2b4bf6772f990b0595a63b8fc740d07cbfe71e44, 25 September 2026
Languages: Python (126), Rust (44), Jupyter (25)
Size: 257 files, 195 scripts
Software Heritage: archived
Found in: “Data Availability”
Holds: README, license file, environment (pyproject.toml, uv.lock), tests, continuous integration, documentation, 25 notebooks
Not found: CITATION.cff
Tools: JAX (91 files), NumPy (47 files), Matplotlib (36 files), seaborn (19 files), PyMC (10 files), ArviZ (9 files), pandas (9 files), SciPy (3 files), NetworkX (1 file), xarray (1 file)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
197 files

probml/dynamax

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: b58635f7bca2d8f936c0d711df3a1ad45f64511c, 16 September 2026
Languages: Python (72), Jupyter (30)
Size: 143 files, 102 scripts
Software Heritage: archived
Found in: the text, “2.2 Optimisation and inference”
Holds: README, license file, CITATION.cff, environment (pyproject.toml, setup.py), tests, continuous integration, documentation, 30 notebooks
Tools: JAX (84 files), Matplotlib (33 files), scikit-learn (10 files), NumPy (7 files), seaborn (6 files), SciPy (3 files)
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
104 files

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 297 scripts, each with its path and the digest of its content;
  • 4 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Data Availability

The source code and data used to produce the results and analyses presented in this manuscript are available from the main Git repository: https://github.com/ComputationalPsychiatry/pyhgf.

Reproduced under the paper's license (CC BY), from the paper cited above.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 7 authors, 8 MeSH terms, 2 funders, 44 references.

Cite

This paper

Legrand, N., Weber, L., Waade, P. T., Møller Daugaard, A. H., Khodadadi, M., Mikuš, N., & Mathys, C. (2026). pyhgf: A neural network library for predictive coding. PLoS computational biology, 22(6), e1014340. https://doi.org/10.1371/journal.pcbi.1014340

BibTeX

@article{legrand2026pyhgf,
author = {Legrand, Nicolas and Weber, Lilian and Waade, Peter Thestrup and Møller Daugaard, Anna Hedvig and Khodadadi, Mojtaba and Mikuš, Nace and Mathys, Christoph},
title = {{pyhgf: A neural network library for predictive coding}},
journal = {PLoS computational biology},
year = {2026},
month = jun,
volume = {22},
number = {6},
pages = {e1014340},
publisher = {PLOS},
issn = {1553-734X},
doi = {10.1371/journal.pcbi.1014340},
url = {https://doi.org/10.1371/journal.pcbi.1014340},
pmid = {42330057},
pmcid = {PMC13318038}
}

RIS

TY - JOUR
AU - Legrand, Nicolas
AU - Weber, Lilian
AU - Waade, Peter Thestrup
AU - Møller Daugaard, Anna Hedvig
AU - Khodadadi, Mojtaba
AU - Mikuš, Nace
AU - Mathys, Christoph
TI - pyhgf: A neural network library for predictive coding
T2 - PLoS computational biology
J2 - PLoS Comput Biol
PY - 2026
DA - 2026/06/22
VL - 22
IS - 6
SP - e1014340
SN - 1553-734X
PB - PLOS
DO - 10.1371/journal.pcbi.1014340
UR - https://doi.org/10.1371/journal.pcbi.1014340
LA - en
ER -

CSL-JSON

{
"id": "10.1371/journal.pcbi.1014340",
"type": "article-journal",
"title": "pyhgf: A neural network library for predictive coding",
"container-title": "PLoS computational biology",
"author": [
{
"family": "Legrand",
"given": "Nicolas"
},
{
"family": "Weber",
"given": "Lilian"
},
{
"family": "Waade",
"given": "Peter Thestrup"
},
{
"family": "Møller Daugaard",
"given": "Anna Hedvig"
},
{
"family": "Khodadadi",
"given": "Mojtaba"
},
{
"family": "Mikuš",
"given": "Nace"
},
{
"family": "Mathys",
"given": "Christoph"
}
],
"container-title-short": "PLoS Comput Biol",
"volume": "22",
"issue": "6",
"page": "e1014340",
"DOI": "10.1371/journal.pcbi.1014340",
"PMID": "42330057",
"PMCID": "PMC13318038",
"ISSN": "1553-734X",
"publisher": "PLOS",
"URL": "https://doi.org/10.1371/journal.pcbi.1014340",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
22
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1038/s42003-026-10957-8 [code]
Brain defence by the extracellular matrix protein Cochlin.
Journal: Communications biology
In common: ArviZ, PyMC, JAX, 7 other tools
[2] doi:10.1126/sciadv.aed4944 [code]
Focused ultrasound blood-brain barrier opening reveals a paradoxical remote metabolic response in the primate brain.
Journal: Science advances
In common: ArviZ, PyMC, JAX, 5 other tools, 1 reference
[3] doi:10.1038/s41467-026-72940-5 [code]
Cerebellar growth is associated with domain-specific cerebral maturation and socio-linguistic behavior.
Journal: Nature communications
In common: ArviZ, PyMC, xarray, 6 other tools
[4] doi:10.1038/s41398-026-03902-0 [code]
Mapping heterogeneous brain structural subtypes in alzheimer's disease and mild cognitive impairment using normative models.
Journal: Translational psychiatry
In common: ArviZ, PyMC, xarray, 6 other tools
[5] doi:10.1162/imag.a.1269 [code]
From early to contemporary normative modeling: Mapping individual differences in neurophysiological signals.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: ArviZ, PyMC, xarray, 6 other tools
[6] doi:10.7554/elife.100605 [code]
Age-related changes in ‘cortical’ 1/f dynamics are linked to cardiac activity
Journal: n/a
In common: ArviZ, PyMC, NetworkX, 6 other tools
[7] doi:10.7554/elife.108223 [code]
Two time scales of adaptation in human learning rates.
Journal: eLife
In common: ArviZ, seaborn, pandas, 3 other tools, 3 references
[8] doi:10.1038/s41467-026-75959-w [code]
Charting higher-order models of brain function beyond pairwise interactions.
Journal: Nature communications
In common: JAX, xarray, NetworkX, 6 other tools
[9] doi: [code]
Naturalistic behavior and self-generated neural activity predictive of self-correction
Journal: bioRxiv : the preprint server for biology
In common: JAX, xarray, NetworkX, 6 other tools
[10] doi:10.1038/s41591-026-04287-9 [code]
An international mega-analysis of psychedelic drug effects on brain circuit function.
Journal: Nature medicine
In common: ArviZ, PyMC, seaborn, 5 other tools

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.