braintools 0.0.2__py2.py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. braintools/__init__.py +27 -0
  2. braintools/functional/__init__.py +25 -0
  3. braintools/functional/_activations.py +733 -0
  4. braintools/functional/_normalization.py +62 -0
  5. braintools/functional/_spikes.py +90 -0
  6. braintools/init/__init__.py +26 -0
  7. braintools/init/_base.py +36 -0
  8. braintools/init/_generic.py +205 -0
  9. braintools/init/_random_inits.py +489 -0
  10. braintools/init/_regular_inits.py +109 -0
  11. braintools/input/__init__.py +27 -0
  12. braintools/input/currents.py +380 -0
  13. braintools/input/currents_test.py +111 -0
  14. braintools/metric/__init__.py +46 -0
  15. braintools/metric/_classification.py +569 -0
  16. braintools/metric/_classification_test.py +625 -0
  17. braintools/metric/_correlation.py +302 -0
  18. braintools/metric/_correlation_test.py +124 -0
  19. braintools/metric/_fenchel_young.py +67 -0
  20. braintools/metric/_fenchel_young_test.py +55 -0
  21. braintools/metric/_firings.py +85 -0
  22. braintools/metric/_firings_test.py +41 -0
  23. braintools/metric/_lfp.py +139 -0
  24. braintools/metric/_ranking.py +163 -0
  25. braintools/metric/_ranking_test.py +242 -0
  26. braintools/metric/_regression.py +425 -0
  27. braintools/metric/_regression_test.py +133 -0
  28. braintools/metric/_smoothing.py +46 -0
  29. braintools/metric/_smoothing_test.py +43 -0
  30. braintools/metric/_util.py +25 -0
  31. braintools/optim/__init__.py +23 -0
  32. braintools/optim/_lr_scheduler.py +482 -0
  33. braintools/optim/_sgd_optimizer.py +1130 -0
  34. braintools-0.0.2.dist-info/LICENSE +202 -0
  35. braintools-0.0.2.dist-info/METADATA +93 -0
  36. braintools-0.0.2.dist-info/RECORD +38 -0
  37. braintools-0.0.2.dist-info/WHEEL +6 -0
  38. braintools-0.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,302 @@
