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

Rename resolution() to timestep()/floating point epsilon #988

Open
clinssen opened this issue Nov 16, 2023 · 5 comments
Open

Rename resolution() to timestep()/floating point epsilon #988

clinssen opened this issue Nov 16, 2023 · 5 comments

Comments

@clinssen
Copy link
Contributor

Depends on #879.

The update block can be used to integrate the subthreshold dynamics of the neuron across one timestep. However, the timestep might not be constant, for instance when integrating from spike to spike in a synapse model. The word "resolution" implies a fixed timestep. Hence I would suggest to rename it to "timestep".

@clinssen
Copy link
Contributor Author

An issue with this is the use of resolution() in an onCondition expression, for instance in refractoriness:

onCondition(is_refractory and refr_t <= resolution() / 2):

What should the timestep refer to here in case of non-constant simulation resolution? Possibly, NESTML should have a predefined variable "epsilon", which could for instance correspond to the C++ std::numeric_limits<float>::epsilon().

@heplesser
Copy link

An issue with this is the use of resolution() in an onCondition expression, for instance in refractoriness:

onCondition(is_refractory and refr_t <= resolution() / 2):

What should the timestep refer to here in case of non-constant simulation resolution? Possibly, NESTML should have a predefined variable "epsilon", which could for instance correspond to the C++ std::numeric_limits<float>::epsilon().

I wonder what mathematical concept you want to express with refr_t < resolution() / 2 here, as you in the remainder of the comment mention some epsilon?

@clinssen
Copy link
Contributor Author

@heplesser: you came up with this solution initially, as we wanted to refactor the refractoriness mechanism from an integer-based countdown method to a floating point countdown method, to make models more generic in supporting simulation platforms that have a non-constant timestep. Because of floating point rounding errors, we introduced an epsilon equal to resolution/2. We could possibly replace this with a numeric_floats::epsilon. Do you think that would be an adequate solution?

@heplesser
Copy link

resolution is clearly a concept that makes sense only for fixed-time-step simulation schemes. Since NESTML aims to be generic and describe models, not implementations, resolution is indeed not a suitable term.

At the same time, I think that timestep is not a suitable term either, as it is also an implementation detail. In the model specification, strictly speaking neither should appear, although one might consider implementation hints, e.g., for fixed-time-step models.

The underlying challenge here is how to reliably implement discrete events such as return from refractoriness. Let us assume that $t_r$ is the precise, model-defined time at which the refractory period of the neuron ends and that we have a sequence of update time points $t_1 &lt; t_2 &lt; t3$ with no further update events in between them (except possibly return from refractoriness). These time points might be times at which incoming spikes arrive, outgoing spikes are generated or times on a fixed update grid. Updates are defined over the left-open, right-closed intervals $(t_1, t_2]$ and $(t_2, t_3]$. If $t_r\in (t_1, t_2]$, we can distinguish two cases (I am not sure how to specify in NESTML which approach to use)

  • If we have precise-timing style updates (see Morrison et al (2007)), we need to break the first update interval into $(t_1, t_r]$ and $(t_r, t_2]$ with the neuron refractory only in the first part. In this case, we have no rounding problems, we treat $t_r$ simply as a double.
  • If we switch refractoriness only at otherwise existing update times, then small numerical errors in calculating t_r and t_2 could lead to delayed return from refractoriness. But this is an implementation rather than a model specification problem:
    • In a step-based simulation scheme, all times should be expressed as discrete steps (as NEST does it classically) and the rounding problem disappears.
    • For continuous-time schemes (including the precise-timing case above) one can consider to allow for some numerical jitter to avoid confusing results, although such measures may again lead to confusing results if a user injects carefully crafter spike trains. But an approach here could be to allow a relative error of a few bits of precision.

Now for the specification of refractoriness in general, I wonder if the onCondition() expression above is sensible at all, or if it would not be better to express refractoriness in a declarative way as follows

  1. At any time, $t_s$ is the time of the last spike and $t_r = t_s+\tau_r$ is the end of the refractory period following that spike.
  2. The neuron is thus refractory in the period $(t_s, t_r]$ and this is expressed in NESTML as is_refractory = t_s < t <= t_s + tau_r

I am not entirely happy with writing ... < ... <= ... since the user might deviate from the left-open, right-closed logic, so

is_refractory = t in Interval(t_s, t_s + tau_r)

could be a better solution, where Interval() always is left-open, right-closed.

@clinssen clinssen changed the title Rename resolution() to timestep() Rename resolution() to timestep()/floating point epsilon Aug 16, 2024
@clinssen
Copy link
Contributor Author

clinssen commented Aug 16, 2024

The discussion of whether a floating point epsilon has also come up in a discussion about the ignore_and_fire neuron.

When the simulation resolution is 1 ms and the rate is set to 100/s, the actual number of spikes achieved over a 1 second interval is not 100, but 91. This is because it takes 10 iterations to update the phase to 0.99999999999999989. Only on the next iteration (with a phase value close to 1.1) does the threshold check get triggered and a spike get fired, so we are only firing a spike every 11 iterations rather than every 10.

Clearly, a floating point epsilon primitive in the NESTML language would help here.

@tomtetzlaff argues against the inclusion of a floating point epsilon in NESTML: "At the level of the model description, this would be confusing because, mathematically, i.e. by solving the ODE for the phase, the firing rate of the defined model is correct." I would tend to agree as we define numbers as "real" in NESTML, not as "float" or "double".

Alternatively, we should evaluate whether floating point comparisons can automatically be made safe in a generic way, by including the epsilon when generating code in a fully automated fashion.

@diesmann: "This problems of the floating point representation are known to every scientist and it is better to rather expose than to hide them."

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

No branches or pull requests

2 participants