Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there a problem during the update function of the model aeif? #3387

Closed
jing-alice opened this issue Dec 27, 2024 · 2 comments
Closed

Is there a problem during the update function of the model aeif? #3387

jing-alice opened this issue Dec 27, 2024 · 2 comments
Assignees
Labels
S: Normal Handle this with default priority

Comments

@jing-alice
Copy link

jing-alice commented Dec 27, 2024

I have one question of the update of model aeif.
The following is the update function of HH model, it is copyed from hh_psc_alpha.cpp.
The paramer such as membrane voltage is updated when the numerical integration fininshed.

void 
nest::hh_psc_alpha::update( Time const& origin, const long from, const long to )
{
  for ( long lag = from; lag < to; ++lag )
  {

    double t = 0.0;
    const double U_old = S_.y_[ State_::V_M ];

    // numerical integration with adaptive step size control:
    // ------------------------------------------------------
    // gsl_odeiv_evolve_apply performs only a single numerical
    // integration step, starting from t and bounded by step;
    // the while-loop ensures integration over the whole simulation
    // step (0, step] if more than one integration step is needed due
    // to a small integration step size;
    // note that (t+IntegrationStep > step) leads to integration over
    // (t, step] and afterwards setting t to step, but it does not
    // enforce setting IntegrationStep to step-t; this is of advantage
    // for a consistent and efficient integration across subsequent
    // simulation intervals
    while ( t < B_.step_ )
    {
      const int status = gsl_odeiv_evolve_apply( B_.e_,
        B_.c_,
        B_.s_,
        &B_.sys_,             // system of ODE
        &t,                   // from t
        B_.step_,             // to t <= step
        &B_.IntegrationStep_, // integration step size
        S_.y_ );              // neuronal state
      if ( status != GSL_SUCCESS )
      {
        throw GSLSolverFailure( get_name(), status );
      }
    }

    S_.y_[ State_::DI_EXC ] += B_.spike_exc_.get_value( lag ) * V_.PSCurrInit_E_;
    S_.y_[ State_::DI_INH ] += B_.spike_inh_.get_value( lag ) * V_.PSCurrInit_I_;

    // sending spikes: crossing 0 mV, pseudo-refractoriness and local maximum...
    // refractory?
    if ( S_.r_ > 0 )
    {
      --S_.r_;
    }
    else if ( S_.y_[ State_::V_M ] >= 0 and U_old > S_.y_[ State_::V_M ] ) // ( threshold and maximum )
    {
      S_.r_ = V_.RefractoryCounts_;

      set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );

      SpikeEvent se;
      kernel().event_delivery_manager.send( *this, se, lag );
    }

    // log state data
    B_.logger_.record_data( origin.get_steps() + lag );

    // set new input current
    B_.I_stim_ = B_.currents_.get_value( lag );
  }
}

Differs from HH, the aeif model updated the parameter multiple times during the numerical integration. Whether it should be similar to HH model is not updated until the end of numerical integration.
The following is copyed from aeif_cond_alpha.cpp.
In the code, it is mainly reflected in where does the while loop terminate

void
nest::aeif_cond_alpha::update( Time const& origin, const long from, const long to )
{
  assert( State_::V_M == 0 );

  for ( long lag = from; lag < to; ++lag )
  {
    double t = 0.0;

    // numerical integration with adaptive step size control:
    // ------------------------------------------------------
    // gsl_odeiv_evolve_apply performs only a single numerical
    // integration step, starting from t and bounded by step;
    // the while-loop ensures integration over the whole simulation
    // step (0, step] if more than one integration step is needed due
    // to a small integration step size;
    // note that (t+IntegrationStep > step) leads to integration over
    // (t, step] and afterwards setting t to step, but it does not
    // enforce setting IntegrationStep to step-t; this is of advantage
    // for a consistent and efficient integration across subsequent
    // simulation intervals

    while ( t < B_.step_ )
    {
      const int status = gsl_odeiv_evolve_apply( B_.e_,
        B_.c_,
        B_.s_,
        &B_.sys_,             // system of ODE
        &t,                   // from t
        B_.step_,             // to t <= step
        &B_.IntegrationStep_, // integration step size
        S_.y_ );              // neuronal state
      if ( status != GSL_SUCCESS )
      {
        throw GSLSolverFailure( get_name(), status );
      }

      // check for unreasonable values; we allow V_M to explode
      if ( S_.y_[ State_::V_M ] < -1e3 or S_.y_[ State_::W ] < -1e6 or S_.y_[ State_::W ] > 1e6 )
      {
        throw NumericalInstability( get_name() );
      }

      // spikes are handled inside the while-loop
      // due to spike-driven adaptation
      if ( S_.r_ > 0 )
      {
        S_.y_[ State_::V_M ] = P_.V_reset_;
      }
      else if ( S_.y_[ State_::V_M ] >= V_.V_peak )
      {
        S_.y_[ State_::V_M ] = P_.V_reset_;
        S_.y_[ State_::W ] += P_.b; // spike-driven adaptation

        /* Initialize refractory step counter.
         * - We need to add 1 to compensate for count-down immediately after
         *   while loop.
         * - If neuron has no refractory time, set to 0 to avoid refractory
         *   artifact inside while loop.
         */
        S_.r_ = V_.refractory_counts_ > 0 ? V_.refractory_counts_ + 1 : 0;

        set_spiketime( Time::step( origin.get_steps() + lag + 1 ) );
        SpikeEvent se;
        kernel().event_delivery_manager.send( *this, se, lag );
      }
    }

    // decrement refractory count
    if ( S_.r_ > 0 )
    {
      --S_.r_;
    }

    // apply spikes
    S_.y_[ State_::DG_EXC ] += B_.spike_exc_.get_value( lag ) * V_.g0_ex_;
    S_.y_[ State_::DG_INH ] += B_.spike_inh_.get_value( lag ) * V_.g0_in_;

    // set new input current
    B_.I_stim_ = B_.currents_.get_value( lag );

    // log state data
    B_.logger_.record_data( origin.get_steps() + lag );
  }
}

Looking forward to your reply.

@clinssen
Copy link
Contributor

clinssen commented Jan 6, 2025

Hi, and thanks for writing in! There is indeed a subtle difference between these two methods.

When integrating the numerics, it can be the case that the gsl_odeiv_evolve_apply() function does not update all the way to B_.step_, so we need to put a while-loop around this call (while ( t < B_.step_ )).

The difference between the two models, is that aeif_cond_alpha puts the check for threshold crossing (if ( S_.y_[ State_::V_M ] >= V_.V_peak )) inside this inner loop, whereas hh_psc_alpha leaves the check in the outer loop. The reason for this is that the numerics for aeif models can be notoriously unstable; putting the check inside the inner loop helps to prevent the numerical values from diverging.

I also filed #3388 as a small clarification.

Please let us know if this answers your question!

@jing-alice
Copy link
Author

Thank you very much for your detailed explanation, which is very helpful to me. This is a really good way to avoid diverging values.

@gtrensch gtrensch added the S: Normal Handle this with default priority label Jan 10, 2025
@gtrensch gtrensch added this to Models Jan 10, 2025
@github-project-automation github-project-automation bot moved this to To do in Models Jan 10, 2025
@github-project-automation github-project-automation bot moved this from To do to Done in Models Jan 10, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
S: Normal Handle this with default priority
Projects
Status: Done
Development

No branches or pull requests

3 participants