1
+ # Copyright 2024 BrainPy Ecosystem Limited. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ # -*- coding: utf-8 -*-
17
+
18
+ from typing import Union
19
+
20
+ import braincore as bc
21
+ import numpy as onp
22
+ from jax import vmap, lax, numpy as jnp
23
+
24
+ __all__ = [
25
+ 'cross_correlation',
26
+ 'voltage_fluctuation',
27
+ 'matrix_correlation',
28
+ 'weighted_correlation',
29
+ 'functional_connectivity',
30
+ 'functional_connectivity_dynamics',
31
+ ]
32
+
33
+
34
+ def cross_correlation(spikes: bc.typing.ArrayLike,
35
+ bin: Union[int, float],
36
+ dt: Union[int, float] =None,
37
+ method: str = 'loop'):
38
+ r"""Calculate cross correlation index between neurons.
39
+
40
+ The coherence [1]_ between two neurons i and j is measured by their
41
+ cross-correlation of spike trains at zero time lag within a time bin
42
+ of :math:`\Delta t = \tau`. More specifically, suppose that a long
43
+ time interval T is divided into small bins of :math:`\Delta t` and
44
+ that two spike trains are given by :math:`X(l)=` 0 or 1, :math:`Y(l)=` 0
45
+ or 1, :math:`l=1,2, \ldots, K(T / K=\tau)`. Thus, we define a coherence
46
+ measure for the pair as:
47
+
48
+ .. math::
49
+
50
+ \kappa_{i j}(\tau)=\frac{\sum_{l=1}^{K} X(l) Y(l)}
51
+ {\sqrt{\sum_{l=1}^{K} X(l) \sum_{l=1}^{K} Y(l)}}
52
+
53
+ The population coherence measure :math:`\kappa(\tau)` is defined by the
54
+ average of :math:`\kappa_{i j}(\tau)` over many pairs of neurons in the
55
+ network.
56
+
57
+ .. note::
58
+ To JIT compile this function, users should make ``bin``, ``dt``, ``numpy`` static.
59
+ For example, ``partial(brainpy.measure.cross_correlation, bin=10, numpy=False)``.
60
+
61
+ Parameters
62
+ ----------
63
+ spikes : ndarray
64
+ The history of spike states of the neuron group.
65
+ bin : float, int
66
+ The time bin to normalize spike states.
67
+ dt : float, optional
68
+ The time precision.
69
+ method: str
70
+ The method to calculate all pairs of cross correlation.
71
+ Supports two kinds of methods: `loop` and `vmap`.
72
+ `vmap` method needs much more memory.
73
+
74
+ Returns
75
+ -------
76
+ cc_index : float
77
+ The cross correlation value which represents the synchronization index.
78
+
79
+ References
80
+ ----------
81
+ .. [1] Wang, Xiao-Jing, and György Buzsáki. "Gamma oscillation by synaptic
82
+ inhibition in a hippocampal interneuronal network model." Journal of
83
+ neuroscience 16.20 (1996): 6402-6413.
84
+ """
85
+ dt = bc.environ.get_dt() if dt is None else dt
86
+ bin_size = int(bin / dt)
87
+ num_hist, num_neu = spikes.shape
88
+ num_bin = int(onp.ceil(num_hist / bin_size))
89
+ if num_bin * bin_size != num_hist:
90
+ spikes = jnp.append(spikes, jnp.zeros((num_bin * bin_size - num_hist, num_neu)), axis=0)
91
+ states = spikes.T.reshape((num_neu, num_bin, bin_size))
92
+ states = jnp.asarray(jnp.sum(states, axis=2) > 0., dtype=jnp.float_)
93
+ indices = jnp.tril_indices(num_neu, k=-1)
94
+
95
+ if method == 'loop':
96
+ def _f(i, j):
97
+ sqrt_ij = jnp.sqrt(jnp.sum(states[i]) * jnp.sum(states[j]))
98
+ return lax.cond(sqrt_ij == 0.,
99
+ lambda _: 0.,
100
+ lambda _: jnp.sum(states[i] * states[j]) / sqrt_ij,
101
+ None)
102
+
103
+ res = bc.transform.for_loop(_f, *indices)
104
+
105
+ elif method == 'vmap':
106
+ @vmap
107
+ def _cc(i, j):
108
+ sqrt_ij = jnp.sqrt(jnp.sum(states[i]) * jnp.sum(states[j]))
109
+ return lax.cond(sqrt_ij == 0.,
110
+ lambda _: 0.,
111
+ lambda _: jnp.sum(states[i] * states[j]) / sqrt_ij,
112
+ None)
113
+
114
+ res = _cc(*indices)
115
+ else:
116
+ raise ValueError(f'Do not support {method}. We only support "loop" or "vmap".')
117
+
118
+ return jnp.mean(jnp.asarray(res))
119
+
120
+
121
+ def _f_signal(signal):
122
+ return jnp.mean(signal * signal) - jnp.mean(signal) ** 2
123
+
124
+
125
+ def voltage_fluctuation(potentials, method='loop'):
126
+ r"""Calculate neuronal synchronization via voltage variance.
127
+
128
+ The method comes from [1]_ [2]_ [3]_.
129
+
130
+ First, average over the membrane potential :math:`V`
131
+
132
+ .. math::
133
+
134
+ V(t) = \frac{1}{N} \sum_{i=1}^{N} V_i(t)
135
+
136
+ The variance of the time fluctuations of :math:`V(t)` is
137
+
138
+ .. math::
139
+
140
+ \sigma_V^2 = \left\langle \left[ V(t) \right]^2 \right\rangle_t -
141
+ \left[ \left\langle V(t) \right\rangle_t \right]^2
142
+
143
+ where :math:`\left\langle \ldots \right\rangle_t = (1 / T_m) \int_0^{T_m} dt \, \ldots`
144
+ denotes time-averaging over a large time, :math:`\tau_m`. After normalization
145
+ of :math:`\sigma_V` to the average over the population of the single cell
146
+ membrane potentials
147
+
148
+ .. math::
149
+
150
+ \sigma_{V_i}^2 = \left\langle\left[ V_i(t) \right]^2 \right\rangle_t -
151
+ \left[ \left\langle V_i(t) \right\rangle_t \right]^2
152
+
153
+ one defines a synchrony measure, :math:`\chi (N)`, for the activity of a system
154
+ of :math:`N` neurons by:
155
+
156
+ .. math::
157
+
158
+ \chi^2 \left( N \right) = \frac{\sigma_V^2}{ \frac{1}{N} \sum_{i=1}^N
159
+ \sigma_{V_i}^2}
160
+
161
+ .. [1] Golomb, D. and Rinzel J. (1993) Dynamics of globally coupled
162
+ inhibitory neurons with heterogeneity. Phys. Rev. E 48:4810-4814.
163
+ .. [2] Golomb D. and Rinzel J. (1994) Clustering in globally coupled
164
+ inhibitory neurons. Physica D 72:259-282.
165
+ .. [3] David Golomb (2007) Neuronal synchrony measures. Scholarpedia, 2(1):1347.
166
+
167
+ Args:
168
+ potentials: The membrane potential matrix of the neuron group.
169
+ method: The method to calculate all pairs of cross correlation.
170
+ Supports two kinds of methods: `loop` and `vmap`.
171
+ `vmap` method will consume much more memory.
172
+
173
+ Returns:
174
+ sync_index: The synchronization index.
175
+ """
176
+
177
+ avg = jnp.mean(potentials, axis=1)
178
+ avg_var = jnp.mean(avg * avg) - jnp.mean(avg) ** 2
179
+
180
+ if method == 'loop':
181
+ _var = bc.transform.for_loop(_f_signal, jnp.moveaxis(potentials, 0, 1))
182
+ elif method == 'vmap':
183
+ _var = vmap(_f_signal, in_axes=1)(potentials)
184
+ else:
185
+ raise ValueError(f'Do not support {method}. We only support "loop" or "vmap".')
186
+
187
+ var_mean = jnp.mean(_var)
188
+ r = jnp.where(var_mean == 0., 1., avg_var / var_mean)
189
+ return r
190
+
191
+
192
+ def matrix_correlation(x, y):
193
+ """Pearson correlation of the lower triagonal of two matrices.
194
+
195
+ The triangular matrix is offset by k = 1 in order to ignore the diagonal line
196
+
197
+ Parameters
198
+ ----------
199
+ x: ndarray
200
+ First matrix.
201
+ y: ndarray
202
+ Second matrix
203
+ numpy: bool
204
+ Whether we use numpy array as the functional output.
205
+ If ``False``, this function can be JIT compiled.
206
+
207
+ Returns
208
+ -------
209
+ coef: ndarray
210
+ Correlation coefficient
211
+ """
212
+ if x.ndim != 2:
213
+ raise ValueError(f'Only support 2d array, but we got a array '
214
+ f'with the shape of {x.shape}')
215
+ if y.ndim != 2:
216
+ raise ValueError(f'Only support 2d array, but we got a array '
217
+ f'with the shape of {y.shape}')
218
+ x = x[jnp.triu_indices_from(x, k=1)]
219
+ y = y[jnp.triu_indices_from(y, k=1)]
220
+ cc = jnp.corrcoef(x, y)[0, 1]
221
+ return cc
222
+
223
+
224
+ def functional_connectivity(activities):
225
+ """Functional connectivity matrix of timeseries activities.
226
+
227
+ Parameters
228
+ ----------
229
+ activities: ndarray
230
+ The multidimensional array with the shape of ``(num_time, num_sample)``.
231
+ numpy: bool
232
+ Whether we use numpy array as the functional output.
233
+ If ``False``, this function can be JIT compiled.
234
+
235
+ Returns
236
+ -------
237
+ connectivity_matrix: ndarray
238
+ ``num_sample x num_sample`` functional connectivity matrix.
239
+ """
240
+ if activities.ndim != 2:
241
+ raise ValueError('Only support 2d array with shape of "(num_time, num_sample)". '
242
+ f'But we got a array with the shape of {activities.shape}')
243
+ fc = jnp.corrcoef(activities.T)
244
+ return jnp.nan_to_num(fc)
245
+
246
+
247
+ def functional_connectivity_dynamics(activities, window_size=30, step_size=5):
248
+ """Computes functional connectivity dynamics (FCD) matrix.
249
+
250
+ Parameters
251
+ ----------
252
+ activities: ndarray
253
+ The time series with shape of ``(num_time, num_sample)``.
254
+ window_size: int
255
+ Size of each rolling window in time steps, defaults to 30.
256
+ step_size: int
257
+ Step size between each rolling window, defaults to 5.
258
+
259
+ Returns
260
+ -------
261
+ fcd_matrix: ndarray
262
+ FCD matrix.
263
+ """
264
+ pass
265
+
266
+
267
+ def weighted_correlation(x, y, w):
268
+ """Weighted Pearson correlation of two data series.
269
+
270
+ Parameters
271
+ ----------
272
+ x: ndarray
273
+ The data series 1.
274
+ y: ndarray
275
+ The data series 2.
276
+ w: ndarray
277
+ Weight vector, must have same length as x and y.
278
+
279
+ Returns
280
+ -------
281
+ corr: ndarray
282
+ Weighted correlation coefficient.
283
+ """
284
+
285
+ def _weighted_mean(x, w):
286
+ """Weighted Mean"""
287
+ return jnp.sum(x * w) / jnp.sum(w)
288
+
289
+ def _weighted_cov(x, y, w):
290
+ """Weighted Covariance"""
291
+ return jnp.sum(w * (x - _weighted_mean(x, w)) * (y - _weighted_mean(y, w))) / jnp.sum(w)
292
+
293
+ if x.ndim != 1:
294
+ raise ValueError(f'Only support 1d array, but we got a array '
295
+ f'with the shape of {x.shape}')
296
+ if y.ndim != 1:
297
+ raise ValueError(f'Only support 1d array, but we got a array '
298
+ f'with the shape of {y.shape}')
299
+ if w.ndim != 1:
300
+ raise ValueError(f'Only support 1d array, but we got a array '
301
+ f'with the shape of {w.shape}')
302
+ return _weighted_cov(x, y, w) / jnp.sqrt(_weighted_cov(x, x, w) * _weighted_cov(y, y, w))
@@ -0,0 +1,124 @@
1
+ # Copyright 2024- BrainPy Ecosystem Limited. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ # -*- coding: utf-8 -*-
17
+
18
+
19
+ import unittest
20
+ from functools import partial
21
+ import braintools as bt
22
+
23
+ from jax import jit
24
+ import jax.numpy as jnp
25
+ import braincore as bc
26
+
27
+
28
+ class TestCrossCorrelation(unittest.TestCase):
29
+ def test_c(self):
30
+ bc.random.seed()
31
+ spikes = jnp.asarray([[1, 0, 1, 0, 1, 0, 1, 0, 0], [1, 1, 1, 1, 1, 1, 1, 0, 0]]).T
32
+ cc1 = bt.metric.cross_correlation(spikes, 1., dt=1.)
33
+ f_cc = jit(partial(bt.metric.cross_correlation, bin=1, dt=1.))
34
+ cc2 = f_cc(spikes)
35
+ print(cc1, cc2)
36
+ self.assertTrue(cc1 == cc2)
37
+ bc.util.clear_buffer_memory()
38
+
39
+ def test_cc(self):
40
+ bc.random.seed()
41
+ spikes = jnp.ones((1000, 10))
42
+ cc1 = bt.metric.cross_correlation(spikes, 1.)
43
+ self.assertTrue(cc1 == 1.)
44
+
45
+ spikes = jnp.zeros((1000, 10))
46
+ cc2 = bt.metric.cross_correlation(spikes, 1.)
47
+ self.assertTrue(cc2 == 0.)
48
+
49
+ bc.util.clear_buffer_memory()
50
+
51
+ def test_cc2(self):
52
+ bc.random.seed()
53
+ spikes = bc.random.randint(0, 2, (1000, 10))
54
+ print(bt.metric.cross_correlation(spikes, 1.))
55
+ print(bt.metric.cross_correlation(spikes, 0.5))
56
+ bc.util.clear_buffer_memory()
57
+
58
+ def test_cc3(self):
59
+ bc.random.seed()
60
+ spikes = bc.random.random((1000, 100)) < 0.8
61
+ print(bt.metric.cross_correlation(spikes, 1.))
62
+ print(bt.metric.cross_correlation(spikes, 0.5))
63
+ bc.util.clear_buffer_memory()
64
+
65
+ def test_cc4(self):
66
+ bc.random.seed()
67
+ spikes = bc.random.random((1000, 100)) < 0.2
68
+ print(bt.metric.cross_correlation(spikes, 1.))
69
+ print(bt.metric.cross_correlation(spikes, 0.5))
70
+ bc.util.clear_buffer_memory()
71
+
72
+ def test_cc5(self):
73
+ bc.random.seed()
74
+ spikes = bc.random.random((1000, 100)) < 0.05
75
+ print(bt.metric.cross_correlation(spikes, 1.))
76
+ print(bt.metric.cross_correlation(spikes, 0.5))
77
+ bc.util.clear_buffer_memory()
78
+
79
+
80
+ class TestVoltageFluctuation(unittest.TestCase):
81
+ def test_vf1(self):
82
+ bc.random.seed()
83
+ voltages = bc.random.normal(0, 10, size=(100, 10))
84
+ print(bt.metric.voltage_fluctuation(voltages))
85
+
86
+ with bc.environ.context(precision=64):
87
+ voltages = jnp.ones((100, 10))
88
+ r1 = bt.metric.voltage_fluctuation(voltages)
89
+
90
+ jit_f = jit(partial(bt.metric.voltage_fluctuation))
91
+ jit_f = jit(lambda a: bt.metric.voltage_fluctuation(a))
92
+ r2 = jit_f(voltages)
93
+ print(r1, r2) # TODO: JIT results are different?
94
+ # self.assertTrue(r1 == r2)
95
+
96
+ bc.util.clear_buffer_memory()
97
+
98
+
99
+ class TestFunctionalConnectivity(unittest.TestCase):
100
+ def test_cf1(self):
101
+ bc.random.seed()
102
+ act = bc.random.random((10000, 3))
103
+ r1 = bt.metric.functional_connectivity(act)
104
+
105
+ jit_f = jit(partial(bt.metric.functional_connectivity))
106
+ r2 = jit_f(act)
107
+
108
+ self.assertTrue(jnp.allclose(r1, r2))
109
+ bc.util.clear_buffer_memory()
110
+
111
+
112
+ class TestMatrixCorrelation(unittest.TestCase):
113
+ def test_mc(self):
114
+ bc.random.seed()
115
+ A = bc.random.random((100, 100))
116
+ B = bc.random.random((100, 100))
117
+ r1 = (bt.metric.matrix_correlation(A, B))
118
+
119
+ jit_f = jit(partial(bt.metric.matrix_correlation))
120
+ r2 = jit_f(A, B)
121
+ self.assertTrue(jnp.allclose(r1, r2))
122
+ bc.util.clear_buffer_memory()
123
+
124
+
@@ -0,0 +1,67 @@
1
+ # Copyright 2024 BrainPy Ecosystem Limited. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ """Fenchel-Young losses."""
17
+
18
+ from typing import Any, Protocol
19
+
20
+ import jax.numpy as jnp
21
+
22
+ __all__ = [
23
+ "make_fenchel_young_loss",
24
+ ]
25
+
26
+
27
+ class MaxFun(Protocol):
28
+
29
+ def __call__(self, scores, *args, **kwargs: Any):
30
+ ...
31
+
32
+
33
+ def make_fenchel_young_loss(
34
+ max_fun: MaxFun
35
+ ):
36
+ """Creates a Fenchel-Young loss from a max function.
37
+
38
+ WARNING: The resulting loss accepts an arbitrary number of leading dimensions
39
+ with the fy_loss operating over the last dimension. The jaxopt version of this
40
+ function would instead flatten any vector in a single big 1D vector.
41
+
42
+ Examples:
43
+ Given a max function, e.g., the log sum exp, you can construct a
44
+ Fenchel-Young loss easily as follows:
45
+
46
+ >>> from jax.scipy.special import logsumexp
47
+ >>> fy_loss = make_fy_loss(max_fun=logsumexp)
48
+
49
+ Reference:
50
+ Blondel et al. `Learning with Fenchel-Young Losses
51
+ <https://arxiv.org/pdf/1901.02324.pdf>`_, 2020
52
+
53
+ Args:
54
+ max_fun: the max function on which the Fenchel-Young loss is built.
55
+
56
+ Returns:
57
+ A Fenchel-Young loss function with the same signature.
58
+ """
59
+
60
+ vdot_last_dim = jnp.vectorize(jnp.vdot, signature="(n),(n)->()")
61
+ max_fun_last_dim = jnp.vectorize(max_fun, signature="(n)->()")
62
+
63
+ def fenchel_young_loss(scores, targets, *args, **kwargs):
64
+ max_value = max_fun_last_dim(scores, *args, **kwargs)
65
+ return max_value - vdot_last_dim(targets, scores)
66
+
67
+ return fenchel_young_loss
@@ -0,0 +1,55 @@
1
+ # Copyright 2024 BrainPy Ecosystem Limited. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ========================================================================
15
+
16
+ import jax
17
+ import jax.numpy as jnp
18
+ from absl.testing import parameterized
19
+ from jax.scipy.special import logsumexp
20
+
21
+ from braintools.metric import _classification
22
+ from braintools.metric import _fenchel_young
23
+
24
+
25
+ def one_hot_argmax(inputs: jnp.ndarray) -> jnp.ndarray:
26
+ """An argmax one-hot function for arbitrary shapes."""
27
+ inputs_flat = jnp.reshape(inputs, (-1))
28
+ flat_one_hot = jax.nn.one_hot(jnp.argmax(inputs_flat), inputs_flat.shape[0])
29
+ return jnp.reshape(flat_one_hot, inputs.shape)
30
+
31
+
32
+ class FenchelYoungTest(parameterized.TestCase):
33
+
34
+ def test_fenchel_young_reg(self):
35
+ # Checks the behavior of the Fenchel-Young loss.
36
+ fy_loss = (_fenchel_young.make_fenchel_young_loss(logsumexp))
37
+ rng = jax.random.PRNGKey(0)
38
+ rngs = jax.random.split(rng, 2)
39
+ theta_true = jax.random.uniform(rngs[0], (8, 5))
40
+ y_true = jax.vmap(jax.nn.softmax)(theta_true)
41
+ theta_random = jax.random.uniform(rngs[1], (8, 5))
42
+ y_random = jax.vmap(jax.nn.softmax)(theta_random)
43
+ grad_random = jax.vmap(jax.grad(fy_loss))(theta_random, y_true)
44
+ # Checks that the gradient of the loss takes the correct form.
45
+ self.assertTrue(jnp.allclose(grad_random, y_random - y_true, rtol=1e-4))
46
+ y_one_hot = jax.vmap(one_hot_argmax)(theta_true)
47
+ int_one_hot = jnp.where(y_one_hot == 1.)[1]
48
+ loss_one_hot = jax.vmap(fy_loss)(theta_random, y_one_hot)
49
+ log_loss = jax.vmap(_classification.softmax_cross_entropy_with_integer_labels)(theta_random, int_one_hot)
50
+ # Checks that the FY loss associated to logsumexp is correct.
51
+ self.assertTrue(jnp.allclose(loss_one_hot, log_loss, rtol=1e-4))
52
+ # Checks that vmapping or not is equivalent.
53
+ loss_one_hot_no_vmap = fy_loss(theta_random, y_one_hot)
54
+ self.assertTrue(jnp.allclose(loss_one_hot, loss_one_hot_no_vmap, rtol=1e-4))
55
+
@@ -0,0 +1,85 @@
1
+ # Copyright 2024- BrainPy Ecosystem Limited. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ # -*- coding: utf-8 -*-
17
+
18
+ from typing import Union
19
+ import numpy as onp
20
+ import jax.numpy as jnp
21
+ import braincore as bc
22
+
23
+
24
+ __all__ = [
25
+ 'raster_plot',
26
+ 'firing_rate',
27
+ ]
28
+
29
+
30
+ def raster_plot(sp_matrix: bc.typing.ArrayLike,
31
+ times: bc.typing.ArrayLike):
32
+ """Get spike raster plot which displays the spiking activity
33
+ of a group of neurons over time.
34
+
35
+ Parameters
36
+ ----------
37
+ sp_matrix : bnp.ndarray
38
+ The matrix which record spiking activities.
39
+ times : bnp.ndarray
40
+ The time steps.
41
+
42
+ Returns
43
+ -------
44
+ raster_plot : tuple
45
+ Include (neuron index, spike time).
46
+ """
47
+ times = onp.asarray(times)
48
+ elements = onp.where(sp_matrix > 0.)
49
+ index = elements[1]
50
+ time = times[elements[0]]
51
+ return index, time
52
+
53
+
54
+ def firing_rate(spikes: bc.typing.ArrayLike,
55
+ width: Union[int, float],
56
+ dt: Union[int, float]=None):
57
+ r"""Calculate the mean firing rate over in a neuron group.
58
+
59
+ This method is adopted from Brian2.
60
+
61
+ The firing rate in trial :math:`k` is the spike count :math:`n_{k}^{sp}`
62
+ in an interval of duration :math:`T` divided by :math:`T`:
63
+
64
+ .. math::
65
+
66
+ v_k = {n_k^{sp} \over T}
67
+
68
+ Parameters
69
+ ----------
70
+ spikes : ndarray
71
+ The spike matrix which record spiking activities.
72
+ width : int, float
73
+ The width of the ``window`` in millisecond.
74
+ dt : float, optional
75
+ The sample rate.
76
+
77
+ Returns
78
+ -------
79
+ rate : ndarray
80
+ The population rate in Hz, smoothed with the given window.
81
+ """
82
+ dt = bc.environ.get_dt() if (dt is None) else dt
83
+ width1 = int(width / 2 / dt) * 2 + 1
84
+ window = jnp.ones(width1) * 1000 / width
85
+ return jnp.convolve(jnp.mean(spikes, axis=1), window, mode='same')
@@ -0,0 +1,41 @@
1
+ # Copyright 2024- BrainPy Ecosystem Limited. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ # ==============================================================================
15
+
16
+ # -*- coding: utf-8 -*-
17
+
18
+
19
+ import unittest
20
+ import braincore as bc
21
+ import jax.numpy as jnp
22
+ import braintools as bt
23
+
24
+
25
+ class TestFiringRate(unittest.TestCase):
26
+ def test_fr1(self):
27
+ spikes = jnp.ones((1000, 10))
28
+ print(bt.metric.firing_rate(spikes, 1.))
29
+
30
+ def test_fr2(self):
31
+ bc.random.seed()
32
+ spikes = bc.random.random((1000, 10)) < 0.2
33
+ print(bt.metric.firing_rate(spikes, 1.))
34
+ print(bt.metric.firing_rate(spikes, 10.))
35
+
36
+ def test_fr3(self):
37
+ bc.random.seed()
38
+ spikes = bc.random.random((1000, 10)) < 0.02
39
+ print(bt.metric.firing_rate(spikes, 1.))
40
+ print(bt.metric.firing_rate(spikes, 5.))
41
+