Skip to content

Conversation

@FinnBurkhardt
Copy link

Add Ornstein–Uhlenbeck Noise Generator

This pull request adds a new ou_noise_generator device and corresponding pytest-based tests. The device generates temporally correlated current following an Ornstein–Uhlenbeck (OU). The tests verify both parameter handling and statistical properties.

Model Implementation

  • Files added

    • models/ou_noise_generator.h
    • models/ou_noise_generator.cpp
    • pytests/sli2py_stimulating/test_ou_noise_generator.py
  • Process equation
    The state variable (x) evolves as

$$ dx = \frac{1}{\tau}(\mu - x)\ dt + \sigma\ dW $$

where

  • ($\mu$) (mean parameter) is the long-term mean of the process,

  • ($\sigma$) (std parameter) its stationary standard deviation,

  • ($\tau$) (tau parameter) is the time constant,

  • dt is the internal integration step.

  • Integration and output

    • Internally integrates the OU equation at user-specified dt.
    • On each simulation step it emits the current (x) to connected targets.

Tests (test_ou_noise_generator.py)

  • Parameter setting and defaults

    • Verifies that set() and SetDefaults() both correctly configure all parameters (mean, std, tau, dt).
  • Error conditions

    • Asserts that attempting to create the generator with a dt not divisible by nest.resolution raises StepMultipleRequired.
  • Mean and variance

    • Runs 100 trials of 1000 ms each, records the last current value, and checks that the sample mean and variance match the target $\mu$ and $\sigma^2$ within reasonable statistical bounds.
  • Autocorrelation

    • Collects a long trace, discards the initial $10\tau$ burn-in, and computes the empirical lag-1 autocorrelation.
    • Confirms it matches $\exp(-\text{dt}/\tau)$ within a small tolerance.
  • Cross-correlation

    • Connects a single OU generator to two independent neurons and records their membrane potentials.
    • Computes the cross-correlation of the two voltage traces and verifies it remains near zero.
@heplesser heplesser added T: Enhancement New functionality, model or documentation S: Normal Handle this with default priority I: No breaking change Previously written code will work as before, no one should note anything changing (aside the fix) labels Aug 7, 2025
@heplesser heplesser added this to Models Aug 7, 2025
@github-project-automation github-project-automation bot moved this to To do in Models Aug 7, 2025
@jessica-mitchell jessica-mitchell changed the title Add ou generator Add Ornstein–Uhlenbeck noise generator Aug 11, 2025
Copy link
Contributor

@jessica-mitchell jessica-mitchell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the contribution!
I have a minor change for the docs, and a question if there is a good use case we can use an example for this generator?

@github-project-automation github-project-automation bot moved this from To do to Review in Models Aug 11, 2025
@terhorstd terhorstd requested a review from jakobkramp August 13, 2025 07:26
@heplesser
Copy link
Contributor

@FinnBurkhardt Would you be able to address the open issues within a few days? I also added two minor suggestions. Please merge master into your branch for this PR to ensure that the testsuite works properly.

@heplesser heplesser requested a review from clinssen September 11, 2025 17:44
@FinnBurkhardt
Copy link
Author

Thanks for the contribution! I have a minor change for the docs, and a question if there is a good use case we can use an example for this generator?

Thanks, I’ve applied the suggested changes.

For examples, I’d have two candidates at hand:

  1. Single neuron driven by the OU noise generator.

  2. White vs. OU noise (matched mean/variance), comparing firing rate and coefficient of variation.

Would one (or both) be useful for the docs?

@jessica-mitchell
Copy link
Contributor

@FinnBurkhardt if they are ready then we could take both.
You can add them to pynest/examples/ (please adhere to the style of docstring as in the other examples)

And you can add it to the doc/htmldoc/examples/index.rst page:
I think in the 'stimulating network' section would make sense?

 .. grid-item-card:: Stimulating networks :img-top: ../static/img/pynest/sensitivitypertubation.png * :doc:`../auto_examples/sinusoidal_poisson_generator` * :doc:`../auto_examples/sinusoidal_gamma_generator` * :doc:`../auto_examples/repeated_stimulation` * :doc:`../auto_examples/sensitivity_to_perturbation` * :doc:`../auto_examples/intrinsic_currents_spiking` * :doc:`../auto_examples/intrinsic_currents_subthreshold` 

It also needs to be added to the .. toctree::

I can also help with this if it's confusing!

Copy link
Contributor

@jessica-mitchell jessica-mitchell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @FinnBurkhardt looks good, just a few minor formatting suggestions

Copy link
Contributor

@jessica-mitchell jessica-mitchell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!

mean
The mean value :math:`\mu` to which the process reverts (pA).

std
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On line 66 of models/ou_noise_generator.cpp, this unit is given as pA/sqrt(s)

struct Parameters_
{
double mean_; //!< mean current, in pA
double std_; //!< standard deviation of current, in pA
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here

{
double mean_; //!< mean current, in pA
double std_; //!< standard deviation of current, in pA
double tau_; //!< Standard frequency in Hz
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unit here seems wrong


/**
* Number of targets.
* This is a hidden parameter; must be placed in parameters,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious why not put it into Variables?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering the same thing, but it is the same in noise_generator and several other generators. This comment may be very old, from the time when we still had a (non-working) ResetNetwork command. But changing that should be done for all pertaining generators. I have created #3617 for follow-up and suggest we keep it as is here.

Copy link
Contributor

@clinssen clinssen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the PR! I just have some small comments/questions.

Copy link
Contributor

@heplesser heplesser left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for the revisions. I noticed a few more details and added one principal questions concerning the return from periods of inactivity.

public:
ou_noise_generator();
ou_noise_generator( const ou_noise_generator& );

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Comment on lines +232 to +233
double noise_amp_; //!<
double tau_inv_; //!<
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment texts are missing here. I find tau_inv_ misleading (would expect value 1/tau from the name), while it is 1-exp(-h/tau). Can you find a better name? Further, if I understand the code right, it is only used in in the combination P_.mean_ * V_.tau_inv_, so why not store that value in a variable mean_decayed_ (or whatever would be a good name)?


/**
* Number of targets.
* This is a hidden parameter; must be placed in parameters,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am wondering the same thing, but it is the same in noise_generator and several other generators. This comment may be very old, from the time when we still had a (non-working) ResetNetwork command. But changing that should be done for all pertaining generators. I have created #3617 for follow-up and suggest we keep it as is here.

const double h = Time::get_resolution().get_ms();
const double t = kernel().simulation_manager.get_time().get_ms();

// scale Hz to ms
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment seems to make no sense here.

Comment on lines +229 to +235
const double noise_amp = P_.std_ * std::sqrt( -1 * std::expm1( -2 * h / P_.tau_ ) );
const double prop = std::exp( -1 * h / P_.tau_ );
const double tau_inv = -std::expm1( -h / P_.tau_ );

V_.noise_amp_ = noise_amp;
V_.prop_ = prop;
V_.tau_inv_ = tau_inv;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would simplify this a bit

Suggested change
const double noise_amp = P_.std_ * std::sqrt( -1 * std::expm1( -2 * h / P_.tau_ ) );
const double prop = std::exp( -1 * h / P_.tau_ );
const double tau_inv = -std::expm1( -h / P_.tau_ );
V_.noise_amp_ = noise_amp;
V_.prop_ = prop;
V_.tau_inv_ = tau_inv;
V_.noise_amp_ = P_.std_ * std::sqrt( -1 * std::expm1( -2 * h / P_.tau_ ) );
V_.prop_ = std::exp( -1 * h / P_.tau_ );
V_.tau_inv_ = -std::expm1( -h / P_.tau_ );
}

// >= in case we woke from inactivity
if ( now >= B_.next_step_ )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the logic for handling inactivity is correct here. Consider a situation like the following:

ou = nest.Create('ou_noise_generator', params={'start': 200, 'stop': 500'}) for k in range(10): ou.offset = k * 1000 nest.Simulate(1000)

This runs a simulation in sections of 1000 ms and in each section the generator is on from 200 to 500 ms and off in between.

With the code as it is now, we freeze the OU process while the generator is inactive. But would it not make more sense to assume that the process runs permanently and we only send its values out between start and stop? Then, amp should always be updated and only I_amp should be kept to 0 and no events sent out while the generator is inactive. Or does this not matter? Or could on be more efficient and correct in probability by updating according to the time that has passed since the generator was last active?

You can use a :doc:`multimeter <multimeter>` to record the average current sent to all targets for each time step if
simulating on a single thread; multiple MPI processes with one thread each also work. In this case, the recording
interval of the multimeter should be equal to the `dt` of the generator to avoid aliasing effects. In multi-threaded
mode, recording of noise currents is prohibited for technical reasons.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not see that prohibition in the code.

@@ -0,0 +1,155 @@
# -*- coding: utf-8 -*-
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file should be directly in pytests. The sli2py_* directories are only for tests ported from SLI.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

I: No breaking change Previously written code will work as before, no one should note anything changing (aside the fix) S: Normal Handle this with default priority T: Enhancement New functionality, model or documentation

6 participants