From 532e1ea3438bc795d2f9bf450cd65905146bba6c Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:31:44 +0200 Subject: [PATCH 01/30] Add tutorial --- doc/tutorial/index.rst | 1 + doc/tutorial/plasticity.rst | 76 + python/example/plasticity/03-rates.svg | 2011 +++++++++++++++++++ python/example/plasticity/homeostasis.py | 115 ++ python/example/plasticity/random_network.py | 96 + python/example/plasticity/unconnected.py | 82 + 6 files changed, 2381 insertions(+) create mode 100644 doc/tutorial/plasticity.rst create mode 100644 python/example/plasticity/03-rates.svg create mode 100644 python/example/plasticity/homeostasis.py create mode 100644 python/example/plasticity/random_network.py create mode 100644 python/example/plasticity/unconnected.py diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst index 8ef283e2d..3adb1567c 100644 --- a/doc/tutorial/index.rst +++ b/doc/tutorial/index.rst @@ -78,6 +78,7 @@ Advanced :maxdepth: 1 nmodl + plasticity Demonstrations -------------- diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst new file mode 100644 index 000000000..bc9a060d0 --- /dev/null +++ b/doc/tutorial/plasticity.rst @@ -0,0 +1,76 @@ +.. _tutorial_plasticity: + +In this tutorial, we are going to demonstrate how a network can be built using +plasticity and homeostatic connection rules. Despite not playing towards Arbor's +strengths, we choose a LIF (Leaky Integrate and Fire) neuron model, as we are +primarily interested in examining the required scaffolding. + +We will build up the simulation in stages, starting with an unconnected network +and finishing with a dynamically built connectome. + +An Unconnected Network +---------------------- + +Consider a collection of ``N`` LIF cells. This will be the starting point for +our exploration. For now, we set up each cell with a Poissonian input such that +it will produce spikes periodically at a low frequency. + +The Python file ``01-setup.py`` is the scaffolding we will build our simulation +around and thus contains some passages that might seem redundant now, but will +be helpful in later steps. + +We begin by defining the global settings + +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 9-17 + +- ``T`` is the total runtime of the simulation in ``ms`` +- ``dT`` defines the _interval_ such that the simulation is advance in discrete + steps ``[0, dT, 2 dT, ..., T]``. Later, this will be the timescale of + plasticity. +- ``dt`` is the numerical timestep on which cells evolve + +These parameters are used here + +.. literalinclude:: ../../python/example/plasticity/step-01.py + :language: python + +Next, we define the ``recipe`` used to describe the 'network' which is currently +unconnected. + +We also proceed to add spike recording and generating raster/rate plots. + +A Randomly Wired Network +------------------------ + +We use inheritance to derive a new recipe that contains all the functionality of +the ``unconnected`` recipe. We add a random connectivity matrix during +construction, fixed connection weights, and deliver the resulting connections +via the callback, with the only extra consideration of allowing multiple +connections between two neurons. + +Adding Homeostasis +------------------ + +Under the homeostatic model, each cell was a setpoint for the firing rate :math:`\nu^*` +which is used to determine the creation or destruction of synaptic connections via + +.. math:: + + \frac{dC}{dt} = \alpha(\nu - \nu^*) + +Thus we need to add some extra information to our simulation; namely the +setpoint :math:`\nu^*_i` for each neuron :math:`i` and the sensitivity parameter +:math:`\alpha`. We will also use a simplified version of the differential +equation above, namely adding/deleting exactly one connection if the difference +of observed to desired spiking frequency exceeds :math:`\pm\alpha`. This is both +for simplicity and to avoid sudden changes in the network structure. + +We do this by tweaking the connection table in between calls to ``run``. In +particular, we walk the potential pairings of targets and sources in random +order and check whether the targets requires adding or removing connections. If +we find an option to fulfill that requirement, we do so and proceed to the next +target. The randomization is important here, espcially for adding connections as +to avoid biases, in particular when there are too few eglible connection +partners. diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg new file mode 100644 index 000000000..c0cbdde28 --- /dev/null +++ b/python/example/plasticity/03-rates.svg @@ -0,0 +1,2011 @@ + + + + + + + + 2024-10-02T20:06:06.006052 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py new file mode 100644 index 000000000..b296c862d --- /dev/null +++ b/python/example/plasticity/homeostasis.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +import arbor as A +from arbor import units as U +from typing import Any +import matplotlib.pyplot as plt +from scipy.signal import savgol_filter +import numpy as np + +from random_network import random_network + +# global parameters +# total runtime [ms] +T = 10000 +# one interval [ms] +dT = 100 +# number of intervals +nT = int((T + dT - 1)//dT) +# numerical time step [ms] +dt = 0.1 + +def randrange(n: int): + res = np.arange(n, dtype=int) + np.random.shuffle(res) + return res + +np.random.seed = 23 + +class homeostatic_network(random_network): + + def __init__(self, N) -> None: + super().__init__(N) + self.max_inc = 8 + self.max_out = 8 + # setpoint rate in kHz + self.setpoint = 0.1 + # sensitivty towards deviations from setpoint + self.alpha = 200 + + +if __name__ == "__main__": + rec = homeostatic_network(10) + sim = A.simulation(rec) + sim.record(A.spike_recording.all) + + print("Initial network:") + print(rec.inc) + print(rec.out) + print(rec.connections) + + t = 0 + while t < T: + sim.run((t + dT) * U.ms, dt * U.ms) + if t < T/2: + t += dT + continue + n = rec.num_cells() + rates = np.zeros(n) + for (gid, _), time in sim.spikes(): + if time < t: + continue + rates[gid] += 1 + rates /= dT # kHz + dC = ((rec.setpoint - rates)*rec.alpha).astype(int) + unchangeable = set() + added = [] + deled = [] + for tgt in randrange(n): + if dC[tgt] == 0: + continue + for src in randrange(n): + if dC[tgt] > 0 and rec.add_connection(src, tgt): + added.append((src, tgt)) + break + elif dC[tgt] < 0 and rec.del_connection(src, tgt): + deled.append((src, tgt)) + break + unchangeable.add(tgt) + sim.update(rec) + print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") + t += dT + + print("Final network:") + print(rec.inc) + print(rec.out) + print(rec.connections) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(nT, rec.num_cells())) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // dT) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel('Time $(t/ms)$') + ax.set_ylabel('GID') + ax.set_xlim(0, T) + fg.savefig('03-raster.pdf') + + fg, ax = plt.subplots() + ax.plot(np.arange(nT), rates/dT) + ax.plot(np.arange(nT), savgol_filter(rates.mean(axis=1)/dT, window_length=5, polyorder=2), color='0.8', lw=4, label='Mean rate') + ax.axhline(0.1, label='Setpoint', lw=2, c='0.4') + ax.legend() + ax.set_xlabel('Interval') + ax.set_ylabel('Rate $(kHz)$') + ax.set_xlim(0, nT) + fg.savefig('03-rates.pdf') + fg.savefig('03-rates.png') + fg.savefig('03-rates.svg') diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py new file mode 100644 index 000000000..921a1598a --- /dev/null +++ b/python/example/plasticity/random_network.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +import arbor as A +from arbor import units as U +from typing import Any +import matplotlib.pyplot as plt +import numpy as np + +from unconnected import unconnected + +# global parameters +# total runtime [ms] +T = 100 +# one interval [ms] +dT = 10 +# number of intervals +nT = int((T + dT - 1)//dT) +# numerical time step [ms] +dt = 0.1 + +class random_network(unconnected): + + def __init__(self, N) -> None: + super().__init__(N) + self.syn_weight = 80 + self.syn_delay = 0.5 * U.ms + self.max_inc = 4 + self.max_out = 4 + # format [to, from] + self.connections = np.zeros(shape=(N, N), dtype=np.uint8) + self.inc = np.zeros(N, np.uint8) + self.out = np.zeros(N, np.uint8) + + def connections_on(self, gid: int): + return [A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) + for source in range(self.N) + for _ in range(self.connections[gid, source]) + ] + + def add_connection(self, src: int, tgt: int) -> bool: + if tgt == src or self.inc[tgt] >= self.max_inc or self.out[src] >= self.max_out: + return False + self.inc[tgt] += 1 + self.out[src] += 1 + self.connections[tgt, src] += 1 + return True + + def del_connection(self, src: int, tgt: int) -> bool: + if tgt == src or self.connections[tgt, src] <= 0: + return False + self.inc[tgt] -= 1 + self.out[src] -= 1 + self.connections[tgt, src] -= 1 + return True + + def rewire(self): + tries = self.N*self.N*self.max_inc*self.max_out + while tries > 0 and self.inc.sum() < self.N*self.max_inc and self.out.sum() < self.N*self.max_out: + src, tgt = np.random.randint(self.N, size=2, dtype=int) + self.add_connection(src, tgt) + tries -= 1 + + +if __name__ == "__main__": + rec = random(10) + rec.rewire() + sim = A.simulation(rec) + sim.record(A.spike_recording.all) + t = 0 + while t < T: + t += dT + sim.run(t * U.ms, dt * U.ms) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(nT, rec.num_cells())) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // dT) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel('Time $(t/ms)$') + ax.set_ylabel('GID') + ax.set_xlim(0, T) + fg.savefig('02-raster.pdf') + + fg, ax = plt.subplots() + ax.plot(np.arange(nT), rates) + ax.set_xlabel('Interval') + ax.set_ylabel('Rate $(kHz)$') + ax.set_xlim(0, nT) + fg.savefig('02-rates.pdf') diff --git a/python/example/plasticity/unconnected.py b/python/example/plasticity/unconnected.py new file mode 100644 index 000000000..78b848ca7 --- /dev/null +++ b/python/example/plasticity/unconnected.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +import arbor as A +from arbor import units as U +from typing import Any +import matplotlib.pyplot as plt +import numpy as np + +# global parameters +# total runtime [ms] +T = 100 +# one interval [ms] +dT = 10 +# number of intervals +nT = int((T + dT - 1)//dT) +# numerical time step [ms] +dt = 0.1 + +class unconnected(A.recipe): + + def __init__(self, N) -> None: + super().__init__() + self.N = N + # Cell prototype + self.cell = A.lif_cell("src", "tgt") + # random seed [0, 100] + self.seed = 42 + # event generator parameters + self.gen_weight = 20 + self.gen_freq = 1 * U.kHz + + def num_cells(self) -> int: + return self.N + + def event_generators(self, gid: int) -> list[Any]: + return [A.event_generator("tgt", + self.gen_weight, + A.poisson_schedule(freq=self.gen_freq, + seed=self.cell_seed(gid)))] + + def cell_description(self, gid: int) -> Any: + return self.cell + + def cell_kind(self, gid: int) -> A.cell_kind: + return A.cell_kind.lif + + def cell_seed(self, gid): + return self.seed + gid*100 + +if __name__ == "__main__": + rec = unconnected(10) + sim = A.simulation(rec) + sim.record(A.spike_recording.all) + + t = 0 + while t < T: + t += dT + sim.run(t * U.ms, dt * U.ms) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(nT, rec.num_cells())) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // dT) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel('Time $(t/ms)$') + ax.set_ylabel('GID') + ax.set_xlim(0, T) + fg.savefig('01-raster.pdf') + + fg, ax = plt.subplots() + ax.plot(np.arange(nT), rates) + ax.set_xlabel('Interval') + ax.set_ylabel('Rate $(kHz)$') + ax.set_xlim(0, nT) + fg.savefig('01-rates.pdf') From dbe7b518e46ccd05cb66b5d86f26c9b444aec115 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:43:17 +0200 Subject: [PATCH 02/30] Begin code snippets. --- doc/tutorial/plasticity.rst | 98 +- python/example/plasticity/01-raster.svg | 1863 ++++++++++++++ python/example/plasticity/01-rates.svg | 2107 ++++++++++++++++ python/example/plasticity/03-rates.svg | 2404 ++++++++++--------- python/example/plasticity/homeostasis.py | 54 +- python/example/plasticity/random_network.py | 62 +- python/example/plasticity/unconnected.py | 62 +- python/example/plasticity/util.py | 45 + 8 files changed, 5404 insertions(+), 1291 deletions(-) create mode 100644 python/example/plasticity/01-raster.svg create mode 100644 python/example/plasticity/01-rates.svg create mode 100644 python/example/plasticity/util.py diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index bc9a060d0..d10c079c6 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -23,32 +23,110 @@ We begin by defining the global settings .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python - :lines: 9-17 + :lines: 7-15 +- ``N`` is the cell count of the simulation - ``T`` is the total runtime of the simulation in ``ms`` -- ``dT`` defines the _interval_ such that the simulation is advance in discrete - steps ``[0, dT, 2 dT, ..., T]``. Later, this will be the timescale of +- ``t_interval`` defines the _interval_ such that the simulation is advance in + discrete steps ``[0, 1, 2, ...] t_interval``. Later, this will be the timescale of plasticity. - ``dt`` is the numerical timestep on which cells evolve These parameters are used here -.. literalinclude:: ../../python/example/plasticity/step-01.py +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 52-62 + +where we run the simulation in increments of ``t_interval``. + +Back to the recipe; we set a prototypical cell + +.. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python + :lines: 23 -Next, we define the ``recipe`` used to describe the 'network' which is currently -unconnected. +and deliver it for all ``gid`` s -We also proceed to add spike recording and generating raster/rate plots. +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 42-43 + +Also, each cell has an event generator attached + +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 33-40 + +using a Poisson point process seeded with the cell's ``gid``. All other +parameters are set in the constructor + +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 19-28 + +We also proceed to add spike recording and generate plots using a helper +function ``plot_spikes`` from ``util.py``. You can skip the following details +for now and come back later if you are interested how it works. We generate +raster plots via ``scatter``. Rates are computed by binning spikes into +``t_interval`` and the neuron id; the mean rate is the average across the +neurons smoothed using a Savitzky-Golay filter (``scipy.signal.savgol_filter``). +We plot per-neuron and mean rates. A Randomly Wired Network ------------------------ We use inheritance to derive a new recipe that contains all the functionality of -the ``unconnected`` recipe. We add a random connectivity matrix during +the ```unconnected`` recipe. We then add a random connectivity matrix during construction, fixed connection weights, and deliver the resulting connections -via the callback, with the only extra consideration of allowing multiple -connections between two neurons. +via the ``connections_on`` callback, with the only extra consideration of +allowing multiple connections between two neurons. + +In detail, the recipe stores the connection matrix, the current +incoming/outgoing connections per neuron, and the maximum for both directions + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 26-31 + +The connection matrix is used to construct connections + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 33-38 + +together with the fixed connection parameters + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 24-25 + +We define helper functions ``add|del_connections`` to manipulate the connection +table while upholding these invariants: + +- no self-connections, i.e. ``connection[i, i] == 0`` +- ``inc[i]`` the sum of ``connections[:, i]`` +- no more incoming connections than allowed by ``max_inc``, i.e. ``inc[i] <= max_inc`` +- ``out[i]`` the sum of ``connections[i, :]`` +- no more outgoing connections than allowed by ``max_out``, i.e. ``out[i] <= max_out`` + +These methods return ``True`` on success and ``False`` otherwise + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 40-54 + +Both are used in ``rewire`` to produce a random connection matrix + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 56-65 + +We then proceed to run the simulation and plot the results as before + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 68-79 Adding Homeostasis ------------------ diff --git a/python/example/plasticity/01-raster.svg b/python/example/plasticity/01-raster.svg new file mode 100644 index 000000000..7b4af213b --- /dev/null +++ b/python/example/plasticity/01-raster.svg @@ -0,0 +1,1863 @@ + + + + + + + + 2024-10-08T09:16:17.177252 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/01-rates.svg b/python/example/plasticity/01-rates.svg new file mode 100644 index 000000000..67fd888a3 --- /dev/null +++ b/python/example/plasticity/01-rates.svg @@ -0,0 +1,2107 @@ + + + + + + + + 2024-10-08T09:16:17.364183 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg index c0cbdde28..c6c97fc74 100644 --- a/python/example/plasticity/03-rates.svg +++ b/python/example/plasticity/03-rates.svg @@ -6,7 +6,7 @@ - 2024-10-02T20:06:06.006052 + 2024-10-03T11:36:12.842254 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,7 +82,7 @@ z - + @@ -122,7 +122,7 @@ z - + @@ -157,7 +157,7 @@ z - + @@ -203,7 +203,7 @@ z - + @@ -258,7 +258,7 @@ z - + @@ -445,12 +445,12 @@ z - - + @@ -475,12 +475,12 @@ z - + - + - + - + @@ -534,12 +534,12 @@ z - + - + @@ -550,12 +550,12 @@ z - + - + @@ -563,7 +563,89 @@ z - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -678,1143 +760,1143 @@ z - - - - - - - - - - + - + - + - + - + - + - + - + - + + + + + + + + + + - - - + - + - + - - + - + - + + diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py index b296c862d..c15970340 100644 --- a/python/example/plasticity/homeostasis.py +++ b/python/example/plasticity/homeostasis.py @@ -4,30 +4,30 @@ from arbor import units as U from typing import Any import matplotlib.pyplot as plt -from scipy.signal import savgol_filter import numpy as np +from util import plot_spikes from random_network import random_network # global parameters # total runtime [ms] T = 10000 # one interval [ms] -dT = 100 -# number of intervals -nT = int((T + dT - 1)//dT) +t_interval = 100 # numerical time step [ms] dt = 0.1 + def randrange(n: int): res = np.arange(n, dtype=int) np.random.shuffle(res) return res + np.random.seed = 23 -class homeostatic_network(random_network): +class homeostatic_network(random_network): def __init__(self, N) -> None: super().__init__(N) self.max_inc = 8 @@ -50,9 +50,9 @@ def __init__(self, N) -> None: t = 0 while t < T: - sim.run((t + dT) * U.ms, dt * U.ms) - if t < T/2: - t += dT + sim.run((t + t_interval) * U.ms, dt * U.ms) + if t < T / 2: + t += t_interval continue n = rec.num_cells() rates = np.zeros(n) @@ -60,8 +60,8 @@ def __init__(self, N) -> None: if time < t: continue rates[gid] += 1 - rates /= dT # kHz - dC = ((rec.setpoint - rates)*rec.alpha).astype(int) + rates /= t_interval # kHz + dC = ((rec.setpoint - rates) * rec.alpha).astype(int) unchangeable = set() added = [] deled = [] @@ -78,38 +78,14 @@ def __init__(self, N) -> None: unchangeable.add(tgt) sim.update(rec) print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") - t += dT + t += t_interval print("Final network:") print(rec.inc) print(rec.out) print(rec.connections) - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(nT, rec.num_cells())) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // dT) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel('Time $(t/ms)$') - ax.set_ylabel('GID') - ax.set_xlim(0, T) - fg.savefig('03-raster.pdf') - - fg, ax = plt.subplots() - ax.plot(np.arange(nT), rates/dT) - ax.plot(np.arange(nT), savgol_filter(rates.mean(axis=1)/dT, window_length=5, polyorder=2), color='0.8', lw=4, label='Mean rate') - ax.axhline(0.1, label='Setpoint', lw=2, c='0.4') - ax.legend() - ax.set_xlabel('Interval') - ax.set_ylabel('Rate $(kHz)$') - ax.set_xlim(0, nT) - fg.savefig('03-rates.pdf') - fg.savefig('03-rates.png') - fg.savefig('03-rates.svg') + plot_spikes( + sim, + rec.num_cells(), + ) diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py index 921a1598a..84625afee 100644 --- a/python/example/plasticity/random_network.py +++ b/python/example/plasticity/random_network.py @@ -2,40 +2,40 @@ import arbor as A from arbor import units as U -from typing import Any -import matplotlib.pyplot as plt import numpy as np +from util import plot_spikes, plot_network from unconnected import unconnected # global parameters +# cell count +N = 10 # total runtime [ms] -T = 100 +T = 1000 # one interval [ms] -dT = 10 -# number of intervals -nT = int((T + dT - 1)//dT) +t_interval = 10 # numerical time step [ms] dt = 0.1 -class random_network(unconnected): +class random_network(unconnected): def __init__(self, N) -> None: super().__init__(N) self.syn_weight = 80 self.syn_delay = 0.5 * U.ms - self.max_inc = 4 - self.max_out = 4 # format [to, from] self.connections = np.zeros(shape=(N, N), dtype=np.uint8) self.inc = np.zeros(N, np.uint8) self.out = np.zeros(N, np.uint8) + self.max_inc = 4 + self.max_out = 4 def connections_on(self, gid: int): - return [A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) - for source in range(self.N) - for _ in range(self.connections[gid, source]) - ] + return [ + A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) + for source in range(self.N) + for _ in range(self.connections[gid, source]) + ] def add_connection(self, src: int, tgt: int) -> bool: if tgt == src or self.inc[tgt] >= self.max_inc or self.out[src] >= self.max_out: @@ -54,43 +54,25 @@ def del_connection(self, src: int, tgt: int) -> bool: return True def rewire(self): - tries = self.N*self.N*self.max_inc*self.max_out - while tries > 0 and self.inc.sum() < self.N*self.max_inc and self.out.sum() < self.N*self.max_out: + tries = self.N * self.N * self.max_inc * self.max_out + while ( + tries > 0 + and self.inc.sum() < self.N * self.max_inc + and self.out.sum() < self.N * self.max_out + ): src, tgt = np.random.randint(self.N, size=2, dtype=int) self.add_connection(src, tgt) tries -= 1 if __name__ == "__main__": - rec = random(10) + rec = random_network(10) rec.rewire() sim = A.simulation(rec) sim.record(A.spike_recording.all) t = 0 while t < T: - t += dT + t += t_interval sim.run(t * U.ms, dt * U.ms) - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(nT, rec.num_cells())) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // dT) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel('Time $(t/ms)$') - ax.set_ylabel('GID') - ax.set_xlim(0, T) - fg.savefig('02-raster.pdf') - - fg, ax = plt.subplots() - ax.plot(np.arange(nT), rates) - ax.set_xlabel('Interval') - ax.set_ylabel('Rate $(kHz)$') - ax.set_xlim(0, nT) - fg.savefig('02-rates.pdf') + plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="02-") diff --git a/python/example/plasticity/unconnected.py b/python/example/plasticity/unconnected.py index 78b848ca7..e9a07ec99 100644 --- a/python/example/plasticity/unconnected.py +++ b/python/example/plasticity/unconnected.py @@ -2,22 +2,20 @@ import arbor as A from arbor import units as U -from typing import Any -import matplotlib.pyplot as plt -import numpy as np +from util import plot_spikes # global parameters +# cell count +N = 10 # total runtime [ms] -T = 100 +T = 1000 # one interval [ms] -dT = 10 -# number of intervals -nT = int((T + dT - 1)//dT) +t_interval = 10 # numerical time step [ms] dt = 0.1 -class unconnected(A.recipe): +class unconnected(A.recipe): def __init__(self, N) -> None: super().__init__() self.N = N @@ -32,51 +30,33 @@ def __init__(self, N) -> None: def num_cells(self) -> int: return self.N - def event_generators(self, gid: int) -> list[Any]: - return [A.event_generator("tgt", - self.gen_weight, - A.poisson_schedule(freq=self.gen_freq, - seed=self.cell_seed(gid)))] + def event_generators(self, gid: int): + return [ + A.event_generator( + "tgt", + self.gen_weight, + A.poisson_schedule(freq=self.gen_freq, seed=self.cell_seed(gid)), + ) + ] - def cell_description(self, gid: int) -> Any: + def cell_description(self, gid: int): return self.cell def cell_kind(self, gid: int) -> A.cell_kind: return A.cell_kind.lif - def cell_seed(self, gid): - return self.seed + gid*100 + def cell_seed(self, gid: int): + return self.seed + gid * 100 + if __name__ == "__main__": - rec = unconnected(10) + rec = unconnected(N) sim = A.simulation(rec) sim.record(A.spike_recording.all) t = 0 while t < T: - t += dT + t += t_interval sim.run(t * U.ms, dt * U.ms) - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(nT, rec.num_cells())) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // dT) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel('Time $(t/ms)$') - ax.set_ylabel('GID') - ax.set_xlim(0, T) - fg.savefig('01-raster.pdf') - - fg, ax = plt.subplots() - ax.plot(np.arange(nT), rates) - ax.set_xlabel('Interval') - ax.set_ylabel('Rate $(kHz)$') - ax.set_xlim(0, nT) - fg.savefig('01-rates.pdf') + plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="01-") diff --git a/python/example/plasticity/util.py b/python/example/plasticity/util.py new file mode 100644 index 000000000..a089fc626 --- /dev/null +++ b/python/example/plasticity/util.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import matplotlib.pyplot as plt +import numpy as np +from scipy.signal import savgol_filter + + +def plot_spikes(sim, n_cells, t_interval, T, prefix=""): + # number of intervals + n_interval = int((T + t_interval - 1) // t_interval) + print(n_interval, T, t_interval) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(n_interval, n_cells)) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // t_interval) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel("Time $(t/ms)$") + ax.set_ylabel("GID") + ax.set_xlim(0, T) + fg.savefig(f"{prefix}raster.pdf") + fg.savefig(f"{prefix}raster.png") + fg.savefig(f"{prefix}raster.svg") + + ts = np.arange(n_interval) * t_interval + mean_rate = savgol_filter( + rates.mean(axis=1) / t_interval, window_length=5, polyorder=2 + ) + fg, ax = plt.subplots() + ax.plot(ts, rates) + ax.plot(ts, mean_rate, color="0.8", lw=4, label="Mean rate") + ax.set_xlabel("Time $(t/ms)$") + ax.legend() + ax.set_ylabel("Rate $(kHz)$") + ax.set_xlim(0, T) + fg.savefig(f"{prefix}rates.pdf") + fg.savefig(f"{prefix}rates.png") + fg.savefig(f"{prefix}rates.svg") From 8292e816ffeba779c2a337a4800a12042ef566d4 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:35:24 +0200 Subject: [PATCH 03/30] Add graph plotting. --- doc/tutorial/plasticity.rst | 85 +- python/example/plasticity/01-raster.svg | 1592 +- python/example/plasticity/01-rates.svg | 260 +- python/example/plasticity/02-graph.svg | 808 + python/example/plasticity/02-matrix.svg | 388 + python/example/plasticity/02-raster.svg | 4005 ++++ python/example/plasticity/02-rates.svg | 2090 ++ python/example/plasticity/03-final-graph.svg | 1208 + python/example/plasticity/03-final-matrix.svg | 388 + .../example/plasticity/03-initial-graph.svg | 408 + .../example/plasticity/03-initial-matrix.svg | 388 + python/example/plasticity/03-raster.svg | 19409 ++++++++++++++++ python/example/plasticity/03-rates.svg | 2982 ++- python/example/plasticity/homeostasis.py | 52 +- python/example/plasticity/random_network.py | 3 +- python/example/plasticity/util.py | 29 +- 16 files changed, 31593 insertions(+), 2502 deletions(-) create mode 100644 python/example/plasticity/02-graph.svg create mode 100644 python/example/plasticity/02-matrix.svg create mode 100644 python/example/plasticity/02-raster.svg create mode 100644 python/example/plasticity/02-rates.svg create mode 100644 python/example/plasticity/03-final-graph.svg create mode 100644 python/example/plasticity/03-final-matrix.svg create mode 100644 python/example/plasticity/03-initial-graph.svg create mode 100644 python/example/plasticity/03-initial-matrix.svg create mode 100644 python/example/plasticity/03-raster.svg diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index d10c079c6..7aa3028f1 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -8,8 +8,18 @@ primarily interested in examining the required scaffolding. We will build up the simulation in stages, starting with an unconnected network and finishing with a dynamically built connectome. -An Unconnected Network ----------------------- +.. admonition:: Concepts and Requirements + + We cover some advanced topics in this tutorial, mainly structural + plasticity. Please refer to other tutorials for the basics of network + building. The model employed here --- storing an explicit connection matrix + --- is not advisable in most scenarios. + + In addition to Arbor and its requirements, ``scipy``, ``matplotlib``, and + ``networkx`` need to be installed. + +Unconnected Network +------------------- Consider a collection of ``N`` LIF cells. This will be the starting point for our exploration. For now, we set up each cell with a Poissonian input such that @@ -128,6 +138,9 @@ We then proceed to run the simulation and plot the results as before :language: python :lines: 68-79 +Note that we added a plot of the network connectivity using ``plot_network`` +from ``util`` as well. This generates images of the graph and connection matrix. + Adding Homeostasis ------------------ @@ -145,10 +158,64 @@ equation above, namely adding/deleting exactly one connection if the difference of observed to desired spiking frequency exceeds :math:`\pm\alpha`. This is both for simplicity and to avoid sudden changes in the network structure. -We do this by tweaking the connection table in between calls to ``run``. In -particular, we walk the potential pairings of targets and sources in random -order and check whether the targets requires adding or removing connections. If -we find an option to fulfill that requirement, we do so and proceed to the next -target. The randomization is important here, espcially for adding connections as -to avoid biases, in particular when there are too few eglible connection -partners. +As before, we set up global parameters + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 10-24 + +and prepare our simulation + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 37-39 + +Note that our new recipe is almost unaltered from the random network + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 27-33 + +all changes are contained to the way we run the simulation. To add a further +interesting feature, we skip the rewiring for the first half of the simulation. + +Plasticity is implemented by tweaking the connection table inside the recipe +between calls to ``run`` and calling ``simulation.update`` with the modified +recipe: + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 70 + +Changes are based on the difference of current rate we compute from the spikes +during the last interval + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 49-54 + +and the setpoint times the sensitivity + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 55 + +Then, each potential pairing of target and source is checked in random +order for whether adding or removing a connection is required + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 59-68 + +If we find an option to fulfill that requirement, we do so and proceed to the +next target. The randomization is important here, espcially for adding +connections as to avoid biases, in particular when there are too few eglible +connection partners. The ``randrange`` function produces a shuffled range ``[0, +N)``. We leverage the helper functions from the random network recipe to +manipulate the connection table, see the discussion above. + +Finally, we plot networks and spikes as before + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 74-75 diff --git a/python/example/plasticity/01-raster.svg b/python/example/plasticity/01-raster.svg index 7b4af213b..11b1a2fbd 100644 --- a/python/example/plasticity/01-raster.svg +++ b/python/example/plasticity/01-raster.svg @@ -6,7 +6,7 @@ - 2024-10-08T09:16:17.177252 + 2024-10-08T11:34:50.435338 image/svg+xml @@ -39,7 +39,7 @@ z - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + @@ -1267,7 +1267,7 @@ z - + @@ -1308,7 +1308,7 @@ z - + @@ -1344,7 +1344,7 @@ z - + @@ -1391,7 +1391,7 @@ z - + @@ -1447,7 +1447,7 @@ z - + @@ -1704,12 +1704,12 @@ z - - + @@ -1722,7 +1722,7 @@ L -3.5 0 - + @@ -1735,7 +1735,7 @@ L -3.5 0 - + @@ -1748,7 +1748,7 @@ L -3.5 0 - + @@ -1761,7 +1761,7 @@ L -3.5 0 - + @@ -1856,7 +1856,7 @@ L 414.72 41.472 - + diff --git a/python/example/plasticity/01-rates.svg b/python/example/plasticity/01-rates.svg index 67fd888a3..087291dbb 100644 --- a/python/example/plasticity/01-rates.svg +++ b/python/example/plasticity/01-rates.svg @@ -6,7 +6,7 @@ - 2024-10-08T09:16:17.364183 + 2024-10-08T11:34:50.626245 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,7 +82,7 @@ z - + @@ -123,7 +123,7 @@ z - + @@ -159,7 +159,7 @@ z - + @@ -206,7 +206,7 @@ z - + @@ -262,7 +262,7 @@ z - + @@ -519,12 +519,12 @@ z - - + @@ -549,7 +549,7 @@ z - + @@ -592,7 +592,7 @@ z - + @@ -608,7 +608,7 @@ z - + @@ -636,7 +636,7 @@ z - + @@ -652,7 +652,7 @@ z - + @@ -668,7 +668,7 @@ z - + @@ -684,7 +684,7 @@ z - + @@ -700,7 +700,7 @@ z - + @@ -956,7 +956,7 @@ L 400.4352 174.528 L 404.0064 174.528 L 407.5776 295.488 L 411.1488 295.488 -" clip-path="url(#p4dc5912be8)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #ff7f0e; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #2ca02c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #d62728; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #9467bd; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #8c564b; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #e377c2; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #7f7f7f; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #bcbd22; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #17becf; stroke-width: 1.5; stroke-linecap: square"/> - + + diff --git a/python/example/plasticity/02-graph.svg b/python/example/plasticity/02-graph.svg new file mode 100644 index 000000000..e40f6759e --- /dev/null +++ b/python/example/plasticity/02-graph.svg @@ -0,0 +1,808 @@ + + + + + + + + 2024-10-08T11:34:44.762762 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/02-matrix.svg b/python/example/plasticity/02-matrix.svg new file mode 100644 index 000000000..ce78d1a5e --- /dev/null +++ b/python/example/plasticity/02-matrix.svg @@ -0,0 +1,388 @@ + + + + + + + + 2024-10-08T11:34:44.531402 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/02-raster.svg b/python/example/plasticity/02-raster.svg new file mode 100644 index 000000000..eba26e802 --- /dev/null +++ b/python/example/plasticity/02-raster.svg @@ -0,0 +1,4005 @@ + + + + + + + + 2024-10-08T11:34:44.998033 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/02-rates.svg b/python/example/plasticity/02-rates.svg new file mode 100644 index 000000000..c2b4f9acf --- /dev/null +++ b/python/example/plasticity/02-rates.svg @@ -0,0 +1,2090 @@ + + + + + + + + 2024-10-08T11:34:45.180583 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-final-graph.svg b/python/example/plasticity/03-final-graph.svg new file mode 100644 index 000000000..6b31fdae9 --- /dev/null +++ b/python/example/plasticity/03-final-graph.svg @@ -0,0 +1,1208 @@ + + + + + + + + 2024-10-08T11:34:38.359950 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-final-matrix.svg b/python/example/plasticity/03-final-matrix.svg new file mode 100644 index 000000000..746c6e5d7 --- /dev/null +++ b/python/example/plasticity/03-final-matrix.svg @@ -0,0 +1,388 @@ + + + + + + + + 2024-10-08T11:34:38.085467 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-initial-graph.svg b/python/example/plasticity/03-initial-graph.svg new file mode 100644 index 000000000..f01b66872 --- /dev/null +++ b/python/example/plasticity/03-initial-graph.svg @@ -0,0 +1,408 @@ + + + + + + + + 2024-10-08T11:34:37.211454 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-initial-matrix.svg b/python/example/plasticity/03-initial-matrix.svg new file mode 100644 index 000000000..42c20f7b9 --- /dev/null +++ b/python/example/plasticity/03-initial-matrix.svg @@ -0,0 +1,388 @@ + + + + + + + + 2024-10-08T11:34:37.047482 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-raster.svg b/python/example/plasticity/03-raster.svg new file mode 100644 index 000000000..e091e70cc --- /dev/null +++ b/python/example/plasticity/03-raster.svg @@ -0,0 +1,19409 @@ + + + + + + + + 2024-10-08T11:34:38.815593 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg index c6c97fc74..dbb954ad0 100644 --- a/python/example/plasticity/03-rates.svg +++ b/python/example/plasticity/03-rates.svg @@ -6,7 +6,7 @@ - 2024-10-03T11:36:12.842254 + 2024-10-08T11:34:39.088775 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,12 +82,12 @@ z - + - - + + + + - + - - + + + + - + - - + + + + - + - - + + + + - + - - + + + + - - + + - - + - - + - - + - + - - - - - - - - + + + + + + + + + + + @@ -445,209 +524,104 @@ z - - + - - - - - + + - - - - + - - - - - - - - - + + + - + - - - - - - + + + - + - - - - - - + + + - + - - - - - - + + + - + - - - - - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + - + - - + - @@ -760,1143 +761,1138 @@ z + + + - + - + - + - + - + - + - + - + - - - - - - - +L 232.5888 221.051077 +L 236.16 221.051077 +L 239.7312 183.832615 +L 243.3024 146.614154 +L 246.8736 165.223385 +L 250.4448 128.004923 +L 254.016 146.614154 +L 257.5872 109.395692 +L 261.1584 128.004923 +L 264.7296 109.395692 +L 268.3008 72.177231 +L 271.872 146.614154 +L 275.4432 146.614154 +L 279.0144 128.004923 +L 282.5856 109.395692 +L 286.1568 165.223385 +L 289.728 146.614154 +L 293.2992 128.004923 +L 296.8704 165.223385 +L 300.4416 109.395692 +L 304.0128 146.614154 +L 307.584 146.614154 +L 311.1552 90.786462 +L 314.7264 128.004923 +L 318.2976 128.004923 +L 321.8688 72.177231 +L 325.44 109.395692 +L 329.0112 146.614154 +L 332.5824 165.223385 +L 336.1536 146.614154 +L 339.7248 146.614154 +L 343.296 165.223385 +L 346.8672 128.004923 +L 350.4384 72.177231 +L 354.0096 128.004923 +L 357.5808 146.614154 +L 361.152 146.614154 +L 364.7232 128.004923 +L 368.2944 146.614154 +L 371.8656 146.614154 +L 375.4368 146.614154 +L 379.008 165.223385 +L 382.5792 146.614154 +L 386.1504 128.004923 +L 389.7216 128.004923 +L 393.2928 109.395692 +L 396.864 128.004923 +L 400.4352 128.004923 +L 404.0064 165.223385 +L 407.5776 146.614154 +L 411.1488 146.614154 +" clip-path="url(#p71169c5d0f)" style="fill: none; stroke: #17becf; stroke-width: 1.5; stroke-linecap: square"/> - - + + - - - + - + - + + + @@ -1970,123 +2002,11 @@ z - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py index c15970340..4edf354ee 100644 --- a/python/example/plasticity/homeostasis.py +++ b/python/example/plasticity/homeostasis.py @@ -2,51 +2,43 @@ import arbor as A from arbor import units as U -from typing import Any -import matplotlib.pyplot as plt import numpy as np -from util import plot_spikes +from util import plot_spikes, plot_network, randrange from random_network import random_network # global parameters +# cell count +N = 10 # total runtime [ms] T = 10000 # one interval [ms] t_interval = 100 # numerical time step [ms] dt = 0.1 - - -def randrange(n: int): - res = np.arange(n, dtype=int) - np.random.shuffle(res) - return res - - +# Set seed for numpy np.random.seed = 23 +# setpoint rate in kHz +setpoint_rate = 0.1 +# sensitivty towards deviations from setpoint +sensitivity = 200 class homeostatic_network(random_network): - def __init__(self, N) -> None: + def __init__(self, N, setpoint_rate, sensitivity) -> None: super().__init__(N) self.max_inc = 8 self.max_out = 8 - # setpoint rate in kHz - self.setpoint = 0.1 - # sensitivty towards deviations from setpoint - self.alpha = 200 + self.setpoint = setpoint_rate + self.alpha = sensitivity if __name__ == "__main__": - rec = homeostatic_network(10) + rec = homeostatic_network(N, setpoint_rate, sensitivity) sim = A.simulation(rec) sim.record(A.spike_recording.all) - print("Initial network:") - print(rec.inc) - print(rec.out) - print(rec.connections) + plot_network(rec, prefix="03-initial-") t = 0 while t < T: @@ -54,8 +46,7 @@ def __init__(self, N) -> None: if t < T / 2: t += t_interval continue - n = rec.num_cells() - rates = np.zeros(n) + rates = np.zeros(N) for (gid, _), time in sim.spikes(): if time < t: continue @@ -65,10 +56,10 @@ def __init__(self, N) -> None: unchangeable = set() added = [] deled = [] - for tgt in randrange(n): + for tgt in randrange(N): if dC[tgt] == 0: continue - for src in randrange(n): + for src in randrange(N): if dC[tgt] > 0 and rec.add_connection(src, tgt): added.append((src, tgt)) break @@ -80,12 +71,5 @@ def __init__(self, N) -> None: print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") t += t_interval - print("Final network:") - print(rec.inc) - print(rec.out) - print(rec.connections) - - plot_spikes( - sim, - rec.num_cells(), - ) + plot_network(rec, prefix="03-final-") + plot_spikes(sim, N, t_interval, T, prefix="03-") diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py index 84625afee..cc58c90af 100644 --- a/python/example/plasticity/random_network.py +++ b/python/example/plasticity/random_network.py @@ -75,4 +75,5 @@ def rewire(self): t += t_interval sim.run(t * U.ms, dt * U.ms) - plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="02-") + plot_network(rec, prefix="02-") + plot_spikes(sim, N, t_interval, T, prefix="02-") diff --git a/python/example/plasticity/util.py b/python/example/plasticity/util.py index a089fc626..bb5e2878b 100644 --- a/python/example/plasticity/util.py +++ b/python/example/plasticity/util.py @@ -3,6 +3,27 @@ import matplotlib.pyplot as plt import numpy as np from scipy.signal import savgol_filter +import networkx as nx + +def plot_network(rec, prefix=""): + fg, ax = plt.subplots() + ax.matshow(rec.connections) + fg.savefig(f"{prefix}matrix.pdf") + fg.savefig(f"{prefix}matrix.png") + fg.savefig(f"{prefix}matrix.svg") + + n = rec.num_cells() + fg, ax = plt.subplots() + g = nx.MultiDiGraph() + g.add_nodes_from(np.arange(n)) + for i in range(n): + for j in range(n): + for _ in range(rec.connections[i, j]): + g.add_edge(i, j) + nx.draw(g, with_labels=True, font_weight='bold') + fg.savefig(f"{prefix}graph.pdf") + fg.savefig(f"{prefix}graph.png") + fg.savefig(f"{prefix}graph.svg") def plot_spikes(sim, n_cells, t_interval, T, prefix=""): @@ -31,7 +52,7 @@ def plot_spikes(sim, n_cells, t_interval, T, prefix=""): ts = np.arange(n_interval) * t_interval mean_rate = savgol_filter( - rates.mean(axis=1) / t_interval, window_length=5, polyorder=2 + rates.mean(axis=1), window_length=5, polyorder=2 ) fg, ax = plt.subplots() ax.plot(ts, rates) @@ -43,3 +64,9 @@ def plot_spikes(sim, n_cells, t_interval, T, prefix=""): fg.savefig(f"{prefix}rates.pdf") fg.savefig(f"{prefix}rates.png") fg.savefig(f"{prefix}rates.svg") + + +def randrange(n: int): + res = np.arange(n, dtype=int) + np.random.shuffle(res) + return res From bca8a5a0a682cbf501d9911cbd3a170b596dd1f8 Mon Sep 17 00:00:00 2001 From: boeschf <48126478+boeschf@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:39:25 +0200 Subject: [PATCH 04/30] spack: variant for hwloc support (#2419) --- spack/package.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/spack/package.py b/spack/package.py index 08c3a5ce9..7b9f9d82e 100644 --- a/spack/package.py +++ b/spack/package.py @@ -70,6 +70,7 @@ class Arbor(CMakePackage, CudaPackage): default=False, description="Enable vectorization of computational kernels", ) + variant("hwloc", default=False, description="support for thread pinning via HWLOC") variant( "gpu_rng", default=False, @@ -107,6 +108,9 @@ class Arbor(CMakePackage, CudaPackage): depends_on("mpi", when="+mpi") depends_on("py-mpi4py", when="+mpi+python", type=("build", "run")) + # hwloc + depends_on("hwloc@2:", when="+hwloc", type=("build", "run")) + # python (bindings) with when("+python"): extends("python") @@ -135,6 +139,7 @@ def cmake_args(self): self.define_from_variant("ARB_WITH_MPI", "mpi"), self.define_from_variant("ARB_WITH_PYTHON", "python"), self.define_from_variant("ARB_VECTORIZE", "vectorize"), + self.define_from_variant("ARB_USE_HWLOC", "hwloc"), ] if "+cuda" in self.spec: From 09cb472b68904476bc20e372e476860c14d75b28 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:54:00 +0200 Subject: [PATCH 05/30] Add title and conclusion --- doc/tutorial/plasticity.rst | 72 +++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index 7aa3028f1..4f6db878d 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -1,5 +1,8 @@ .. _tutorial_plasticity: +Structural Plasticity in Arbor +============================== + In this tutorial, we are going to demonstrate how a network can be built using plasticity and homeostatic connection rules. Despite not playing towards Arbor's strengths, we choose a LIF (Leaky Integrate and Fire) neuron model, as we are @@ -77,11 +80,23 @@ parameters are set in the constructor We also proceed to add spike recording and generate plots using a helper function ``plot_spikes`` from ``util.py``. You can skip the following details -for now and come back later if you are interested how it works. We generate -raster plots via ``scatter``. Rates are computed by binning spikes into -``t_interval`` and the neuron id; the mean rate is the average across the -neurons smoothed using a Savitzky-Golay filter (``scipy.signal.savgol_filter``). -We plot per-neuron and mean rates. +for now and come back later if you are interested how it works. Rates are +computed by binning spikes into ``t_interval`` and the neuron id; the mean rate +is the average across the neurons smoothed using a Savitzky-Golay filter +(``scipy.signal.savgol_filter``). + +We plot per-neuron and mean rates: + +.. figure:: ../../python/example/plasticity/01-rates.svg + :width: 400 + :align: center + +We also generate raster plots via ``scatter``. + +.. figure:: ../../python/example/plasticity/01-raster.svg + :width: 400 + :align: center + A Randomly Wired Network ------------------------ @@ -132,15 +147,32 @@ Both are used in ``rewire`` to produce a random connection matrix :language: python :lines: 56-65 -We then proceed to run the simulation and plot the results as before +We then proceed to run the simulation .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python :lines: 68-79 + and plot the results as before + +.. figure:: ../../python/example/plasticity/02-rates.svg + :width: 400 + :align: center + + Note that we added a plot of the network connectivity using ``plot_network`` from ``util`` as well. This generates images of the graph and connection matrix. +.. figure:: ../../python/example/plasticity/02-matrix.svg + :width: 400 + :align: center + +.. figure:: ../../python/example/plasticity/02-graph.svg + :width: 400 + :align: center + + + Adding Homeostasis ------------------ @@ -214,8 +246,28 @@ connection partners. The ``randrange`` function produces a shuffled range ``[0, N)``. We leverage the helper functions from the random network recipe to manipulate the connection table, see the discussion above. -Finally, we plot networks and spikes as before +Finally, we plot spiking rates as before; the jump at the half-way point is the +effect of the plasticity activating after which each neuron moves to the +setpoint -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 74-75 +.. figure:: ../../python/example/plasticity/03-rates.svg + :width: 400 + :align: center + +and the resulting network + +.. figure:: ../../python/example/plasticity/03-graph.svg + :width: 400 + :align: center + +Conclusion +---------- + +This concludes our foray into structural plasticity. While the building blocks + +- an explicit representation of the connections, +- running the simulation in batches (and calling ``simulation.update``!) +- a rule to derive the change + +will likely be the same in all approaches, the concrete implementation of the +rules is the centerpiece here. From 9cbc94770b1ed7880ab81c3d8545f6511f9fdf11 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:56:09 +0200 Subject: [PATCH 06/30] Use correct file name --- doc/tutorial/plasticity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index 4f6db878d..db9108d48 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -256,7 +256,7 @@ setpoint and the resulting network -.. figure:: ../../python/example/plasticity/03-graph.svg +.. figure:: ../../python/example/plasticity/03-final-graph.svg :width: 400 :align: center From 9358a289612437acfe094f1bb4376dcbeb255e5f Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:00:18 +0200 Subject: [PATCH 07/30] Small comment. --- doc/tutorial/plasticity.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index db9108d48..7cdf474d6 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -210,6 +210,9 @@ Note that our new recipe is almost unaltered from the random network all changes are contained to the way we run the simulation. To add a further interesting feature, we skip the rewiring for the first half of the simulation. +The initial network is unconnected, but could be populated randomly (or any +other way) if desired by calling ``self.rewire()`` in the constructor of +``homeostatic_network`` before setting the maxima to eight. Plasticity is implemented by tweaking the connection table inside the recipe between calls to ``run`` and calling ``simulation.update`` with the modified From 235ad2dfb129fe6e3e05c95dc55430cc58df1895 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:45:32 +0200 Subject: [PATCH 08/30] Final thoughts and warnings. --- doc/tutorial/plasticity.rst | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index 7cdf474d6..ea6aa58bc 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -222,6 +222,32 @@ recipe: :language: python :lines: 70 +.. note:: + + As it is the central point here, it is worth emphasizing why this yields a + changed network. The call to ``sim.update(rec)`` causes Arbor to internally + re-build the connection table from scratch based on the data returned by + ``rec.connections_on``. However, here, this method just inspects the matrix + in ``rec.connections`` and converts the data into a ``arbor.connection``. + Thus, changing this matrix before ``update`` will build a different network. + + Important caveats: + + - without ``update``, changes to the recipe have no effect + - vice versa ``update`` has no effect if the recipe doesn't return different + data than before + - ``update`` will delete all existing connections and their parameters, so + all connections to be kept must be explicitly re-instantiated + - ``update`` will **not** delete synapses or their state, e.g. ODEs will + still be integrated even if not connected and currents might be produced + - neither synapses/targets nor detectors/sources can be altered. Create all + endpoints up front. + - only the network is updated (this might change in future versions!) + - be very aware that ``connections_on`` might be called in arbitrary order + and by multiples (potentially different) threads and processes! This + requires some thought and synchronization when dealing with random numbers + and updating data *inside* ``connections_on``. + Changes are based on the difference of current rate we compute from the spikes during the last interval @@ -273,4 +299,10 @@ This concludes our foray into structural plasticity. While the building blocks - a rule to derive the change will likely be the same in all approaches, the concrete implementation of the -rules is the centerpiece here. +rules is the centerpiece here. For example, although spike rate homeostasis was +used here, mechanism states and ion concentrations --- extracted via the normal +probe and sample interface --- can be leveraged to build rules. Due to the way +the Python interface is required to link to measurements, using the C++ API for +access to streaming spike and measurement data could help to address performance +issues. Plasticity as shown also meshes with the high-level connection builder. +External tools to build and update connections might be useful as well. From 5e6e2419a84469dcfc44acf3009ab1441b53c64a Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Mon, 14 Oct 2024 10:51:40 +0200 Subject: [PATCH 09/30] Fix me. (#2420) Fix DOI link. --- doc/index.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/index.rst b/doc/index.rst index 525f78a54..8d35bce97 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -96,7 +96,7 @@ Cite (Bibtex format) :target: https://doi.org/10.1109/EMPDP.2019.8671560 .. |zlatest| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.13284789.svg - :target: https://doi.org/10.5281/zenodo.13284789image:: + :target: https://doi.org/10.5281/zenodo.13284789 .. |z0100| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.13284789.svg :target: https://doi.org/10.5281/zenodo.13284789 From 17d6f9f566424db7b89110c4fc17f8bb4d3480c5 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:37:13 +0200 Subject: [PATCH 10/30] Check for supported compilers. (#2411) Utter a dire warning for those who try to build with outdated and/or weird compilers. XLC is no longer a thing, so we remove checking for it. --- CMakeLists.txt | 5 +---- cmake/CheckCompilerXLC.cmake | 17 ----------------- cmake/CompilerOptions.cmake | 35 +++++++++++++++++++---------------- 3 files changed, 20 insertions(+), 37 deletions(-) delete mode 100644 cmake/CheckCompilerXLC.cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index c32ad4a75..355987a1b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -201,13 +201,10 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_EXPORT_COMPILE_COMMANDS "YES") -# Detect and deprecate xlC. - -include("CheckCompilerXLC") - # Compiler options common to library, examples, tests, etc. include("CompilerOptions") +check_supported_cxx() add_compile_options("$<$:${CXXOPT_WALL}>") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CUDA_STANDARD 20) diff --git a/cmake/CheckCompilerXLC.cmake b/cmake/CheckCompilerXLC.cmake deleted file mode 100644 index 7354dd279..000000000 --- a/cmake/CheckCompilerXLC.cmake +++ /dev/null @@ -1,17 +0,0 @@ -# CMake (at least sometimes) misidentifies XL 13 for Linux as Clang. - -if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - try_compile(ignore ${CMAKE_BINARY_DIR} ${PROJECT_SOURCE_DIR}/cmake/dummy.cpp COMPILE_DEFINITIONS --version OUTPUT_VARIABLE cc_out) - string(REPLACE "\n" ";" cc_out "${cc_out}") - foreach(line ${cc_out}) - if(line MATCHES "^IBM XL C") - set(CMAKE_CXX_COMPILER_ID "XL") - endif() - endforeach(line) -endif() - -# If we _do_ find xlC, don't try and build: too many bugs! - -if(CMAKE_CXX_COMPILER_ID STREQUAL "XL") - message(FATAL_ERROR "Arbor does not support being built by the IBM xlC compiler") -endif() diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 503e04610..751f7ac68 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -74,24 +74,28 @@ set(CXXOPT_WALL # because there is nothing to fix on our side. $,-Wno-psabi,> - - # Intel: - # - # Disable warning for unused template parameter - # this is raised by a templated function in the json library. - - $,-wd488,>) +) + +# Check for supported compilers / versions +function(check_supported_cxx) + set(cxx_supported_ids "AppleClang" "GNU" "Clang") + set(cxx_supported_ver 15 12 12) + foreach(id ver IN ZIP_LISTS cxx_supported_ids cxx_supported_ver) + if(CMAKE_CXX_COMPILER_ID MATCHES ${id} AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL ${ver}) + return() + endif() + endforeach() + message(WARNING "Found an unsupported compiler ${CMAKE_CXX_COMPILER_ID} version ${CMAKE_CXX_COMPILER_VERSION}, please consider switching to a supported version of GCC or Clang. Build failure is expected. We reserve the option to close all related issues without consideration.") +endfunction() # Set ${optvar} in parent scope according to requested architecture. # Architectures are given by the same names that GCC uses for its # -mcpu or -march options. - function(set_arch_target optvar optvar_cuda_guarded arch) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Correct compiler option unfortunately depends upon the target architecture family. # Extract this information from running the configured compiler with --verbose. - try_compile(ignore ${CMAKE_BINARY_DIR} ${PROJECT_SOURCE_DIR}/cmake/dummy.cpp COMPILE_DEFINITIONS --verbose OUTPUT_VARIABLE cc_out) string(REPLACE "\n" ";" cc_out "${cc_out}") set(target) @@ -106,22 +110,22 @@ function(set_arch_target optvar optvar_cuda_guarded arch) # architecture. # See clang / gcc manuals and: # https://maskray.me/blog/2022-08-28-march-mcpu-mtune - if (CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" AND CMAKE_CXX_COMPILER_VERSION LESS 15) - set(arch_opt "") + if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") + set(arch_opt "-march=${arch}") elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - if ("${target}" MATCHES "(arm64|aarch64)-.*") + if("${target}" MATCHES "(arm64|aarch64)-.*") # on AArch64, this is correct, ... set(arch_opt "-mcpu=${arch} -mtune=${arch}") else() # ... however on x86 mcpu _is_ mtune _and_ deprecated (since 2003!), but ... set(arch_opt "-march=${arch}") - endif () + endif() elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # ... clang likes march (and possibly mtune) # See https://discourse.llvm.org/t/when-to-use-mcpu-versus-march/47953/9 set(arch_opt "-march=${arch} -mtune=${arch}") - else () - message(STATUS "Falling back to -march=${arch} for compiler ${CMAKE_CXX_COMPILER_ID}") + else() + message(STATUS "Setting fallback architecture flags for ${CMAKE_CXX_COMPILER}.") set(arch_opt "-march=${arch}") endif() endif() @@ -140,7 +144,6 @@ function(set_arch_target optvar optvar_cuda_guarded arch) else() set("${optvar_cuda_guarded}" "${arch_opt}" PARENT_SCOPE) endif() - endfunction() # Set ${has_sve} and ${sve_length} in parent scope according to auto detection. From 7d1f82e2b738080d0c90c65258bd5361a5bbfd01 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Wed, 16 Oct 2024 19:37:56 +0200 Subject: [PATCH 11/30] Clean-up redundant items: files and functions. (#2410) Some justifications: - power & Co: only useful on Cray, likely outdated. Use external power measurements if this is relevant. - trace.hpp was never used and was locked_ostream's only user. --- arbor/CMakeLists.txt | 4 - arbor/backends/gpu/fvm.hpp | 6 - arbor/backends/multicore/fvm.hpp | 1 - arbor/hardware/power.cpp | 29 ---- arbor/hardware/power.hpp | 18 --- arbor/io/locked_ostream.cpp | 76 ---------- arbor/io/locked_ostream.hpp | 25 ---- arbor/io/save_ios.hpp | 24 --- arbor/io/serialize_hex.cpp | 62 -------- arbor/io/serialize_hex.hpp | 37 ----- arbor/io/trace.hpp | 79 ---------- arbor/matrix.hpp | 63 -------- arbor/merge_events.cpp | 2 - arbor/profile/meter_manager.cpp | 4 - arbor/profile/power_meter.cpp | 46 ------ arbor/profile/power_meter.hpp | 11 -- arbor/util/cycle.hpp | 213 --------------------------- arbor/util/meta.hpp | 1 - arbor/util/nop.hpp | 33 ----- arbor/util/rangeutil.hpp | 13 -- modcc/identifier.hpp | 4 - modcc/printer/printerutil.hpp | 21 --- test/unit/CMakeLists.txt | 2 - test/unit/test_cycle.cpp | 225 ---------------------------- test/unit/test_mechanisms.cpp | 244 ------------------------------- 25 files changed, 1243 deletions(-) delete mode 100644 arbor/hardware/power.cpp delete mode 100644 arbor/hardware/power.hpp delete mode 100644 arbor/io/locked_ostream.cpp delete mode 100644 arbor/io/locked_ostream.hpp delete mode 100644 arbor/io/save_ios.hpp delete mode 100644 arbor/io/serialize_hex.cpp delete mode 100644 arbor/io/serialize_hex.hpp delete mode 100644 arbor/io/trace.hpp delete mode 100644 arbor/matrix.hpp delete mode 100644 arbor/profile/power_meter.cpp delete mode 100644 arbor/profile/power_meter.hpp delete mode 100644 arbor/util/cycle.hpp delete mode 100644 arbor/util/nop.hpp delete mode 100644 test/unit/test_cycle.cpp delete mode 100644 test/unit/test_mechanisms.cpp diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt index 5ce77a147..c27295393 100644 --- a/arbor/CMakeLists.txt +++ b/arbor/CMakeLists.txt @@ -20,10 +20,7 @@ set(arbor_sources fvm_layout.cpp fvm_lowered_cell_impl.cpp hardware/memory.cpp - hardware/power.cpp iexpr.cpp - io/locked_ostream.cpp - io/serialize_hex.cpp label_resolution.cpp lif_cell_group.cpp cable_cell_group.cpp @@ -50,7 +47,6 @@ set(arbor_sources partition_load_balance.cpp profile/memory_meter.cpp profile/meter_manager.cpp - profile/power_meter.cpp profile/profiler.cpp schedule.cpp spike_event_io.cpp diff --git a/arbor/backends/gpu/fvm.hpp b/arbor/backends/gpu/fvm.hpp index eaac5ab2b..37023c4f4 100644 --- a/arbor/backends/gpu/fvm.hpp +++ b/arbor/backends/gpu/fvm.hpp @@ -1,16 +1,11 @@ #pragma once -#include #include #include #include #include "memory/memory.hpp" -#include "util/rangeutil.hpp" - -#include "backends/event.hpp" - #include "backends/gpu/gpu_store_types.hpp" #include "backends/gpu/shared_state.hpp" @@ -20,7 +15,6 @@ namespace arb { namespace gpu { struct backend { - static bool is_supported() { return true; } static std::string name() { return "gpu"; } using value_type = arb_value_type; diff --git a/arbor/backends/multicore/fvm.hpp b/arbor/backends/multicore/fvm.hpp index be787cbcc..a845a52bf 100644 --- a/arbor/backends/multicore/fvm.hpp +++ b/arbor/backends/multicore/fvm.hpp @@ -16,7 +16,6 @@ namespace arb { namespace multicore { struct backend { - static bool is_supported() { return true; } static std::string name() { return "cpu"; } using value_type = arb_value_type; diff --git a/arbor/hardware/power.cpp b/arbor/hardware/power.cpp deleted file mode 100644 index 7ca8df68f..000000000 --- a/arbor/hardware/power.cpp +++ /dev/null @@ -1,29 +0,0 @@ -#include - -#include "power.hpp" - -// Currently only supporting Cray PM counters. - -#define CRAY_PM_COUNTER_ENERGY "/sys/cray/pm_counters/energy" - -namespace arb { -namespace hw { - -bool has_energy_measurement() { - return static_cast(std::ifstream(CRAY_PM_COUNTER_ENERGY)); -} - -energy_size_type energy() { - energy_size_type result = energy_size_type(-1); - - std::ifstream fid(CRAY_PM_COUNTER_ENERGY); - if (fid) { - fid >> result; - } - - return result; -} - -} // namespace hw -} // namespace arb - diff --git a/arbor/hardware/power.hpp b/arbor/hardware/power.hpp deleted file mode 100644 index 003a30798..000000000 --- a/arbor/hardware/power.hpp +++ /dev/null @@ -1,18 +0,0 @@ -#pragma once - -#include - -namespace arb { -namespace hw { - -// Test for support on configured architecture: -bool has_energy_measurement(); - -// Energy in Joules (J) -using energy_size_type = std::uint64_t; - -// Returns energy_size_type(-1) if unable to read energy -energy_size_type energy(); - -} // namespace hw -} // namespace arb diff --git a/arbor/io/locked_ostream.cpp b/arbor/io/locked_ostream.cpp deleted file mode 100644 index 1aa855217..000000000 --- a/arbor/io/locked_ostream.cpp +++ /dev/null @@ -1,76 +0,0 @@ -#include -#include -#include -#include -#include - -#include "locked_ostream.hpp" - -namespace arb { -namespace io { - -using tbl_type = std::unordered_map>; - -static tbl_type& g_mex_tbl() { - static tbl_type tbl; - return tbl; -} - -static std::mutex& g_mex_tbl_mex() { - static std::mutex mex; - return mex; -} - -static std::shared_ptr register_sbuf(std::streambuf* b) { - if (b) { - std::lock_guard lock(g_mex_tbl_mex()); - - auto& wptr = g_mex_tbl()[b]; - auto mex = wptr.lock(); - if (!mex) { - mex = std::shared_ptr(new std::mutex); - wptr = mex; - } - return mex; - } - else { - return std::shared_ptr(); - } -} - -static void deregister_sbuf(std::streambuf* b) { - if (b) { - std::lock_guard lock(g_mex_tbl_mex()); - - auto i = g_mex_tbl().find(b); - if (i!=g_mex_tbl().end() && !(i->second.use_count())) { - g_mex_tbl().erase(i); - } - } -} - -locked_ostream::locked_ostream(std::streambuf *b): - std::ostream(b), - mex(register_sbuf(b)) -{} - - -locked_ostream::locked_ostream(locked_ostream&& other): - std::ostream(std::move(other)), - mex(std::move(other.mex)) -{ - set_rdbuf(other.rdbuf()); - other.set_rdbuf(nullptr); -} - -locked_ostream::~locked_ostream() { - mex.reset(); - deregister_sbuf(rdbuf()); -} - -std::unique_lock locked_ostream::guard() { - return std::unique_lock(*mex); -} - -} // namespace io -} // namespace arb diff --git a/arbor/io/locked_ostream.hpp b/arbor/io/locked_ostream.hpp deleted file mode 100644 index 20ecfc74a..000000000 --- a/arbor/io/locked_ostream.hpp +++ /dev/null @@ -1,25 +0,0 @@ -#pragma once - -// Lockable ostream over a provided streambuf. - -#include -#include -#include - -namespace arb { -namespace io { - -struct locked_ostream: std::ostream { - locked_ostream(std::streambuf *b); - locked_ostream(locked_ostream&& other); - - ~locked_ostream(); - - std::unique_lock guard(); - -private: - std::shared_ptr mex; -}; - -} // namespace io -} // namespace arb diff --git a/arbor/io/save_ios.hpp b/arbor/io/save_ios.hpp deleted file mode 100644 index adfcda247..000000000 --- a/arbor/io/save_ios.hpp +++ /dev/null @@ -1,24 +0,0 @@ -#pragma once - -// RAII save-and-restore ios formatting flags. - -#include - -namespace arb { -namespace io { - -struct save_ios_flags { - std::ios_base& ios; - std::ios_base::fmtflags flags; - - save_ios_flags(std::ios_base& ios): - ios(ios), flags(ios.flags()) {} - - save_ios_flags(const save_ios_flags&) = delete; - - ~save_ios_flags() { ios.flags(flags); } -}; - - -} // namespace io -} // namespace arb diff --git a/arbor/io/serialize_hex.cpp b/arbor/io/serialize_hex.cpp deleted file mode 100644 index 522f8401a..000000000 --- a/arbor/io/serialize_hex.cpp +++ /dev/null @@ -1,62 +0,0 @@ -// Adaptor for hexadecimal output to a std::ostream. - -#include -#include - -// Required for endianness macros: -#include - -#include "io/serialize_hex.hpp" - -namespace arb { -namespace io { - -namespace impl { - - enum class endian { - little = __ORDER_LITTLE_ENDIAN__, - big = __ORDER_BIG_ENDIAN__, - native = __BYTE_ORDER__ - }; - - std::ostream& operator<<(std::ostream& out, const hex_inline_wrap& h) { - using std::ptrdiff_t; - - constexpr bool little = endian::native==endian::little; - ptrdiff_t width = h.width; - const unsigned char* from = h.from; - const unsigned char* end = h.from+h.size; - std::string buf; - - auto emit = [&buf](unsigned char c) { - const char* digit = "0123456789abcdef"; - buf += digit[(c>>4)&0xf]; - buf += digit[c&0xf]; - }; - - constexpr unsigned bufsz = 512; - unsigned bufmargin = 4*width+1; - - buf.reserve(bufsz); - while (end-from>width) { - if (buf.size()+bufmargin>=bufsz) { - out << buf; - buf.clear(); - } - for (ptrdiff_t i = 0; i - -namespace arb { -namespace io { - -namespace impl { - // Wrapper for emitting values on an ostream as a sequence of hex digits. - struct hex_inline_wrap { - const unsigned char* from; - std::size_t size; - unsigned width; - }; - - std::ostream& operator<<(std::ostream&, const hex_inline_wrap&); -} // namespace impl - -// Inline hexadecimal adaptor: group output in `width` bytes. - -template -impl::hex_inline_wrap hex_inline(const T& obj, unsigned width = 4) { - return impl::hex_inline_wrap{reinterpret_cast(&obj), sizeof obj, width}; -} - -// Inline hexadecimal adaptor: print `n` bytes of data from `ptr`, grouping output in `width` bytes. - -template -impl::hex_inline_wrap hex_inline_n(const T* ptr, std::size_t n, unsigned width = 4) { - return impl::hex_inline_wrap{reinterpret_cast(ptr), n, width}; -} - -} // namespace io -} // namespace arb - diff --git a/arbor/io/trace.hpp b/arbor/io/trace.hpp deleted file mode 100644 index 5b50b7d60..000000000 --- a/arbor/io/trace.hpp +++ /dev/null @@ -1,79 +0,0 @@ -#pragma once - -// Internal TRACE macros and formatters for debugging during -// development. - -#include -#include - -// Required for endianness macros: -#include - -#include "io/locked_ostream.hpp" -#include "io/sepval.hpp" -#include "io/serialize_hex.hpp" - - -// TRACE(expr1 [, expr2 ...]) -// -// Emit current source location to std::cerr, followed by the -// literal expressions expr1, ..., and then the values of those expressions. -// -// TRACE output is to std::cerr is serialized. - -#define TRACE(...) arb::impl::debug_emit_trace(__FILE__, __LINE__, #__VA_ARGS__, ##__VA_ARGS__) - - -// DEBUG << ...; -// -// Emit arguments to std::cerr followed by a newline. -// DEBUG output to std::cerr is serialized. - -#define DEBUG arb::impl::emit_nl_locked(std::cerr.rdbuf()) - - -namespace arb { - -namespace impl { - inline void debug_emit_csv(std::ostream&) {} - - template - void debug_emit_csv(std::ostream& out, const Head& head, const Tail&... tail) { - out << head; - if (sizeof...(tail)) { - out << ", "; - } - debug_emit_csv(out, tail...); - } - - inline void debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* vars) { - out << file << ':' << line << ": " << vars << ": "; - } - - struct emit_nl_locked: public io::locked_ostream { - emit_nl_locked(std::streambuf* buf): - io::locked_ostream(buf), - lock_(this->guard()) - {} - - ~emit_nl_locked() { - if (rdbuf()) { - (*this) << std::endl; - } - } - - private: - std::unique_lock lock_; - }; - - template - void debug_emit_trace(const char* file, int line, const char* varlist, const Args&... args) { - impl::emit_nl_locked out(std::cerr.rdbuf()); - - out.precision(17); - impl::debug_emit_trace_leader(out, file, line, varlist); - impl::debug_emit_csv(out, args...); - } -} // namespace impl - -} // namespace arb diff --git a/arbor/matrix.hpp b/arbor/matrix.hpp deleted file mode 100644 index 3b69db504..000000000 --- a/arbor/matrix.hpp +++ /dev/null @@ -1,63 +0,0 @@ -#pragma once - -#include - -#include - -#include -#include - -#include - -namespace arb { - -/// Hines matrix -/// Make the back end state implementation optional to allow for -/// testing different implementations in the same code. -template -class matrix { -public: - using backend = Backend; - using array = typename backend::array; - using iarray = typename backend::iarray; - using const_view = const array&; - using state = State; // backend specific storage for matrix state - - matrix() = default; - - matrix(const std::vector& pi, - const std::vector& ci, - const std::vector& cv_capacitance, - const std::vector& face_conductance, - const std::vector& cv_area, - const std::vector& cell_to_intdom): - num_cells_{ci.size() - 1}, - state_(pi, ci, cv_capacitance, face_conductance, cv_area, cell_to_intdom) - { - arb_assert(cell_index()[num_cells()] == arb_index_type(parent_index().size())); - } - - /// the dimension of the matrix (i.e. the number of rows or colums) - std::size_t size() const { return state_.size(); } - /// the number of cell matrices that have been packed together - std::size_t num_cells() const { return num_cells_; } - /// the vector holding the parent index - const iarray& parent_index() const { return state_.parent_index; } - /// the partition of the parent index over the cells - const iarray& cell_index() const { return state_.cell_cv_divs; } - /// Solve the linear system into a given solution storage. - void solve(array& to) { state_.solve(to); } - /// Assemble the matrix for given dt - void assemble(const_view& dt, const_view& U, const_view& I, const_view& g) { state_.assemble(dt, U, I, g); } - -private: - std::size_t num_cells_ = 0; - -public: - // Provide via public interface to make testing much easier. If you modify - // this directly without knowing what you are doing, you get what you - // deserve. - state state_; -}; - -} // namespace arb diff --git a/arbor/merge_events.cpp b/arbor/merge_events.cpp index 9dd71ef48..ccfcd7cd3 100644 --- a/arbor/merge_events.cpp +++ b/arbor/merge_events.cpp @@ -1,11 +1,9 @@ -#include #include #include #include #include -#include "io/trace.hpp" #include "merge_events.hpp" #include "util/tourney_tree.hpp" diff --git a/arbor/profile/meter_manager.cpp b/arbor/profile/meter_manager.cpp index 99710caeb..f8066270f 100644 --- a/arbor/profile/meter_manager.cpp +++ b/arbor/profile/meter_manager.cpp @@ -4,7 +4,6 @@ #include #include "memory_meter.hpp" -#include "power_meter.hpp" #include "execution_context.hpp" #include "util/hostname.hpp" @@ -48,9 +47,6 @@ meter_manager::meter_manager() { if (auto m = make_gpu_memory_meter()) { meters_.push_back(std::move(m)); } - if (auto m = make_power_meter()) { - meters_.push_back(std::move(m)); - } }; void meter_manager::start(context ctx) { diff --git a/arbor/profile/power_meter.cpp b/arbor/profile/power_meter.cpp deleted file mode 100644 index aa5adf02d..000000000 --- a/arbor/profile/power_meter.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include -#include - -#include - -#include "hardware/power.hpp" - -namespace arb { -namespace profile { - -class power_meter: public meter { - std::vector readings_; - -public: - std::string name() override { - return "energy"; - } - - std::string units() override { - return "J"; - } - - std::vector measurements() override { - std::vector diffs; - - for (auto i=1ul; i - -namespace arb { -namespace profile { - -meter_ptr make_power_meter(); - -} // namespace profile -} // namespace arb diff --git a/arbor/util/cycle.hpp b/arbor/util/cycle.hpp deleted file mode 100644 index 905c59e0d..000000000 --- a/arbor/util/cycle.hpp +++ /dev/null @@ -1,213 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "util/iterutil.hpp" -#include "util/range.hpp" - -namespace arb { -namespace util { - -template -class cyclic_iterator : public iterator_adaptor, I> { - using base = iterator_adaptor, I>; - friend class iterator_adaptor, I>; - - I begin_; - I inner_; - S end_; - typename base::difference_type off_; // offset from begin - - const I& inner() const { - return inner_; - } - - I& inner() { - return inner_; - } - -public: - using value_type = typename base::value_type; - using difference_type = typename base::difference_type; - - cyclic_iterator() = default; - - template - cyclic_iterator(Iter&& iter, Sentinel&& sentinel) - : begin_(std::forward(iter)), - inner_(std::forward(iter)), - end_(std::forward(sentinel)), - off_(0) - { } - - cyclic_iterator(const cyclic_iterator& other) - : begin_(other.begin_), - inner_(other.inner_), - end_(other.end_), - off_(other.off_) - { } - - cyclic_iterator(cyclic_iterator&& other) - : begin_(std::move(other.begin_)), - inner_(std::move(other.inner_)), - end_(std::move(other.end_)), - off_(other.off_) - { } - - - cyclic_iterator& operator=(const cyclic_iterator& other) { - if (this != &other) { - inner_ = other.inner_; - begin_ = other.begin_; - end_ = other.end_; - off_ = other.off_; - } - - return *this; - } - - cyclic_iterator& operator=(cyclic_iterator&& other) { - if (this != &other) { - inner_ = std::move(other.inner_); - begin_ = std::move(other.begin_); - end_ = std::move(other.end_); - off_ = other.off_; - } - - return *this; - } - - // forward and input iterator requirements - value_type operator*() const { - return *inner_; - } - - value_type operator[](difference_type n) const { - return *(*this + n); - } - - cyclic_iterator& operator++() { - if (++inner_ == end_) { - // wrap around - inner_ = begin_; - } - - ++off_; - return *this; - } - - cyclic_iterator operator++(int) { - cyclic_iterator iter(*this); - ++(*this); - return iter; - } - - cyclic_iterator& operator--() { - if (inner_ == begin_) { - // wrap around; use upto() to handle efficiently the move to the end - // in case inner_ is a bidirectional iterator - inner_ = upto(inner_, end_); - } - else { - --inner_; - } - - --off_; - return *this; - } - - cyclic_iterator operator--(int) { - cyclic_iterator iter(*this); - --(*this); - return iter; - } - - cyclic_iterator& operator+=(difference_type n) { - // wrap distance - auto size = util::distance(begin_, end_); - - // calculate distance from begin - auto pos = (off_ += n); - if (pos < 0) { - auto mod = -pos % size; - pos = mod ? size - mod : 0; - } - else { - pos = pos % size; - } - - inner_ = std::next(begin_, pos); - return *this; - } - - cyclic_iterator& operator-=(difference_type n) { - return this->operator+=(-n); - } - - bool operator==(const cyclic_iterator& other) const { - return begin_ == other.begin_ && off_ == other.off_; - } - - bool operator!=(const cyclic_iterator& other) const { - return !(*this == other); - } - - cyclic_iterator operator-(difference_type n) const { - cyclic_iterator c(*this); - return c -= n; - } - - difference_type operator-(const cyclic_iterator& other) const { - return off_ - other.off_; - } - - bool operator<(const cyclic_iterator& other) const { - return off_ < other.off_; - } - - // expose inner iterator for testing against a sentinel - template - bool operator==(const Sentinel& s) const { - return inner_ == s; - } - - template - bool operator!=(const Sentinel& s) const { - return !(inner_ == s); - } -}; - -template -cyclic_iterator make_cyclic_iterator(const I& iter, const S& sentinel) { - return cyclic_iterator(iter, sentinel); -} - - -template -auto cyclic_view(Seq&& s) { - using std::begin; - using std::end; - - auto b = begin(s); - auto e = end(s); - - if constexpr (is_regular_sequence_v) { - return make_range(make_cyclic_iterator(b, e), make_cyclic_iterator(e, e)); - } - else { - return make_range(make_cyclic_iterator(b, e), e); - } -} - -// Handle initializer lists -template -auto cyclic_view(const std::initializer_list& list) { - return make_range( - make_cyclic_iterator(list.begin(), list.end()), - make_cyclic_iterator(list.end(), list.end())); -} - -} // namespace util -} // namespace arb diff --git a/arbor/util/meta.hpp b/arbor/util/meta.hpp index d9f30584f..ed0055f08 100644 --- a/arbor/util/meta.hpp +++ b/arbor/util/meta.hpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include diff --git a/arbor/util/nop.hpp b/arbor/util/nop.hpp deleted file mode 100644 index 4f3fdd3dd..000000000 --- a/arbor/util/nop.hpp +++ /dev/null @@ -1,33 +0,0 @@ -#pragma once - -/* - * Provide object that implicitly converts to - * a std::function object that does nothing but return a - * default-constructed type or void. - */ - -#include - -namespace arb { -namespace util { - -struct nop_function_t { - template - operator std::function() const { - return [](Args...) { return R{}; }; - } - - template - operator std::function() const { - return [](Args...) { }; - } - - // keep clang happy: see CWG issue #253, - // http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#253 - constexpr nop_function_t() {} -}; - -static constexpr nop_function_t nop_function; - -} // namespace util -} // namespace arb diff --git a/arbor/util/rangeutil.hpp b/arbor/util/rangeutil.hpp index 2d08638ab..48bac64c0 100644 --- a/arbor/util/rangeutil.hpp +++ b/arbor/util/rangeutil.hpp @@ -7,7 +7,6 @@ #include #include -#include #include #include #include @@ -19,18 +18,6 @@ namespace arb { namespace util { -// Present a single item as a range - -template -range singleton_view(T& item) { - return {&item, &item+1}; -} - -template -range singleton_view(const T& item) { - return {&item, &item+1}; -} - // Non-owning views and subviews template diff --git a/modcc/identifier.hpp b/modcc/identifier.hpp index a2ce9fbbc..08a3f40fd 100644 --- a/modcc/identifier.hpp +++ b/modcc/identifier.hpp @@ -62,10 +62,6 @@ enum class sourceKind { no_source }; -inline std::string yesno(bool val) { - return std::string(val ? "yes" : "no"); -}; - //////////////////////////////////////////// // to_string functions convert types // to strings for printing diagnostics diff --git a/modcc/printer/printerutil.hpp b/modcc/printer/printerutil.hpp index 7fc8cfa34..8033fc10a 100644 --- a/modcc/printer/printerutil.hpp +++ b/modcc/printer/printerutil.hpp @@ -23,14 +23,6 @@ inline const char* arb_header_prefix() { return prefix; } -// TODO: this function will be obsoleted once arbor private/public headers are -// properly split. - -inline const char* arb_private_header_prefix() { - static const char* prefix = ""; - return prefix; -} - struct namespace_declaration_open { const std::vector& ids; namespace_declaration_open(const std::vector& ids): ids(ids) {} @@ -163,16 +155,3 @@ struct ARB_LIBMODCC_API indexed_variable_info { }; ARB_LIBMODCC_API indexed_variable_info decode_indexed_variable(IndexedVariable* sym); - -template -size_t emit_array(std::ostream& out, const C& vars) { - auto n = 0ul; - io::separator sep("", ", "); - out << "{ "; - for (const auto& var: vars) { - out << sep << var; - ++n; - } - out << " }"; - return n; -} diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 09acca19c..c419b8ebc 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -68,7 +68,6 @@ set(unit_sources test_cv_geom.cpp test_cv_layout.cpp test_cv_policy.cpp - test_cycle.cpp test_domain_decomposition.cpp test_dry_run_context.cpp test_event_delivery.cpp @@ -95,7 +94,6 @@ set(unit_sources test_matrix.cpp test_mcable_map.cpp test_cable_cell_group.cpp - test_mechanisms.cpp test_mech_temp_diam.cpp test_mechcat.cpp test_mechinfo.cpp diff --git a/test/unit/test_cycle.cpp b/test/unit/test_cycle.cpp deleted file mode 100644 index ad0b58c1a..000000000 --- a/test/unit/test_cycle.cpp +++ /dev/null @@ -1,225 +0,0 @@ -#include - -#include -#include -#include - -#include "common.hpp" -#include -#include - -using namespace arb; - -TEST(cycle_iterator, construct) { - std::vector values = { 4, 2, 3 }; - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), values.cend()); - - { - // copy constructor - auto cycle_iter_copy(cycle_iter); - EXPECT_EQ(cycle_iter, cycle_iter_copy); - } - - { - // copy assignment - auto cycle_iter_copy = cycle_iter; - EXPECT_EQ(cycle_iter, cycle_iter_copy); - } - - { - // move constructor - auto cycle_iter_copy( - util::make_cyclic_iterator(values.cbegin(), values.cend()) - ); - EXPECT_EQ(cycle_iter, cycle_iter_copy); - } -} - - -TEST(cycle_iterator, increment) { - std::vector values = { 4, 2, 3 }; - - { - // test operator++ - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), - values.cend()); - auto cycle_iter_copy = cycle_iter; - - auto values_size = values.size(); - for (auto i = 0u; i < 2*values_size; ++i) { - EXPECT_EQ(values[i % values_size], *cycle_iter); - EXPECT_EQ(values[i % values_size], *cycle_iter_copy++); - ++cycle_iter; - } - } - - { - // test operator[] - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), - values.cend()); - - for (auto i = 0u; i < values.size(); ++i) { - EXPECT_EQ(values[i], cycle_iter[values.size() + i]); - } - } - - { - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), - values.cend()); - EXPECT_NE(cycle_iter + 1, cycle_iter + 10); - } -} - -TEST(cycle_iterator, decrement) { - std::vector values = { 4, 2, 3 }; - - { - // test operator-- - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), - values.cend()); - auto cycle_iter_copy = cycle_iter; - - auto values_size = values.size(); - for (auto i = 0u; i < 2*values_size; ++i) { - --cycle_iter; - cycle_iter_copy--; - auto val = values[values_size - i%values_size - 1]; - EXPECT_EQ(val, *cycle_iter); - EXPECT_EQ(val, *cycle_iter_copy); - } - } - - { - // test operator[] - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), - values.cend()); - int values_size = values.size(); - for (int i = 0; i < 2*values_size; ++i) { - auto pos = i % values_size; - pos = pos ? values_size - pos : 0; - EXPECT_EQ(values[pos], cycle_iter[-i]); - } - } - - { - auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), - values.cend()); - EXPECT_NE(cycle_iter - 2, cycle_iter - 5); - EXPECT_NE(cycle_iter + 1, cycle_iter - 5); - } -} - -TEST(cycle_iterator, carray) { - int values[] = { 4, 2, 3 }; - auto cycle_iter = util::make_cyclic_iterator(std::cbegin(values), - std::cend(values)); - auto values_size = std::size(values); - for (auto i = 0u; i < 2*values_size; ++i) { - EXPECT_EQ(values[i % values_size], *cycle_iter++); - } -} - -TEST(cycle_iterator, sentinel) { - using testing::null_terminated; - - auto msg = "hello"; - auto cycle_iter = util::make_cyclic_iterator(msg, null_terminated); - - auto msg_len = std::string(msg).size(); - for (auto i = 0u; i < 2*msg_len; ++i) { - EXPECT_EQ(msg[i % msg_len], *cycle_iter++); - } -} - - -TEST(cycle, cyclic_view) { - std::vector values = { 4, 2, 3 }; - std::vector values_new; - - std::copy_n(util::cyclic_view(values).cbegin(), 10, - std::back_inserter(values_new)); - - EXPECT_EQ(10u, values_new.size()); - - auto i = 0; - for (auto const& v : values_new) { - EXPECT_EQ(values[i++ % values.size()], v); - } -} - -TEST(cycle, cyclic_view_initlist) { - std::vector values; - - std::copy_n(util::cyclic_view({2., 3., 4.}).cbegin(), 10, - std::back_inserter(values)); - - EXPECT_EQ(10u, values.size()); - - auto i = 0; - for (auto const& v : values) { - EXPECT_EQ(2 + i++ % 3, v); - } -} - -TEST(cycle_iterator, difference) { - int values[] = { 4, 2, 3 }; - - auto cycle = util::cyclic_view(values); - auto c1 = cycle.begin(); - - auto c2 = c1; - EXPECT_EQ(0, c2-c1); - - ++c2; - EXPECT_EQ(1, c2-c1); - - ++c1; - EXPECT_EQ(0, c2-c1); - - c2 += 6; - EXPECT_EQ(6, c2-c1); - - c1 += 2; - EXPECT_EQ(4, c2-c1); - - --c2; - EXPECT_EQ(3, c2-c1); - - c1 -= 3; - EXPECT_EQ(6, c2-c1); -} - -TEST(cycle_iterator, order) { - int values[] = { 4, 2, 3 }; - - auto cycle = util::cyclic_view(values); - auto c1 = cycle.begin(); - auto c2 = c1; - - EXPECT_FALSE(c1 < c2); - EXPECT_FALSE(c2 < c1); - EXPECT_TRUE(c1 <= c2); - EXPECT_TRUE(c1 >= c2); - - c2 += std::size(values); - - EXPECT_TRUE(c1 < c2); - EXPECT_FALSE(c2 < c1); - EXPECT_TRUE(c1 <= c2); - EXPECT_FALSE(c1 >= c2); -} - -TEST(cycle, cyclic_view_sentinel) { - const char *msg = "hello"; - auto cycle = util::cyclic_view( - util::make_range(msg, testing::null_terminated) - ); - - std::string msg_new; - auto msg_new_size = 2*std::string(msg).size(); - for (auto i = 0u; i < msg_new_size; ++i) { - msg_new += cycle[i]; - } - - EXPECT_EQ("hellohello", msg_new); -} diff --git a/test/unit/test_mechanisms.cpp b/test/unit/test_mechanisms.cpp deleted file mode 100644 index 3cbf46bb4..000000000 --- a/test/unit/test_mechanisms.cpp +++ /dev/null @@ -1,244 +0,0 @@ -#include - -// TODO: Amend for new mechanism architecture -#if 0 - -// Prototype mechanisms in tests -#include "mech_proto/expsyn_cpu.hpp" -#include "mech_proto/exp2syn_cpu.hpp" -#include "mech_proto/hh_cpu.hpp" -#include "mech_proto/pas_cpu.hpp" -#include "mech_proto/test_kin1_cpu.hpp" -#include "mech_proto/test_kinlva_cpu.hpp" -#include "mech_proto/test_ca_cpu.hpp" - -// modcc generated mechanisms -#include "mechanisms/multicore/expsyn_cpu.hpp" -#include "mechanisms/multicore/exp2syn_cpu.hpp" -#include "mechanisms/multicore/hh_cpu.hpp" -#include "mechanisms/multicore/pas_cpu.hpp" -#include "mechanisms/multicore/test_kin1_cpu.hpp" -#include "mechanisms/multicore/test_kinlva_cpu.hpp" -#include "mechanisms/multicore/test_ca_cpu.hpp" - -#include -#include -#include -#include -#include -#include -#include - -TEST(mechanisms, helpers) { - using namespace arb; - using size_type = multicore::backend::size_type; - using value_type = multicore::backend::value_type; - - // verify that the hh and pas channels are available - EXPECT_TRUE(multicore::backend::has_mechanism("hh")); - EXPECT_TRUE(multicore::backend::has_mechanism("pas")); - - std::vector parent_index = {0,0,1,2,3,4,0,6,7,8}; - auto node_index = std::vector{0,6,7,8,9}; - auto weights = std::vector(node_index.size(), 1.0); - auto n = node_index.size(); - - // one cell - size_type ncell = 1; - std::vector cell_index(n, 0u); - - multicore::backend::array vec_i(n, 0.); - multicore::backend::array vec_v(n, 0.); - multicore::backend::array vec_t(ncell, 0.); - multicore::backend::array vec_t_to(ncell, 0.); - multicore::backend::array vec_dt(n, 0.); - - auto mech = multicore::backend::make_mechanism("hh", 0, - memory::make_view(cell_index), vec_t, vec_t_to, vec_dt, - vec_v, vec_i, weights, node_index); - - EXPECT_EQ(mech->name(), "hh"); - EXPECT_EQ(mech->size(), 5u); - - // check that an out_of_range exception is thrown if an invalid mechanism is requested - ASSERT_THROW( - multicore::backend::make_mechanism("dachshund", 0, - memory::make_view(cell_index), vec_t, vec_t_to, vec_dt, - vec_v, vec_i, weights, node_index), - std::out_of_range - ); -} - -// Setup and update mechanism -template -void mech_update(T* mech, unsigned num_iters) { - - using namespace arb; - std::map> ions; - - mech->set_params(); - mech->init(); - for (auto ion_kind : ion_kinds()) { - auto ion_indexes = util::make_copy>( - mech->node_index_ - ); - - // Create and fill in the ion - ion ion = ion_indexes; - - memory::fill(ion.current(), 5.); - memory::fill(ion.reversal_potential(), 100.); - memory::fill(ion.internal_concentration(), 10.); - memory::fill(ion.external_concentration(), 140.); - ions[ion_kind] = ion; - - if (mech->uses_ion(ion_kind).uses) { - mech->set_ion(ion_kind, ions[ion_kind], ion_indexes); - } - } - - for (auto i=0u; inode_index_.size(); ++i) { - mech->net_receive(i, 1.); - } - - for (auto i=0u; iupdate_current(); - mech->update_state(); - } -} - -template -void array_init(T& array, const Seq& seq) { - auto seq_iter = seq.cbegin(); - for (auto& e : array) { - e = *seq_iter++; - } -} - -template -struct mechanism_info { - using mechanism_type = S; - using proto_mechanism_type = T; - static constexpr bool index_aliasing = alias; -}; - -template -class mechanisms : public ::testing::Test { }; - -TYPED_TEST_CASE_P(mechanisms); - -TYPED_TEST_P(mechanisms, update) { - using mechanism_type = typename TypeParam::mechanism_type; - using proto_mechanism_type = typename TypeParam::proto_mechanism_type; - - // Type checking - EXPECT_TRUE((std::is_same::value)); - EXPECT_TRUE((std::is_same::value)); - EXPECT_TRUE((std::is_same::value)); - - int num_cell = 1; - int num_syn = 32; - int num_comp = num_syn; - - typename mechanism_type::iarray node_index(num_syn); - typename mechanism_type::array voltage(num_comp, -65.0); - typename mechanism_type::array current(num_comp, 1.0); - - typename mechanism_type::array weights(num_syn, 1.0); - - typename mechanism_type::iarray cell_index(num_comp, 0); - typename mechanism_type::array time(num_cell, 2.); - typename mechanism_type::array time_to(num_cell, 2.1); - typename mechanism_type::array dt(num_comp, 2.1-2.); - - array_init(voltage, arb::util::cyclic_view({ -65.0, -61.0, -63.0 })); - array_init(current, arb::util::cyclic_view({ 1.0, 0.9, 1.1 })); - array_init(weights, arb::util::cyclic_view({ 1.0 })); - - // Initialise indexes - std::vector index_freq; - if (TypeParam::index_aliasing) { - index_freq.assign({ 4, 2, 3 }); - } - else { - index_freq.assign({ 1 }); - } - - auto freq_begin = arb::util::cyclic_view(index_freq).cbegin(); - auto freq = freq_begin; - auto index = node_index.begin(); - while (index != node_index.end()) { - for (auto i = 0; i < *freq && index != node_index.end(); ++i) { - *index++ = freq - freq_begin; - } - ++freq; - } - - // Copy indexes, voltage and current to use for the prototype mechanism - typename mechanism_type::iarray node_index_copy(node_index); - typename mechanism_type::array voltage_copy(voltage); - typename mechanism_type::array current_copy(current); - typename mechanism_type::array weights_copy(weights); - - // Create mechanisms - auto mech = arb::make_mechanism( - 0, cell_index, time, time_to, dt, - voltage, current, std::move(weights), std::move(node_index) - ); - - auto mech_proto = arb::make_mechanism( - 0, cell_index, time, time_to, dt, - voltage_copy, current_copy, - std::move(weights_copy), std::move(node_index_copy) - ); - - mech_update(dynamic_cast(mech.get()), 10); - mech_update(dynamic_cast(mech_proto.get()), 10); - - auto citer = current_copy.begin(); - for (auto const& c: current) { - EXPECT_NEAR(*citer++, c, 1e-6); - } -} - -REGISTER_TYPED_TEST_CASE_P(mechanisms, update); - -using mechanism_types = ::testing::Types< - mechanism_info< - arb::multicore::mechanism_hh, - arb::multicore::mechanism_hh_proto - >, - mechanism_info< - arb::multicore::mechanism_pas, - arb::multicore::mechanism_pas_proto - >, - mechanism_info< - arb::multicore::mechanism_expsyn, - arb::multicore::mechanism_expsyn_proto, - true - >, - mechanism_info< - arb::multicore::mechanism_exp2syn, - arb::multicore::mechanism_exp2syn_proto, - true - >, - mechanism_info< - arb::multicore::mechanism_test_kin1, - arb::multicore::mechanism_test_kin1_proto - >, - mechanism_info< - arb::multicore::mechanism_test_kinlva, - arb::multicore::mechanism_test_kinlva_proto - >, - mechanism_info< - arb::multicore::mechanism_test_ca, - arb::multicore::mechanism_test_ca_proto - > ->; - -INSTANTIATE_TYPED_TEST_CASE_P(mechanism_types, mechanisms, mechanism_types); - -#endif // 0 From 76120d16c00ae67128c3c69421ab712f985f3445 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Fri, 18 Oct 2024 13:30:35 +0200 Subject: [PATCH 12/30] Refactor discretization. (#2415) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR rectifies a long-standing issue since the introduction of `decor`: CV discretization was treated as a defaultable value instead of a cell property, along with bio-physical properties. This lead to some awkward facts: - Intermingling of bio-physics and numerical settings - Discretization is treated like a default without override. - Discretization cannot be changed once set, e.g. if read from `.acc`, requiring the copy+update of a decor instead Thus: - Discretization can now be set during `cable_cell` construction - It can be read and updated later - Refactor `cv_policy` to use type erasure like other, similar constructions, e.g. schedules. - Hide internal `cv_policy` constructors. ⚠️ Breaks `.acc` by upgrading from 0.9 to 0.10 ⚠️ Closes #1408 --------- Co-authored-by: boeschf <48126478+boeschf@users.noreply.github.com> --- .github/workflows/sanitize.yml | 8 +- .github/workflows/test-matrix.yml | 4 +- CMakeLists.txt | 16 +- arbor/cable_cell.cpp | 18 +- arbor/cable_cell_param.cpp | 8 - arbor/cv_policy.cpp | 288 ++++++++++-------- arbor/fvm_layout.cpp | 12 +- arbor/include/arbor/cable_cell.hpp | 23 +- arbor/include/arbor/cable_cell_param.hpp | 5 +- arbor/include/arbor/cv_policy.hpp | 167 +++------- arbor/include/arbor/s_expr.hpp | 2 - arbor/include/arbor/util/expected.hpp | 2 +- arbor/morph/cv_data.cpp | 2 +- arbor/util/piecewise.hpp | 39 ++- arborio/cableio.cpp | 114 ++++--- arborio/cv_policy_parse.cpp | 39 +-- arborio/include/arborio/cv_policy_parse.hpp | 1 - doc/concepts/cable_cell.rst | 27 +- doc/concepts/discretization.rst | 55 ++++ doc/dependencies.csv | 2 +- doc/fileformat/cable_cell.rst | 10 +- doc/python/cable_cell.rst | 22 +- doc/python/decor.rst | 14 - doc/tutorial/single_cell_detailed.rst | 4 +- example/busyring/ring.cpp | 8 +- example/diffusion/diffusion.cpp | 2 +- example/dryrun/branch_cell.hpp | 5 +- example/lfp/lfp.cpp | 5 +- example/network_description/branch_cell.hpp | 6 +- example/ornstein_uhlenbeck/ou.cpp | 3 +- example/plasticity/branch_cell.hpp | 4 +- example/plasticity/plasticity.cpp | 5 +- example/ring/branch_cell.hpp | 6 +- python/cells.cpp | 97 +++--- python/example/diffusion.py | 3 +- .../network_two_cells_gap_junctions.py | 6 +- python/example/probe_lfpykit.py | 7 +- python/example/single_cell_allen.py | 4 +- .../l5pc/C060114A7_axon_replacement.acc | 2 +- .../l5pc/C060114A7_modified.acc | 2 +- .../single_cell_bluepyopt/l5pc/l5pc_decor.acc | 2 +- .../l5pc/l5pc_label_dict.acc | 2 +- .../simplecell/simple_cell_decor.acc | 2 +- .../simplecell/simple_cell_label_dict.acc | 2 +- python/example/single_cell_bluepyopt_l5pc.py | 41 ++- .../single_cell_bluepyopt_simplecell.py | 23 +- python/example/single_cell_cable.py | 3 +- python/example/single_cell_detailed.py | 7 +- python/example/single_cell_detailed_recipe.py | 8 +- python/example/single_cell_nml.py | 7 +- python/example/single_cell_swc.py | 9 +- python/strprintf.hpp | 2 +- python/test/unit/test_io.py | 6 +- test/common_cells.cpp | 27 +- test/common_cells.hpp | 7 +- test/ubench/CMakeLists.txt | 2 +- test/ubench/fvm_discretize.cpp | 19 +- test/ubench/mech_vec.cpp | 16 +- test/unit-distributed/test_communicator.cpp | 6 +- test/unit/CMakeLists.txt | 2 +- test/unit/test_cv_geom.cpp | 6 +- test/unit/test_cv_policy.cpp | 18 +- test/unit/test_diffusion.cpp | 2 +- test/unit/test_fvm_layout.cpp | 19 +- test/unit/test_fvm_lowered.cpp | 12 +- test/unit/test_probe.cpp | 45 +-- test/unit/test_s_expr.cpp | 26 +- test/unit/test_sde.cpp | 10 +- test/unit/test_spikes.cpp | 3 +- 69 files changed, 689 insertions(+), 692 deletions(-) create mode 100644 doc/concepts/discretization.rst diff --git a/.github/workflows/sanitize.yml b/.github/workflows/sanitize.yml index b75a1ed9a..4cb6e0e72 100644 --- a/.github/workflows/sanitize.yml +++ b/.github/workflows/sanitize.yml @@ -19,7 +19,7 @@ permissions: jobs: build: name: "Sanitize" - runs-on: ubuntu-22.04 + runs-on: ubuntu-24.04 strategy: fail-fast: false matrix: @@ -27,8 +27,8 @@ jobs: sanitizer: ["address", "undefined", "thread"] simd: ["ON", "OFF"] env: - CC: clang-14 - CXX: clang++-14 + CC: clang-18 + CXX: clang++-18 ASAN_OPTIONS: detect_leaks=1 steps: - name: Get build dependencies @@ -43,8 +43,6 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive - - name: Update pip - run: python -m pip install --upgrade pip # figure out vector extensions for ccache key - name: Check vector extensions run: | diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index a886f582f..343978e16 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -29,8 +29,8 @@ jobs: - { name: "Linux Min Clang", os: "ubuntu-22.04", - cc: "clang-12", - cxx: "clang++-12", + cc: "clang-13", + cxx: "clang++-13", py: "3.9", cmake: "3.19.x", mpi: "ON", diff --git a/CMakeLists.txt b/CMakeLists.txt index 355987a1b..ac4f24c33 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -274,7 +274,7 @@ install(FILES mechanisms/BuildModules.cmake DESTINATION ${ARB_INSTALL_DATADIR}) # First make ourselves less chatty set(_saved_CMAKE_MESSAGE_LOG_LEVEL ${CMAKE_MESSAGE_LOG_LEVEL}) -set(CMAKE_MESSAGE_LOG_LEVEL WARNING) +set(CMAKE_MESSAGE_LOG_LEVEL STATUS) # in the event we can find hwloc, just add it find_package(hwloc QUIET) @@ -341,6 +341,8 @@ CPMAddPackage(NAME fmt VERSION 10.0.0 GIT_TAG 10.0.0) +add_library(ext-gtest INTERFACE) +add_library(ext-bench INTERFACE) if (BUILD_TESTING) CPMAddPackage(NAME benchmark GITHUB_REPOSITORY google/benchmark @@ -351,6 +353,18 @@ if (BUILD_TESTING) GIT_TAG release-1.12.1 VERSION 1.12.1 OPTIONS "INSTALL_GTEST OFF" "BUILD_GMOCK OFF") + if(benchmark_ADDED) + target_link_libraries(ext-bench INTERFACE benchmark) + else() + find_package(benchmark REQUIRED) + target_link_libraries(ext-bench INTERFACE benchmark::benchmark) + endif() + if(googletest_ADDED) + target_link_libraries(ext-gtest INTERFACE ) + else() + find_package(googletest REQUIRED) + target_link_libraries(ext-gtest INTERFACE gtest gtest_main) + endif() endif() CPMAddPackage(NAME units diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp index b956b2db9..a8098ab14 100644 --- a/arbor/cable_cell.cpp +++ b/arbor/cable_cell.cpp @@ -83,18 +83,22 @@ struct cable_cell_impl { // The decorations on the cell. decor decorations; + // Discretization + std::optional discretization_; + // The placeable label to lid_range map dynamic_typed_map>::type> labeled_lid_ranges; - cable_cell_impl(const arb::morphology& m, const label_dict& labels, const decor& decorations): + cable_cell_impl(const arb::morphology& m, const label_dict& labels, const decor& decorations, const std::optional& cvp): provider(m, labels), dictionary(labels), - decorations(decorations) + decorations(decorations), + discretization_{cvp} { init(); } - cable_cell_impl(): cable_cell_impl({},{},{}) {} + cable_cell_impl(): cable_cell_impl({}, {}, {}, {}) {} cable_cell_impl(const cable_cell_impl& other) = default; @@ -203,6 +207,10 @@ struct cable_cell_impl { } }; +const std::optional& cable_cell::discretization() const { return impl_->discretization_; } +void cable_cell::discretization(cv_policy cvp) { impl_->discretization_ = std::move(cvp); } + + using impl_ptr = std::unique_ptr; impl_ptr make_impl(cable_cell_impl* c) { return impl_ptr(c, [](cable_cell_impl* p){delete p;}); @@ -232,8 +240,8 @@ void cable_cell_impl::init() { } } -cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary): - impl_(make_impl(new cable_cell_impl(m, dictionary, decorations))) +cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary, const std::optional& cvp): + impl_(make_impl(new cable_cell_impl(m, dictionary, decorations, cvp))) {} cable_cell::cable_cell(): impl_(make_impl(new cable_cell_impl())) {} diff --git a/arbor/cable_cell_param.cpp b/arbor/cable_cell_param.cpp index a5c4e2079..34fcd7e6e 100644 --- a/arbor/cable_cell_param.cpp +++ b/arbor/cable_cell_param.cpp @@ -105,11 +105,6 @@ std::vector cable_cell_parameter_set::serialize() const { for (const auto& [name, mech]: reversal_potential_method) { D.push_back(ion_reversal_potential_method{name, mech}); } - - if (discretization) { - D.push_back(*discretization); - } - return D; } @@ -163,9 +158,6 @@ decor& decor::set_default(defaultable what) { else if constexpr (std::is_same_v) { defaults_.reversal_potential_method[p.ion] = p.method; } - else if constexpr (std::is_same_v) { - defaults_.discretization = std::forward(p); - } else if constexpr (std::is_same_v) { if (p.scale.type() != iexpr_type::scalar) throw cable_cell_error{"Default values cannot have a scale."}; auto s = p.scale.get_scalar(); diff --git a/arbor/cv_policy.cpp b/arbor/cv_policy.cpp index fcd817054..039d9d9d9 100644 --- a/arbor/cv_policy.cpp +++ b/arbor/cv_policy.cpp @@ -8,194 +8,244 @@ #include #include "util/rangeutil.hpp" -#include "util/span.hpp" - -// Discretization policy implementations: namespace arb { -static auto unique_sum = [](auto&&... lss) { - return ls::support(sum(std::forward(lss)...)); -}; +static std::string print_flag(cv_policy_flag flag) { + switch (flag) { + case arb::cv_policy_flag::none: return "(flag-none)"; + case arb::cv_policy_flag::interior_forks: return "(flag-interior-forks)"; + } + throw std::runtime_error("UNREACHABLE"); +} -// Combinators: -// cv_policy_plus_ represents the result of operator+, -// cv_policy_bar_ represents the result of operator|. +static bool has_flag(cv_policy_flag lhs, cv_policy_flag rhs) { + return static_cast(static_cast(lhs) & static_cast(rhs)); +} -struct cv_policy_plus_: cv_policy_base { - cv_policy_plus_(const cv_policy& lhs, const cv_policy& rhs): - lhs_(lhs), rhs_(rhs) {} - cv_policy_base_ptr clone() const override { - return cv_policy_base_ptr(new cv_policy_plus_(*this)); - } +static auto unique_sum = [](auto&&... lss) { + return ls::support(sum(std::forward(lss)...)); +}; - locset cv_boundary_points(const cable_cell& c) const override { +struct cvp_cv_policy_plus { + locset cv_boundary_points(const cable_cell& c) const { return unique_sum(lhs_.cv_boundary_points(c), rhs_.cv_boundary_points(c)); } - region domain() const override { return join(lhs_.domain(), rhs_.domain()); } + region domain() const { return join(lhs_.domain(), rhs_.domain()); } - std::ostream& print(std::ostream& os) override { + std::ostream& format(std::ostream& os) const { os << "(join " << lhs_ << ' ' << rhs_ << ')'; return os; } + // TODO This is needed seemingly only for older compilers + cvp_cv_policy_plus(cv_policy lhs, cv_policy rhs): lhs_{std::move(lhs)}, rhs_(std::move(rhs)) {} + cv_policy lhs_, rhs_; }; ARB_ARBOR_API cv_policy operator+(const cv_policy& lhs, const cv_policy& rhs) { - return cv_policy_plus_(lhs, rhs); + return cv_policy{cvp_cv_policy_plus(lhs, rhs)}; } -struct cv_policy_bar_: cv_policy_base { - cv_policy_bar_(const cv_policy& lhs, const cv_policy& rhs): - lhs_(lhs), rhs_(rhs) {} - - cv_policy_base_ptr clone() const override { - return cv_policy_base_ptr(new cv_policy_bar_(*this)); +struct cvp_cv_policy_bar { + locset cv_boundary_points(const cable_cell& c) const { + return unique_sum(ls::restrict_to(lhs_.cv_boundary_points(c), + complement(rhs_.domain())), + rhs_.cv_boundary_points(c)); } - locset cv_boundary_points(const cable_cell& c) const override { - return unique_sum(ls::restrict_to(lhs_.cv_boundary_points(c), complement(rhs_.domain())), rhs_.cv_boundary_points(c)); - } - - region domain() const override { return join(lhs_.domain(), rhs_.domain()); } + region domain() const { return join(lhs_.domain(), rhs_.domain()); } - std::ostream& print(std::ostream& os) override { + std::ostream& format(std::ostream& os) const { os << "(replace " << lhs_ << ' ' << rhs_ << ')'; return os; } + // TODO This is needed seemingly only for older compilers + cvp_cv_policy_bar(cv_policy lhs, cv_policy rhs): lhs_{std::move(lhs)}, rhs_(std::move(rhs)) {} + cv_policy lhs_, rhs_; }; ARB_ARBOR_API cv_policy operator|(const cv_policy& lhs, const cv_policy& rhs) { - return cv_policy_bar_(lhs, rhs); + return cv_policy{cvp_cv_policy_bar(lhs, rhs)}; +} + +struct cvp_cv_policy_max_extent { + double max_extent_; + region domain_; + cv_policy_flag flags_; + + std::ostream& format(std::ostream& os) const { + os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << print_flag(flags_) << ')'; + return os; + } + + locset cv_boundary_points(const cable_cell& cell) const { + const unsigned nbranch = cell.morphology().num_branches(); + const auto& embed = cell.embedding(); + if (!nbranch || max_extent_<=0) return ls::nil(); + + std::vector points; + double oomax_extent = 1./max_extent_; + auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); + + for (auto& comp: comps) { + for (mcable c: comp) { + double cable_length = embed.integrate_length(c); + unsigned ncv = std::ceil(cable_length*oomax_extent); + double scale = (c.dist_pos-c.prox_pos)/ncv; + + if (has_flag(flags_, cv_policy_flag::interior_forks)) { + for (unsigned i = 0; i points; - double oomax_extent = 1./max_extent_; - auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); - - for (auto& comp: comps) { - for (mcable c: comp) { - double cable_length = embed.integrate_length(c); - unsigned ncv = std::ceil(cable_length*oomax_extent); - double scale = (c.dist_pos-c.prox_pos)/ncv; + std::ostream& format(std::ostream& os) const { + os << "(single " << domain_ << ')'; + return os; + } - if (flags_&cv_policy_flag::interior_forks) { - for (unsigned i = 0; i points; - double ooncv = 1./cv_per_branch_; - auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); + std::vector points; + double ooncv = 1./cv_per_branch_; + auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); - for (auto& comp: comps) { - for (mcable c: comp) { - double scale = (c.dist_pos-c.prox_pos)*ooncv; + for (auto& comp: comps) { + for (mcable c: comp) { + double scale = (c.dist_pos-c.prox_pos)*ooncv; - if (flags_&cv_policy_flag::interior_forks) { - for (unsigned i = 0; icv_boundary_points(cell): - global_dflt.discretization? global_dflt.discretization->cv_boundary_points(cell): - default_cv_policy().cv_boundary_points(cell)); + const auto& cvp = cell.discretization().value_or(global_dflt.discretization.value_or(default_cv_policy())); + D.geometry = cv_geometry(cell, cvp.cv_boundary_points(cell)); if (D.geometry.empty()) return D; diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 829582316..3fd84f7b4 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -9,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -237,9 +238,16 @@ using location_assignment = mlocation_map>; using cable_cell_region_map = static_typed_map; + density, + voltage_process, + init_membrane_potential, + axial_resistivity, + temperature, + membrane_capacitance, + init_int_concentration, + ion_diffusivity, + init_ext_concentration, + init_reversal_potential>; using cable_cell_location_map = static_typed_map; @@ -265,7 +273,10 @@ struct ARB_SYMBOL_VISIBLE cable_cell { } /// Construct from morphology, label and decoration descriptions. - cable_cell(const class morphology& m, const decor& d, const label_dict& l={}); + cable_cell(const class morphology& m, + const decor& d, + const label_dict& l={}, + const std::optional& = {}); /// Access to labels const label_dict& labels() const; @@ -306,6 +317,10 @@ struct ARB_SYMBOL_VISIBLE cable_cell { // The decorations on the cell. const decor& decorations() const; + // The current cv_policy of this cell + const std::optional& discretization() const; + void discretization(cv_policy); + // The default parameter and ion settings on the cell. const cable_cell_parameter_set& default_parameters() const; diff --git a/arbor/include/arbor/cable_cell_param.hpp b/arbor/include/arbor/cable_cell_param.hpp index d68c0c73e..621d36b6f 100644 --- a/arbor/include/arbor/cable_cell_param.hpp +++ b/arbor/include/arbor/cable_cell_param.hpp @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include #include #include @@ -380,8 +380,7 @@ using defaultable = init_int_concentration, init_ext_concentration, init_reversal_potential, - ion_reversal_potential_method, - cv_policy>; + ion_reversal_potential_method>; // Cable cell ion and electrical defaults. diff --git a/arbor/include/arbor/cv_policy.hpp b/arbor/include/arbor/cv_policy.hpp index a56a5c51d..e06cd0c73 100644 --- a/arbor/include/arbor/cv_policy.hpp +++ b/arbor/include/arbor/cv_policy.hpp @@ -69,146 +69,77 @@ struct cv_policy_base { virtual std::ostream& print(std::ostream&) = 0; }; -using cv_policy_base_ptr = std::unique_ptr; - struct ARB_SYMBOL_VISIBLE cv_policy { - cv_policy(const cv_policy_base& ref) { // implicit - policy_ptr = ref.clone(); - } - - cv_policy(const cv_policy& other): - policy_ptr(other.policy_ptr->clone()) {} - + // construct from anything except other policies + template , cv_policy>>> + explicit cv_policy(const Impl& impl): impl_(std::make_unique>(impl)) {} + template , cv_policy>>> + explicit cv_policy(Impl&& impl): impl_(std::make_unique>(std::move(impl))) {} + // move + cv_policy(cv_policy&&) = default; + cv_policy& operator=(cv_policy&&) = default; + // copy + cv_policy(const cv_policy& other): impl_(other.impl_->clone()) {} cv_policy& operator=(const cv_policy& other) { - policy_ptr = other.policy_ptr->clone(); + impl_ = other.impl_->clone(); return *this; } - cv_policy(cv_policy&&) = default; - cv_policy& operator=(cv_policy&&) = default; + // interface + locset cv_boundary_points(const cable_cell& cell) const { return impl_->cv_boundary_points(cell); } + region domain() const { return impl_->domain(); } + std::ostream& format(std::ostream& os) const { return impl_->format(os); } - locset cv_boundary_points(const cable_cell& cell) const { - return policy_ptr->cv_boundary_points(cell); - } - - region domain() const { - return policy_ptr->domain(); - } - - friend std::ostream& operator<<(std::ostream& o, const cv_policy& p) { - return p.policy_ptr->print(o); - } + friend ARB_ARBOR_API std::ostream& operator<<(std::ostream& os, const cv_policy& cvp) { return cvp.format(os); } private: - cv_policy_base_ptr policy_ptr; -}; - -ARB_ARBOR_API cv_policy operator+(const cv_policy&, const cv_policy&); -ARB_ARBOR_API cv_policy operator|(const cv_policy&, const cv_policy&); - - -// Common flags for CV policies; bitwise composable. -namespace cv_policy_flag { - using value = unsigned; - enum : unsigned { - none = 0, - interior_forks = 1<<0 + struct iface { + virtual locset cv_boundary_points(const cable_cell& cell) const = 0; + virtual region domain() const = 0; + virtual std::unique_ptr clone() const = 0; + virtual ~iface() {} + virtual std::ostream& format(std::ostream&) const = 0; }; -} - -struct ARB_ARBOR_API cv_policy_explicit: cv_policy_base { - explicit cv_policy_explicit(locset locs, region domain = reg::all()): - locs_(std::move(locs)), domain_(std::move(domain)) {} - - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(explicit " << locs_ << ' ' << domain_ << ')'; - return os; - } -private: - locset locs_; - region domain_; -}; - -struct ARB_ARBOR_API cv_policy_single: cv_policy_base { - explicit cv_policy_single(region domain = reg::all()): - domain_(domain) {} + using iface_ptr = std::unique_ptr; - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(single " << domain_ << ')'; - return os; - } + template + struct wrap: iface { + explicit wrap(const Impl& impl): inner_(impl) {} + explicit wrap(Impl&& impl): inner_(std::move(impl)) {} -private: - region domain_; -}; + locset cv_boundary_points(const cable_cell& cell) const override { return inner_.cv_boundary_points(cell); } + region domain() const override { return inner_.domain(); }; + iface_ptr clone() const override { return std::make_unique>(inner_); } + std::ostream& format(std::ostream& os) const override { return inner_.format(os); }; -struct ARB_ARBOR_API cv_policy_max_extent: cv_policy_base { - cv_policy_max_extent(double max_extent, region domain, cv_policy_flag::value flags = cv_policy_flag::none): - max_extent_(max_extent), domain_(std::move(domain)), flags_(flags) {} - - explicit cv_policy_max_extent(double max_extent, cv_policy_flag::value flags = cv_policy_flag::none): - max_extent_(max_extent), domain_(reg::all()), flags_(flags) {} - - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << flags_ << ')'; - return os; - } + Impl inner_; + }; -private: - double max_extent_; - region domain_; - cv_policy_flag::value flags_; + iface_ptr impl_; }; -struct ARB_ARBOR_API cv_policy_fixed_per_branch: cv_policy_base { - cv_policy_fixed_per_branch(unsigned cv_per_branch, region domain, cv_policy_flag::value flags = cv_policy_flag::none): - cv_per_branch_(cv_per_branch), domain_(std::move(domain)), flags_(flags) {} +// Common flags for CV policies; bitwise composable. +enum class cv_policy_flag: unsigned { + none = 0, + interior_forks = 1<<0 +}; - explicit cv_policy_fixed_per_branch(unsigned cv_per_branch, cv_policy_flag::value flags = cv_policy_flag::none): - cv_per_branch_(cv_per_branch), domain_(reg::all()), flags_(flags) {} +ARB_ARBOR_API cv_policy operator+(const cv_policy&, const cv_policy&); +ARB_ARBOR_API cv_policy operator|(const cv_policy&, const cv_policy&); - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(fixed-per-branch " << cv_per_branch_ << ' ' << domain_ << ' ' << flags_ << ')'; - return os; - } +ARB_ARBOR_API cv_policy cv_policy_explicit(locset, region = reg::all()); -private: - unsigned cv_per_branch_; - region domain_; - cv_policy_flag::value flags_; -}; +ARB_ARBOR_API cv_policy cv_policy_max_extent(double, region, cv_policy_flag = cv_policy_flag::none); +ARB_ARBOR_API cv_policy cv_policy_max_extent(double, cv_policy_flag = cv_policy_flag::none); -struct ARB_ARBOR_API cv_policy_every_segment: cv_policy_base { - explicit cv_policy_every_segment(region domain = reg::all()): - domain_(std::move(domain)) {} +ARB_ARBOR_API cv_policy cv_policy_fixed_per_branch(unsigned, region, cv_policy_flag = cv_policy_flag::none); +ARB_ARBOR_API cv_policy cv_policy_fixed_per_branch(unsigned, cv_policy_flag = cv_policy_flag::none); - cv_policy_base_ptr clone() const override; - locset cv_boundary_points(const cable_cell&) const override; - region domain() const override; - std::ostream& print(std::ostream& os) override { - os << "(every-segment " << domain_ << ')'; - return os; - } +ARB_ARBOR_API cv_policy cv_policy_single(region domain = reg::all()); -private: - region domain_; -}; +ARB_ARBOR_API cv_policy cv_policy_every_segment(region domain = reg::all()); -inline cv_policy default_cv_policy() { - return cv_policy_fixed_per_branch(1); -} +inline cv_policy default_cv_policy() { return cv_policy_fixed_per_branch(1); } } // namespace arb diff --git a/arbor/include/arbor/s_expr.hpp b/arbor/include/arbor/s_expr.hpp index c5c4f51fa..ce23a18ce 100644 --- a/arbor/include/arbor/s_expr.hpp +++ b/arbor/include/arbor/s_expr.hpp @@ -7,9 +7,7 @@ #include #include #include -#include #include -#include #include diff --git a/arbor/include/arbor/util/expected.hpp b/arbor/include/arbor/util/expected.hpp index 9d860f653..1af88eb42 100644 --- a/arbor/include/arbor/util/expected.hpp +++ b/arbor/include/arbor/util/expected.hpp @@ -486,7 +486,7 @@ struct expected { // Swap ops. void swap(expected& other) { - data_.swap(other.data); + data_.swap(other.data_); } // Accessors. diff --git a/arbor/morph/cv_data.cpp b/arbor/morph/cv_data.cpp index ec4d68e76..6fdd996bb 100644 --- a/arbor/morph/cv_data.cpp +++ b/arbor/morph/cv_data.cpp @@ -138,7 +138,7 @@ arb_size_type cell_cv_data::size() const { } ARB_ARBOR_API std::optional cv_data(const cable_cell& cell) { - if (auto policy = cell.decorations().defaults().discretization) { + if (const auto& policy = cell.discretization()) { return cell_cv_data(cell, policy->cv_boundary_points(cell)); } return {}; diff --git a/arbor/util/piecewise.hpp b/arbor/util/piecewise.hpp index f047b2bbd..9fbd936ef 100644 --- a/arbor/util/piecewise.hpp +++ b/arbor/util/piecewise.hpp @@ -1,6 +1,5 @@ #pragma once - // Create/manipulate 1-d piecewise defined objects. // // A `pw_element` describes a _value_ of type `A` and an _extent_ of @@ -398,7 +397,6 @@ struct pw_elements { void push_back(double left, double right, U&& v) { if (!empty() && left != vertex_.back()) throw std::runtime_error("noncontiguous element"); if (right(v)); if (vertex_.empty()) vertex_.push_back(left); @@ -407,10 +405,7 @@ struct pw_elements { template void push_back(double right, U&& v) { - if (empty()) { - throw std::runtime_error("require initial left vertex for element"); - } - + if (empty()) throw std::runtime_error("require initial left vertex for element"); push_back(vertex_.back(), right, std::forward(v)); } @@ -424,37 +419,41 @@ struct pw_elements { using std::begin; using std::end; + // Invariant, see below + // empty() || value_.size() + 1 = vertex_.size() + auto vs = std::size(vertices); + auto es = std::size(values); + // check invariant + if (!((es == 0 && es == vs) + || (es != 0 && es + 1 == vs))) { + // TODO(fmt): Make a better error w/ format. + throw std::runtime_error{"Vertices and values need to have matching lengths"}; + } + + // clean-up + clear(); + + // We know that invariant holds from here on. + if (es == 0) return; + auto vi = begin(vertices); - auto ve = end(vertices); auto ei = begin(values); auto ee = end(values); - if (ei == ee) { // empty case - if (vi != ve) throw std::runtime_error{"Vertices and values need to have same length; values too long."}; - clear(); - return; - } - clear(); - if (vi == ve) throw std::runtime_error{"Vertices and values need to have same length; values too short."}; - - reserve(vertices.size()); + reserve(vs); double left = *vi++; double right = *vi++; push_back(left, right, *ei++); - while (ei != ee) { - if (vi == ve) throw std::runtime_error{"Vertices and values need to have same length; values too short."}; double right = *vi++; push_back(right, *ei++); } - if (vi != ve) throw std::runtime_error{"Vertices and values need to have same length; values too long."}; } private: // Consistency requirements: // 1. empty() || value_.size()+1 = vertex_.size() // 2. vertex_[i]<=vertex_[j] for i<=j. - std::vector vertex_; std::vector value_; }; diff --git a/arborio/cableio.cpp b/arborio/cableio.cpp index 4ca05cc64..61dd4af5a 100644 --- a/arborio/cableio.cpp +++ b/arborio/cableio.cpp @@ -19,7 +19,7 @@ namespace arborio { using namespace arb; -ARB_ARBORIO_API std::string acc_version() {return "0.9-dev";} +ARB_ARBORIO_API std::string acc_version() {return "0.10-dev";} cableio_parse_error::cableio_parse_error(const std::string& msg, const arb::src_location& loc): arb::arbor_exception(msg+" at :"+ @@ -116,12 +116,6 @@ s_expr mksexp(const mpoint& p) { s_expr mksexp(const msegment& seg) { return slist("segment"_symbol, (int)seg.id, mksexp(seg.prox), mksexp(seg.dist), seg.tag); } -// This can be removed once cv_policy is removed from the decor. -s_expr mksexp(const cv_policy& c) { - std::stringstream s; - s << c; - return slist("cv-policy"_symbol, parse_s_expr(s.str())); -} s_expr mksexp(const decor& d) { auto round_trip = [](auto& x) { std::stringstream s; @@ -145,6 +139,11 @@ s_expr mksexp(const decor& d) { } return {"decor"_symbol, slist_range(decorations)}; } +s_expr mksexp(const cv_policy& c) { + std::stringstream s; + s << c; + return slist("cv-policy"_symbol, parse_s_expr(s.str())); +} s_expr mksexp(const label_dict& dict) { auto round_trip = [](auto& x) { std::stringstream s; @@ -202,8 +201,10 @@ ARB_ARBORIO_API std::ostream& write_component(std::ostream& o, const morphology& return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), mksexp(x))}; } ARB_ARBORIO_API std::ostream& write_component(std::ostream& o, const cable_cell& x, const meta_data& m) { - if (m.version != acc_version()) { - throw cableio_version_error(m.version); + if (m.version != acc_version()) throw cableio_version_error(m.version); + if (const auto& cvp = x.discretization()) { + auto cell = s_expr{"cable-cell"_symbol, slist(mksexp(x.morphology()), mksexp(x.labels()), mksexp(x.decorations()), mksexp(*cvp))}; + return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), cell)}; } auto cell = s_expr{"cable-cell"_symbol, slist(mksexp(x.morphology()), mksexp(x.labels()), mksexp(x.decorations()))}; return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), cell)}; @@ -359,20 +360,36 @@ morphology make_morphology(const std::vector>& args) } // Define cable-cell maker -// Accepts the morphology, decor and label_dict arguments in any order as a vector -cable_cell make_cable_cell(const std::vector>& args) { +// Accepts the morphology, decor, label_dict and cv_policy arguments in any order as a vector +cable_cell make_cable_cell4(const std::vector>& args) { decor dec; label_dict dict; morphology morpho; + std::optional cvp; for(const auto& a: args) { auto cable_cell_visitor = arb::util::overload( - [&](const morphology & p) { morpho = p; }, - [&](const label_dict & p) { dict = p; }, - [&](const decor & p){ dec = p; }); + [&](const morphology& q) { morpho = q; }, + [&](const label_dict& q) { dict = q; }, + [&](const cv_policy& q) { cvp = q; }, + [&](const decor& q){ dec = q; }); std::visit(cable_cell_visitor, a); } - return cable_cell(morpho, dec, dict); + return cable_cell(morpho, dec, dict, cvp); } +cable_cell make_cable_cell3(const std::vector>& args) { + decor dec; + label_dict dict; + morphology morpho; + for(const auto& a: args) { + auto cable_cell_visitor = arb::util::overload( + [&](const morphology& q) { morpho = q; }, + [&](const label_dict& q) { dict = q; }, + [&](const decor& q){ dec = q; }); + std::visit(cable_cell_visitor, a); + } + return cable_cell(morpho, dec, dict, {}); +} + version_tuple make_version(const std::string& v) { return version_tuple{v}; } @@ -592,6 +609,16 @@ parse_hopefully eval(const s_expr& e, const eval_map& map, const eval_ auto& hd = e.head(); if (hd.is_atom()) { auto& atom = hd.atom(); + if (atom.spelling == "cv-policy") { + // NOTE tail produces a single item list? + auto res = parse_cv_policy_expression(e.tail().head()); + if (res) { + return res.value(); + } + else { + return res.error(); + } + } // If this is not a symbol, parse as a tuple if (atom.kind != tok::symbol) { auto args = eval_args(e, map, vec); @@ -641,9 +668,6 @@ parse_hopefully eval(const s_expr& e, const eval_map& map, const eval_ if (match(l->type())) return eval_cast(l.value()); } - // Or it could be a cv-policy expression - if (auto p = parse_cv_policy_expression(e)) return p.value(); - // Unable to find a match: try to return a helpful error message. const auto nc = std::distance(matches.first, matches.second); std::string msg = "No matches for found for "+name+" with "+std::to_string(args->size())+" arguments.\n" @@ -673,27 +697,26 @@ eval_map named_evals{ ARBIO_ADD_ION_EVAL("ion-diffusivity", make_ion_diffusivity), {"quantity", make_call(make_quantity, "'quantity' with a value:real and a unit:string")}, {"envelope", make_arg_vec_call(make_envelope, - "'envelope' with one or more pairs of start time and amplitude (start:real amplitude:real)")}, + "'envelope' with one or more pairs of start time and amplitude (start:real amplitude:real)")}, {"envelope-pulse", make_call(make_envelope_pulse, - "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, + "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, {"current-clamp", make_call, double, double>(make_i_clamp, - "'current-clamp' with 3 arguments (env:envelope freq:real phase:real)")}, + "'current-clamp' with 3 arguments (env:envelope freq:real phase:real)")}, {"current-clamp", make_call(make_i_clamp_pulse, - "'current-clamp' with 3 arguments (env:envelope_pulse freq:real phase:real)")}, + "'current-clamp' with 3 arguments (env:envelope_pulse freq:real phase:real)")}, {"threshold-detector", make_call(arb::threshold_detector::from_raw_millivolts, - "'threshold-detector' with 1 argument (threshold:real)")}, + "'threshold-detector' with 1 argument (threshold:real)")}, {"mechanism", make_mech_call("'mechanism' with a name argument, and 0 or more parameter settings" - "(name:string (param:string val:real))")}, + "(name:string (param:string val:real))")}, {"ion-reversal-potential-method", make_call( make_ion_reversal_potential_method, - "'ion-reversal-potential-method' with 2 arguments (ion:string mech:mechanism)")}, - {"cv-policy", make_call(make_cv_policy, - "'cv-policy' with 1 argument (p:policy)")}, + "'ion-reversal-potential-method' with 2 arguments (ion:string mech:mechanism)")}, + {"cv-policy", make_call(make_cv_policy, "'cv-policy' with 1 argument (p:policy)")}, {"junction", make_call(make_wrapped_mechanism, "'junction' with 1 argumnet (m: mechanism)")}, {"synapse", make_call(make_wrapped_mechanism, "'synapse' with 1 argumnet (m: mechanism)")}, {"density", make_call(make_wrapped_mechanism, "'density' with 1 argumnet (m: mechanism)")}, {"voltage-process", make_call(make_wrapped_mechanism, "'voltage-process' with 1 argumnet (m: mechanism)")}, {"scaled-mechanism", make_scaled_mechanism_call("'scaled_mechanism' with a density argument, and 0 or more parameter scaling expressions" - "(d:density (param:string val:iexpr))")}, + "(d:density (param:string val:iexpr))")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset c:current-clamp name:string)")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset t:threshold-detector name:string)")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset gj:junction name:string)")}, @@ -719,39 +742,42 @@ eval_map named_evals{ {"default", make_call(make_default, "'default' with 1 argument (v:ion-diffusivity)")}, {"default", make_call(make_default, "'default' with 1 argument (v:ion-reversal-potential)")}, {"default", make_call(make_default, "'default' with 1 argument (v:ion-reversal-potential-method)")}, - {"default", make_call(make_default, "'default' with 1 argument (v:cv-policy)")}, {"locset-def", make_call(make_locset_pair, - "'locset-def' with 2 arguments (name:string ls:locset)")}, + "'locset-def' with 2 arguments (name:string ls:locset)")}, {"region-def", make_call(make_region_pair, - "'region-def' with 2 arguments (name:string reg:region)")}, + "'region-def' with 2 arguments (name:string reg:region)")}, {"iexpr-def", make_call(make_iexpr_pair, - "'iexpr-def' with 2 arguments (name:string e:iexpr)")}, + "'iexpr-def' with 2 arguments (name:string e:iexpr)")}, {"point", make_call(make_point, - "'point' with 4 arguments (x:real y:real z:real radius:real)")}, + "'point' with 4 arguments (x:real y:real z:real radius:real)")}, {"segment", make_call(make_segment, - "'segment' with 4 arguments (parent:int prox:point dist:point tag:int)")}, + "'segment' with 4 arguments (parent:int prox:point dist:point tag:int)")}, {"branch", make_branch_call( - "'branch' with 2 integers and 1 or more segment arguments (id:int parent:int s0:segment s1:segment ..)")}, + "'branch' with 2 integers and 1 or more segment arguments (id:int parent:int s0:segment s1:segment ..)")}, {"decor", make_arg_vec_call(make_decor, - "'decor' with 1 or more `paint`, `place` or `default` arguments")}, + "'decor' with 1 or more `paint`, `place` or `default` arguments")}, {"label-dict", make_arg_vec_call(make_label_dict, - "'label-dict' with 1 or more `locset-def` or `region-def` or `iexpr-def` arguments")}, + "'label-dict' with 1 or more `locset-def` or `region-def` or `iexpr-def` arguments")}, {"morphology", make_arg_vec_call(make_morphology, - "'morphology' 1 or more `branch` arguments")}, + "'morphology' 1 or more `branch` arguments")}, - {"cable-cell", make_unordered_call(make_cable_cell, - "'cable-cell' with 3 arguments: `morphology`, `label-dict`, and `decor` in any order")}, + {"cable-cell", + make_unordered_call(make_cable_cell4, + "'cable-cell' with 4 arguments: `morphology`, `label-dict`, `decor`, and `cv_policy` in any order")}, + {"cable-cell", + make_unordered_call(make_cable_cell3, + "'cable-cell' with 3 arguments: `morphology`, `label-dict`, and `decor` in any order")}, {"version", make_call(make_version, "'version' with one argment (val:std::string)")}, {"meta-data", make_call(make_meta_data, "'meta-data' with one argument (v:version)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:decor)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:label_dict)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:morphology)")}, - { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:cable_cell)")} + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:decor)")}, + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:label_dict)")}, + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:morphology)")}, + {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:cable_cell)")} }; #undef ARBIO_ADD_EVAL diff --git a/arborio/cv_policy_parse.cpp b/arborio/cv_policy_parse.cpp index 7e5bc2fe4..b371806ce 100644 --- a/arborio/cv_policy_parse.cpp +++ b/arborio/cv_policy_parse.cpp @@ -1,6 +1,4 @@ #include -#include -#include #include #include @@ -10,7 +8,6 @@ #include #include - #include "parse_helpers.hpp" namespace arborio { @@ -29,43 +26,43 @@ template using parse_hopefully = arb::util::expected eval_map {{"default", - make_call<>([] () { return arb::cv_policy{arb::default_cv_policy()}; }, + make_call<>([] () { return arb::default_cv_policy(); }, "'default' with no arguments")}, {"every-segment", - make_call<>([] () { return arb::cv_policy{arb::cv_policy_every_segment()}; }, + make_call<>([] () { return arb::cv_policy_every_segment(); }, "'every-segment' with no arguments")}, {"every-segment", - make_call([] (const region& r) { return arb::cv_policy{arb::cv_policy_every_segment(r) }; }, + make_call([] (const region& r) { return arb::cv_policy_every_segment(r); }, "'every-segment' with one argument (every-segment (reg:region))")}, {"fixed-per-branch", - make_call([] (int i) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i) }; }, + make_call([] (int i) { return arb::cv_policy_fixed_per_branch(i); }, "'every-segment' with one argument (fixed-per-branch (count:int))")}, {"fixed-per-branch", - make_call([] (int i, const region& r) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r) }; }, + make_call([] (int i, const region& r) { return arb::cv_policy_fixed_per_branch(i, r); }, "'every-segment' with two arguments (fixed-per-branch (count:int) (reg:region))")}, {"fixed-per-branch", - make_call([] (int i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r, f) }; }, - "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:int))")}, + make_call([] (int i, const region& r, cv_policy_flag f) { return arb::cv_policy_fixed_per_branch(i, r, f); }, + "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:flag))")}, {"max-extent", - make_call([] (double i) { return arb::cv_policy{arb::cv_policy_max_extent(i) }; }, + make_call([] (double i) { return arb::cv_policy_max_extent(i); }, "'max-extent' with one argument (max-extent (length:double))")}, {"max-extent", - make_call([] (double i, const region& r) { return arb::cv_policy{arb::cv_policy_max_extent(i, r) }; }, + make_call([] (double i, const region& r) { return arb::cv_policy_max_extent(i, r); }, "'max-extent' with two arguments (max-extent (length:double) (reg:region))")}, {"max-extent", - make_call([] (double i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_max_extent(i, r, f) }; }, - "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:int))")}, + make_call([] (double i, const region& r, cv_policy_flag f) { return arb::cv_policy_max_extent(i, r, f); }, + "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:flag))")}, {"single", - make_call<>([] () { return arb::cv_policy{arb::cv_policy_single()}; }, + make_call<>([] () { return arb::cv_policy_single(); }, "'single' with no arguments")}, {"single", - make_call([] (const region& r) { return arb::cv_policy{arb::cv_policy_single(r) }; }, + make_call([] (const region& r) { return arb::cv_policy_single(r); }, "'single' with one argument (single (reg:region))")}, {"explicit", - make_call([] (const locset& l) { return arb::cv_policy{arb::cv_policy_explicit(l) }; }, + make_call([] (const locset& l) { return arb::cv_policy_explicit(l); }, "'explicit' with one argument (explicit (ls:locset))")}, {"explicit", - make_call([] (const locset& l, const region& r) { return arb::cv_policy{arb::cv_policy_explicit(l, r) }; }, + make_call([] (const locset& l, const region& r) { return arb::cv_policy_explicit(l, r); }, "'explicit' with two arguments (explicit (ls:locset) (reg:region))")}, {"join", make_fold([](cv_policy l, cv_policy r) { return l + r; }, @@ -73,6 +70,12 @@ eval_map {{"default", {"replace", make_fold([](cv_policy l, cv_policy r) { return l | r; }, "'replace' with at least 2 arguments: (replace cv_policy cv_policy ...)")}, + {"flag-none", + make_call<>([] () { return cv_policy_flag::none; }, + "'flag:none' with no arguments")}, + {"flag-interior-forks", + make_call<>([] () { return cv_policy_flag::interior_forks; }, + "'flag-interior-forks' with no arguments")}, }; parse_hopefully eval(const s_expr& e); diff --git a/arborio/include/arborio/cv_policy_parse.hpp b/arborio/include/arborio/cv_policy_parse.hpp index 70836105c..6df585877 100644 --- a/arborio/include/arborio/cv_policy_parse.hpp +++ b/arborio/include/arborio/cv_policy_parse.hpp @@ -1,6 +1,5 @@ #pragma once -#include #include #include diff --git a/doc/concepts/cable_cell.rst b/doc/concepts/cable_cell.rst index 3364f20fb..5ed43566f 100644 --- a/doc/concepts/cable_cell.rst +++ b/doc/concepts/cable_cell.rst @@ -7,17 +7,27 @@ An Arbor *cable cell* is a full :ref:`description ` of a cell with morphology and cell dynamics like ion species and their properties, ion channels, synapses, gap junction mechanisms, stimuli and threshold detectors. -Cable cells are constructed from three components: - -* :ref:`Morphology `: a description of the geometry and branching structure of the cell shape. -* :ref:`Label dictionary `: a set of definitions and a :abbr:`DSL (domain specific language)` that refer to regions and locations on the cell morphology. -* :ref:`Decor `: a description of the dynamics on the cell, placed according to the named rules in the dictionary. It can reference :ref:`mechanisms` from mechanism catalogues. +Cable cells are constructed from four components: + +* :ref:`Morphology `: a description of the geometry and branching + structure of the cell shape. +* :ref:`Label dictionary `: a set of definitions and a :abbr:`DSL + (domain specific language)` that refer to regions and locations on the cell + morphology. +* :ref:`Decor `: a description of the dynamics on the + cell, placed according to the named rules in the dictionary. It can reference + :ref:`mechanisms` from mechanism catalogues. +* :ref:`Discretization `, :ref:`decoration `, :ref:`morphology `, or :ref:`cable cell ` object. These are denoted as arbor-components. Arbor-components need to be accompanied by *meta-data* -specifying the version of the format being used. The only version currently supported is ``0.9-dev``. +specifying the version of the format being used. The only version currently supported is ``0.10-dev``. .. label:: (version val:string) @@ -418,7 +418,7 @@ Label-dict .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (label-dict (region-def "my_soma" (tag 1)) (locset-def "root" (root)))) @@ -429,7 +429,7 @@ Decoration .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (decor (default (membrane-potential -55.000000)) (place (locset "root") (synapse (mechanism "expsyn")) "root_synapse") @@ -441,7 +441,7 @@ Morphology .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (morphology (branch 0 -1 (segment 0 (point 0 0 0 2) (point 4 0 0 2) 1) @@ -454,7 +454,7 @@ Cable-cell .. code:: lisp (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (cable-cell (label-dict (region-def "my_soma" (tag 1)) diff --git a/doc/python/cable_cell.rst b/doc/python/cable_cell.rst index b78a753eb..267160bb5 100644 --- a/doc/python/cable_cell.rst +++ b/doc/python/cable_cell.rst @@ -48,7 +48,7 @@ Cable cells # Construct a cable cell. cell = arbor.cable_cell(lmrf.morphology, decor, lmrf.labels) - .. method:: __init__(morphology, decorations, labels) + .. method:: __init__(morphology, decorations, labels=None, discretization=None) Constructor. @@ -58,15 +58,23 @@ Cable cells :type decorations: :py:class:`decor` :param labels: dictionary of labeled regions and locsets :type labels: :py:class:`label_dict` + :param discretization: discretization policy + :type discretization: :py:class:`cv_policy` - .. method:: placed_lid_range(index) + .. method:: discretization(policy) - Returns the range of local indexes assigned to a placement in the decorations as a tuple of two integers, - that define the range of indexes as a half open interval. + Set the cv_policy used to discretise the cell into control volumes for simulation. + + :param policy: The cv_policy. + :type policy: :py:class:`cv_policy` + + .. method:: discretization(policy) + :noindex: + + Set the cv_policy used to discretise the cell into control volumes for simulation. + + :param str policy: :ref:`string representation ` of a cv_policy. - :param index: the unique index of the placement. - :type index: int - :rtype: tuple(int, int) .. py:class:: ion diff --git a/doc/python/decor.rst b/doc/python/decor.rst index 8573fc47c..1f8744e9a 100644 --- a/doc/python/decor.rst +++ b/doc/python/decor.rst @@ -173,20 +173,6 @@ Cable cell decoration :type d: :py:class:`threshold_detector` :param str label: the label of the group of detectors on the locset. - .. method:: discretization(policy) - - Set the cv_policy used to discretise the cell into control volumes for simulation. - - :param policy: The cv_policy. - :type policy: :py:class:`cv_policy` - - .. method:: discretization(policy) - :noindex: - - Set the cv_policy used to discretise the cell into control volumes for simulation. - - :param str policy: :ref:`string representation ` of a cv_policy. - .. method:: paintings() Returns a list of tuples ``(region, painted_object)`` for inspection. diff --git a/doc/tutorial/single_cell_detailed.rst b/doc/tutorial/single_cell_detailed.rst index 5fe68d012..e7f6d6baa 100644 --- a/doc/tutorial/single_cell_detailed.rst +++ b/doc/tutorial/single_cell_detailed.rst @@ -30,8 +30,8 @@ geometry and dynamics which is constructed from 3 components: 2. A **label dictionary** storing labelled expressions which define regions and locations of interest on the cell. 3. A **decor** defining various properties and dynamics on these regions and locations. - The decor also includes hints about how the cell is to be modelled under the hood, by - splitting it into discrete control volumes (CV). +4. Finally, the cell needs to know how we will be splitting it into discrete + control volumes (CV). The morphology ^^^^^^^^^^^^^^^ diff --git a/example/busyring/ring.cpp b/example/busyring/ring.cpp index 57a7e3384..b4903f84c 100644 --- a/example/busyring/ring.cpp +++ b/example/busyring/ring.cpp @@ -421,9 +421,7 @@ arb::cable_cell complex_cell(arb::cell_gid_type gid, const cell_parameters& para if (params.synapses>1) decor.place(syns, arb::synapse("expsyn"), "s"); - decor.set_default(arb::cv_policy_every_segment()); - - return {arb::morphology(tree), decor}; + return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; } arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& params) { @@ -453,7 +451,5 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param } // Make a CV between every sample in the sample tree. - decor.set_default(arb::cv_policy_every_segment()); - - return {arb::morphology(tree), decor}; + return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; } diff --git a/example/diffusion/diffusion.cpp b/example/diffusion/diffusion.cpp index 0d2952bac..80cb3fe34 100644 --- a/example/diffusion/diffusion.cpp +++ b/example/diffusion/diffusion.cpp @@ -26,7 +26,7 @@ namespace U = arb::units; struct linear: public recipe { linear(double ext, double dx, double Xi, double beta): l{ext}, d{dx}, i{Xi}, b{beta} { gprop.default_parameters = neuron_parameter_defaults; - gprop.default_parameters.discretization = cv_policy_max_extent{d}; + gprop.default_parameters.discretization = cv_policy_max_extent(d); gprop.add_ion("bla", 1, 23*U::mM, 42*U::mM, 0*U::mV, b*U::m2/U::s); } diff --git a/example/dryrun/branch_cell.hpp b/example/dryrun/branch_cell.hpp index 665e4f53e..d0ac1ff1b 100644 --- a/example/dryrun/branch_cell.hpp +++ b/example/dryrun/branch_cell.hpp @@ -118,12 +118,11 @@ inline arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters .place(arb::mlocation{0,0}, arb::threshold_detector{10*U::mV}, "detector") // Add spike threshold detector at the soma. - .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse") // Add a synapse to the mid point of the first dendrite. - .set_default(arb::cv_policy_every_segment()); // Make a CV between every sample in the sample tree. + .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse"); // Add a synapse to the mid point of the first dendrite. // Add additional synapses that will not be connected to anything. for (unsigned i=1u; i= d.size()) throw py::index_error("index out of range"); - return d.cables(index); - }, - "index"_a, "Return a list of cables representing the CV at the given index.") - .def("children", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.children(index); - }, - "index"_a, - "Return a list of indices of the CVs representing the children of the CV at the given index.") - .def("parent", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.parent(index); - }, - "index"_a, - "Return the index of the CV representing the parent of the CV at the given index.") - .def("__str__", [](const arb::cell_cv_data& p){return "";}) - .def("__repr__", [](const arb::cell_cv_data& p){return "";}); + .def_property_readonly("num_cv", [](const arb::cell_cv_data& data){return data.size();}, + "Return the number of CVs in the cell.") + .def("cables", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.cables(index); + }, + "index"_a, "Return a list of cables representing the CV at the given index.") + .def("children", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.children(index); + }, + "index"_a, + "Return a list of indices of the CVs representing the children of the CV at the given index.") + .def("parent", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.parent(index); + }, + "index"_a, + "Return the index of the CV representing the parent of the CV at the given index.") + .def("__str__", [](const arb::cell_cv_data& p){return "";}) + .def("__repr__", [](const arb::cell_cv_data& p){return "";}); m.def("cv_data", [](const arb::cable_cell& cell) { return arb::cv_data(cell);}, "cell"_a, "the cable cell", @@ -913,34 +913,22 @@ void register_cells(py::module& m) { }, "locations"_a, "detector"_a, "label"_a, "Add a voltage spike detector at each location in locations." - "The group of spike detectors has the label 'label', used for forming connections between cells.") - .def("discretization", - [](arb::decor& dec, const arb::cv_policy& p) { return dec.set_default(p); }, - py::arg("policy"), - "A cv_policy used to discretise the cell into compartments for simulation") - .def("discretization", - [](arb::decor& dec, const std::string& p) { - return dec.set_default(arborio::parse_cv_policy_expression(p).unwrap()); - }, - py::arg("policy"), - "An s-expression string representing a cv_policy used to discretise the " - "cell into compartments for simulation"); - + "The group of spike detectors has the label 'label', used for forming connections between cells."); cable_cell .def(py::init( - [](const arb::morphology& m, const arb::decor& d, const std::optional& l) { - if (l) return arb::cable_cell(m, d, l->dict); - return arb::cable_cell(m, d); + [](const arb::morphology& m, const arb::decor& d, const std::optional& l, const std::optional& p) { + if (l) return arb::cable_cell(m, d, l->dict, p); + return arb::cable_cell(m, d, {}, p); }), - "morphology"_a, "decor"_a, "labels"_a=py::none(), - "Construct with a morphology, decor, and label dictionary.") + "morphology"_a, "decor"_a, "labels"_a=py::none(), "discretization"_a=py::none(), + "Construct with a morphology, decor, label dictionary, and cv policy.") .def(py::init( - [](const arb::segment_tree& t, const arb::decor& d, const std::optional& l) { - if (l) return arb::cable_cell({t}, d, l->dict); - return arb::cable_cell({t}, d); + [](const arb::segment_tree& t, const arb::decor& d, const std::optional& l, const std::optional& p) { + if (l) return arb::cable_cell({t}, d, l->dict, p); + return arb::cable_cell({t}, d, {}, p); }), - "segment_tree"_a, "decor"_a, "labels"_a=py::none(), - "Construct with a morphology derived from a segment tree, decor, and label dictionary.") + "segment_tree"_a, "decor"_a, "labels"_a=py::none(), "discretization"_a=py::none(), + "Construct with a morphology derived from a segment tree, decor, label dictionary, and cv policy.") .def_property_readonly("num_branches", [](const arb::cable_cell& c) {return c.morphology().num_branches();}, "The number of unbranched cable sections in the morphology.") @@ -952,6 +940,21 @@ void register_cells(py::module& m) { .def("cables", [](arb::cable_cell& c, const char* label) {return c.concrete_region(arborio::parse_region_expression(label).unwrap()).cables();}, "label"_a, "The cable segments of the cell morphology for a region label.") + // Discretization + .def("discretization", + [](const arb::cable_cell& c) { return c.discretization(); }, + "The cv_policy used to discretise the cell into compartments for simulation") + .def("discretization", + [](arb::cable_cell& c, const arb::cv_policy& p) { return c.discretization(p); }, + py::arg("policy"), + "A cv_policy used to discretise the cell into compartments for simulation") + .def("discretization", + [](arb::cable_cell& c, const std::string& p) { + return c.discretization(arborio::parse_cv_policy_expression(p).unwrap()); + }, + py::arg("policy"), + "An s-expression string representing a cv_policy used to discretise the " + "cell into compartments for simulation") // Stringification .def("__repr__", [](const arb::cable_cell&){return "";}) .def("__str__", [](const arb::cable_cell&){return "";}); diff --git a/python/example/diffusion.py b/python/example/diffusion.py index 8dda5e78f..b4281815a 100644 --- a/python/example/diffusion.py +++ b/python/example/diffusion.py @@ -42,7 +42,6 @@ def event_generators(self, _): .set_ion("na", int_con=0.0 * U.mM, diff=0.005 * U.m2 / U.s) .place("(location 0 0.5)", A.synapse("inject/x=na", {"alpha": 200.0}), "Zap") .paint("(all)", A.density("decay/x=na")) - .discretization(A.cv_policy("(max-extent 5)")) # Set up ion diffusion .set_ion( "na", @@ -57,7 +56,7 @@ def event_generators(self, _): prb = [ A.cable_probe_ion_diff_concentration_cell("na", "nad"), ] -cel = A.cable_cell(tree, dec) +cel = A.cable_cell(tree, dec, discretization=A.cv_policy("(max-extent 5)")) rec = recipe(cel, prb) sim = A.simulation(rec) hdl = (sim.sample((0, "nad"), A.regular_schedule(0.1 * U.ms)),) diff --git a/python/example/network_two_cells_gap_junctions.py b/python/example/network_two_cells_gap_junctions.py index c12982307..3a99f078c 100755 --- a/python/example/network_two_cells_gap_junctions.py +++ b/python/example/network_two_cells_gap_junctions.py @@ -60,11 +60,11 @@ def cell_description(self, gid): ) if self.max_extent is not None: - decor.discretization(A.cv_policy_max_extent(self.max_extent)) + cvp = A.cv_policy_max_extent(self.max_extent) else: - decor.discretization(A.cv_policy_single()) + cvp = A.cv_policy_single() - return A.cable_cell(tree, decor, labels) + return A.cable_cell(tree, decor, labels, cvp) def gap_junctions_on(self, gid): return [A.gap_junction_connection(((gid + 1) % 2, "gj"), "gj", 1)] diff --git a/python/example/probe_lfpykit.py b/python/example/probe_lfpykit.py index 795b325d0..45a0ba5a9 100644 --- a/python/example/probe_lfpykit.py +++ b/python/example/probe_lfpykit.py @@ -88,15 +88,16 @@ def probes(self, _): .paint("(all)", A.density("pas/e=-65", g=0.0001)) # attach the stimulus .place(str(clamp_location), iclamp, "iclamp") - # use a fixed 3 CVs per branch - .discretization(A.cv_policy_fixed_per_branch(3)) ) +# use a fixed 3 CVs per branch +cvp = A.cv_policy_fixed_per_branch(3) + # place_pwlin can be queried with region/locset expressions to obtain # geometrical objects, like points and segments, essentially recovering # geometry from morphology. ppwl = A.place_pwlin(morphology) -cell = A.cable_cell(morphology, decor) +cell = A.cable_cell(morphology, decor, discretization=cvp) # instantiate recipe with cell rec = Recipe(cell) diff --git a/python/example/single_cell_allen.py b/python/example/single_cell_allen.py index b3d30c441..2cbcb2da0 100644 --- a/python/example/single_cell_allen.py +++ b/python/example/single_cell_allen.py @@ -131,10 +131,10 @@ def make_cell(base, swc, fit): decor.place('"midpoint"', A.threshold_detector(-40 * U.mV), "sd") # (10) discretisation strategy: max compartment length - decor.discretization(A.cv_policy_max_extent(20)) + cvp = A.cv_policy_max_extent(20) # (11) Create cell - return A.cable_cell(morphology, decor, labels), offset + return A.cable_cell(morphology, decor, labels, cvp), offset # (12) Create cell, model diff --git a/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc b/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc index ad5cfa7de..0370e9754 100644 --- a/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc +++ b/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc @@ -1,6 +1,6 @@ (arbor-component (meta-data - (version "0.9-dev")) + (version "0.10-dev")) (morphology (branch 0 -1 (segment 0 diff --git a/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc b/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc index 317282f7d..3ec66000d 100644 --- a/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc +++ b/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc @@ -1,6 +1,6 @@ (arbor-component (meta-data - (version "0.9-dev")) + (version "0.10-dev")) (morphology (branch 0 -1 (segment 0 diff --git a/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc b/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc index 29831b671..73007e7ff 100644 --- a/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc +++ b/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (decor (default (membrane-potential -65 (scalar 1.0))) (default (temperature-kelvin 307.14999999999998 (scalar 1.0))) diff --git a/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc b/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc index c15ec6057..a6ff3884a 100644 --- a/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc +++ b/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (label-dict (region-def "all" (all)) (region-def "apic" (tag 4)) diff --git a/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc b/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc index c46b2578e..bb67b8b82 100644 --- a/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc +++ b/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (decor (paint (region "soma") (membrane-capacitance 0.01 (scalar 1))) (paint (region "soma") (density (mechanism "default::hh" ("gnabar" 0.10299326453483033) ("gkbar" 0.027124836082684685)))))) diff --git a/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc b/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc index c15ec6057..a6ff3884a 100644 --- a/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc +++ b/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.9-dev")) + (meta-data (version "0.10-dev")) (label-dict (region-def "all" (all)) (region-def "apic" (tag 4)) diff --git a/python/example/single_cell_bluepyopt_l5pc.py b/python/example/single_cell_bluepyopt_l5pc.py index 0a66b2e47..9639202e6 100755 --- a/python/example/single_cell_bluepyopt_l5pc.py +++ b/python/example/single_cell_bluepyopt_l5pc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import arbor +import arbor as A import pandas import seaborn import sys @@ -12,7 +12,6 @@ exit(-42) # (1) Read the cell JSON description referencing morphology, label dictionary and decor. - if len(sys.argv) < 2: print("No JSON file passed to the program") sys.exit(0) @@ -21,7 +20,6 @@ cell_json, morpho, decor, labels = ephys.create_acc.read_acc(cell_json_filename) # (2) Define labels for stimuli and voltage recordings. - labels["soma_center"] = "(location 0 0.5)" labels["dend1"] = ( '(restrict-to (distal-translate (proximal (region "apic")) 660)' @@ -29,42 +27,39 @@ ) # (3) Define stimulus and spike detector, adjust discretization - decor.place( - '"soma_center"', arbor.iclamp(tstart=295, duration=5, current=1.9), "soma_iclamp" + '"soma_center"', A.iclamp(tstart=295, duration=5, current=1.9), "soma_iclamp" ) # Add spike detector -decor.place('"soma_center"', arbor.threshold_detector(-10), "detector") +decor.place('"soma_center"', A.threshold_detector(-10), "detector") # Adjust discretization (single CV on soma, default everywhere else) -decor.discretization(arbor.cv_policy_max_extent(1.0) | arbor.cv_policy_single('"soma"')) +cvp = A.cv_policy_max_extent(1.0) | A.cv_policy_single('"soma"') # (4) Create the cell. - -cell = arbor.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) # (5) Declare a probe. - -probe = arbor.cable_probe_membrane_voltage('"dend1"') +probe = A.cable_probe_membrane_voltage('"dend1"') -# (6) Create a class that inherits from arbor.recipe -class single_recipe(arbor.recipe): +# (6) Create a class that inherits from A.recipe +class single_recipe(A.recipe): # (6.1) Define the class constructor def __init__(self, cell, probes): # The base C++ class constructor must be called first, to ensure that # all memory in the C++ class is initialized correctly. - arbor.recipe.__init__(self) + super().__init__() self.the_cell = cell self.the_probes = probes - self.the_props = arbor.neuron_cable_properties() + self.the_props = A.neuron_cable_properties() # Add catalogues with explicit qualifiers - self.the_props.catalogue = arbor.catalogue() - self.the_props.catalogue.extend(arbor.default_catalogue(), "default::") - self.the_props.catalogue.extend(arbor.bbp_catalogue(), "BBP::") + self.the_props.catalogue = A.catalogue() + self.the_props.catalogue.extend(A.default_catalogue(), "default::") + self.the_props.catalogue.extend(A.bbp_catalogue(), "BBP::") # (6.2) Override the num_cells method def num_cells(self): @@ -72,7 +67,7 @@ def num_cells(self): # (6.3) Override the cell_kind method def cell_kind(self, gid): - return arbor.cell_kind.cable + return A.cell_kind.cable # (6.4) Override the cell_description method def cell_description(self, gid): @@ -92,13 +87,13 @@ def global_properties(self, gid): recipe = single_recipe(cell, [probe]) # (7) Create a simulation (using defaults for context and partition_load_balance) -sim = arbor.simulation(recipe) +sim = A.simulation(recipe) # Instruct the simulation to record the spikes and sample the probe -sim.record(arbor.spike_recording.all) +sim.record(A.spike_recording.all) -probe_id = arbor.cell_member(0, 0) -handle = sim.sample(probe_id, arbor.regular_schedule(0.02)) +probe_id = A.cell_member(0, 0) +handle = sim.sample(probe_id, A.regular_schedule(0.02)) # (8) Run the simulation sim.run(tfinal=600, dt=0.025) diff --git a/python/example/single_cell_bluepyopt_simplecell.py b/python/example/single_cell_bluepyopt_simplecell.py index bffcdce50..78676d716 100755 --- a/python/example/single_cell_bluepyopt_simplecell.py +++ b/python/example/single_cell_bluepyopt_simplecell.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import arbor +import arbor as A import pandas import seaborn import sys @@ -12,7 +12,6 @@ exit(-42) # (1) Read the cell JSON description referencing morphology, label dictionary and decor. - if len(sys.argv) < 2: print("No JSON file passed to the program") sys.exit(0) @@ -21,33 +20,29 @@ cell_json, morpho, decor, labels = ephys.create_acc.read_acc(cell_json_filename) # (2) Define labels for stimuli and voltage recordings. - labels["soma_center"] = "(location 0 0.5)" # (3) Define stimulus and spike detector, adjust discretization - decor.place( - '"soma_center"', arbor.iclamp(tstart=100, duration=50, current=0.05), "soma_iclamp" + '"soma_center"', A.iclamp(tstart=100, duration=50, current=0.05), "soma_iclamp" ) # Add spike detector -decor.place('"soma_center"', arbor.threshold_detector(-10), "detector") +decor.place('"soma_center"', A.threshold_detector(-10), "detector") # Adjust discretization (single CV on soma, default everywhere else) -decor.discretization(arbor.cv_policy_max_extent(1.0) | arbor.cv_policy_single('"soma"')) +cvp = A.cv_policy_max_extent(1.0) | A.cv_policy_single('"soma"') # (4) Create the cell. - -cell = arbor.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) # (5) Make the single cell model. - -m = arbor.single_cell_model(cell) +m = A.single_cell_model(cell) # Add catalogues with qualifiers -m.properties.catalogue = arbor.catalogue() -m.properties.catalogue.extend(arbor.default_catalogue(), "default::") -m.properties.catalogue.extend(arbor.bbp_catalogue(), "BBP::") +m.properties.catalogue = A.catalogue() +m.properties.catalogue.extend(A.default_catalogue(), "default::") +m.properties.catalogue.extend(A.bbp_catalogue(), "BBP::") # (6) Attach voltage probe that samples at 50 kHz. m.probe("voltage", where='"soma_center"', frequency=50) diff --git a/python/example/single_cell_cable.py b/python/example/single_cell_cable.py index fc6cceb15..02da84643 100755 --- a/python/example/single_cell_cable.py +++ b/python/example/single_cell_cable.py @@ -103,9 +103,8 @@ def cell_description(self, _): ) policy = A.cv_policy_max_extent(self.cv_policy_max_extent) - decor.discretization(policy) - return A.cable_cell(tree, decor, labels) + return A.cable_cell(tree, decor, labels, policy) def probes(self, _): return self.the_probes diff --git a/python/example/single_cell_detailed.py b/python/example/single_cell_detailed.py index 1e4b7659b..7ea7220f1 100755 --- a/python/example/single_cell_detailed.py +++ b/python/example/single_cell_detailed.py @@ -71,12 +71,13 @@ .place('"root"', A.iclamp(30 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp1") .place('"root"', A.iclamp(50 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp2") .place('"axon_terminal"', A.threshold_detector(-10 * U.mV), "detector") - # Set discretisation: Soma as one CV, 1um everywhere else - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Set discretisation: Soma as one CV, 1um everywhere else +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') + # (4) Create the cell. -cell = A.cable_cell(morph, decor, labels) +cell = A.cable_cell(morph, decor, labels, cvp) # (5) Construct the model model = A.single_cell_model(cell) diff --git a/python/example/single_cell_detailed_recipe.py b/python/example/single_cell_detailed_recipe.py index 65013797a..b45f2076c 100644 --- a/python/example/single_cell_detailed_recipe.py +++ b/python/example/single_cell_detailed_recipe.py @@ -67,13 +67,13 @@ .place('"root"', A.iclamp(30 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp1") .place('"root"', A.iclamp(50 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp2") .place('"axon_terminal"', A.threshold_detector(-10 * U.mV), "detector") - # Set discretisation: Soma as one CV, 1um everywhere else - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Set discretisation: Soma as one CV, 1um everywhere else +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') -# (4) Create the cell. -cell = A.cable_cell(lmrf.morphology, decor, labels) +# (4) Create the cell +cell = A.cable_cell(lmrf.morphology, decor, labels, cvp) # (5) Create a class that inherits from A.recipe diff --git a/python/example/single_cell_nml.py b/python/example/single_cell_nml.py index 99f74e666..86eff5052 100755 --- a/python/example/single_cell_nml.py +++ b/python/example/single_cell_nml.py @@ -60,12 +60,13 @@ .place('"stim_site"', A.iclamp(8 * U.ms, 1 * U.ms, current=4 * U.nA), "iclamp3") # Detect spikes at the soma with a voltage threshold of -10 mV. .place('"axon_end"', A.threshold_detector(-10 * U.mV), "detector") - # Set discretisation: Soma as one CV, 1um everywhere else - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Set discretisation: Soma as one CV, 1um everywhere else +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') + # Combine morphology with region and locset definitions to make a cable cell. -cell = A.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) print(cell.locations('"axon_end"')) diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py index 6455850ae..5186b7e1e 100755 --- a/python/example/single_cell_swc.py +++ b/python/example/single_cell_swc.py @@ -60,13 +60,14 @@ .place('"stim_site"', A.iclamp(8 * U.ms, 1 * U.ms, current=4 * U.nA), "iclamp3") # Detect spikes at the soma with a voltage threshold of -10 mV. .place('"axon_end"', A.threshold_detector(-10 * U.mV), "detector") - # Create the policy used to discretise the cell into CVs. - # Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. - .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) +# Create the policy used to discretise the cell into CVs. +# Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. +cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') + # Combine morphology with region and locset definitions to make a cable cell. -cell = A.cable_cell(morpho, decor, labels) +cell = A.cable_cell(morpho, decor, labels, cvp) # Make single cell model. m = A.single_cell_model(cell) diff --git a/python/strprintf.hpp b/python/strprintf.hpp index 638016772..2ccf12c1a 100644 --- a/python/strprintf.hpp +++ b/python/strprintf.hpp @@ -168,7 +168,7 @@ namespace impl { for (auto& x: s.seq_) { if (!first) o << s.sep_; first = false; - o << s.f(x); + o << s.f_(x); } return o; } diff --git a/python/test/unit/test_io.py b/python/test/unit/test_io.py index f9e4049aa..6a78beaa7 100644 --- a/python/test/unit/test_io.py +++ b/python/test/unit/test_io.py @@ -9,7 +9,7 @@ acc = """(arbor-component (meta-data - (version "0.9-dev")) + (version "0.10-dev")) (cable-cell (morphology (branch 0 -1 @@ -231,9 +231,7 @@ def cell_description(self, _): dec = A.decor() dec.paint("(all)", A.density("pas")) - dec.discretization(A.cv_policy("(max-extent 1)")) - - return A.cable_cell(tree, dec) + return A.cable_cell(tree, dec, discretization=A.cv_policy("(max-extent 1)")) def global_properties(self, _): return self.the_props diff --git a/test/common_cells.cpp b/test/common_cells.cpp index 903e4e0a4..d275de09b 100644 --- a/test/common_cells.cpp +++ b/test/common_cells.cpp @@ -97,11 +97,9 @@ mcable soma_cell_builder::cable(mcable cab) const { // Add a new branch that is attached to parent_branch. // Returns the id of the new branch. -msize_t soma_cell_builder::add_branch( - msize_t parent_branch, - double len, double r1, double r2, int ncomp, - const std::string& region) -{ +msize_t soma_cell_builder::add_branch(msize_t parent_branch, + double len, double r1, double r2, int ncomp, + const std::string& region) { // Get tag id of region (add a new tag if region does not already exist). int tag = get_tag(region); @@ -147,18 +145,13 @@ cable_cell_description soma_cell_builder::make_cell() const { // Make label dictionary with one entry for each tag. label_dict dict; - for (auto& tag: tag_map) { - dict.set(tag.first, reg::tagged(tag.second)); + for (auto& [k, v]: tag_map) { + dict.set(k, reg::tagged(v)); } auto boundaries = cv_boundaries; - for (auto& b: boundaries) { - b = location(b); - } - decor decorations; - decorations.set_default(cv_policy_explicit(boundaries)); - // Construct cable_cell from sample tree, dictionary and decorations. - return {std::move(tree), std::move(dict), std::move(decorations)}; + for (auto& b: boundaries) b = location(b); + return {std::move(tree), std::move(dict), {}, cv_policy_explicit(boundaries)}; } /* @@ -185,7 +178,7 @@ cable_cell_description make_cell_soma_only(bool with_stim) { "cc"); } - return {c.morph, c.labels, c.decorations}; + return c; } /* @@ -222,7 +215,7 @@ cable_cell_description make_cell_ball_and_stick(bool with_stim) { "cc"); } - return {c.morph, c.labels, c.decorations}; + return c; } /* @@ -265,7 +258,7 @@ cable_cell_description make_cell_ball_and_3stick(bool with_stim) { "cc1"); } - return {c.morph, c.labels, c.decorations}; + return c; } } // namespace arb diff --git a/test/common_cells.hpp b/test/common_cells.hpp index 6e9cda85a..cf72da985 100644 --- a/test/common_cells.hpp +++ b/test/common_cells.hpp @@ -14,10 +14,9 @@ struct cable_cell_description { morphology morph; label_dict labels; decor decorations; + std::optional discretization; - operator cable_cell() const { - return cable_cell(morph, decorations, labels); - } + operator cable_cell() const { return cable_cell(morph, decorations, labels, discretization); } }; class soma_cell_builder { @@ -38,7 +37,7 @@ class soma_cell_builder { // Add a new branch that is attached to parent_branch. // Returns the id of the new branch. msize_t add_branch(msize_t parent_branch, double len, double r1, double r2, int ncomp, - const std::string& region); + const std::string& region); mlocation location(mlocation) const; mcable cable(mcable) const; diff --git a/test/ubench/CMakeLists.txt b/test/ubench/CMakeLists.txt index 56bf7b404..962898765 100644 --- a/test/ubench/CMakeLists.txt +++ b/test/ubench/CMakeLists.txt @@ -26,7 +26,7 @@ foreach(bench_src ${bench_sources}) string(REGEX REPLACE "\\.[^.]*$" "" bench_exe ${bench_src}) add_executable(${bench_exe} EXCLUDE_FROM_ALL "${bench_src}") - target_link_libraries(${bench_exe} arbor arborio arbor-private-headers benchmark) + target_link_libraries(${bench_exe} arbor arborio arbor-private-headers ext-bench) target_compile_options(${bench_exe} PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) target_compile_definitions(${bench_exe} PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") list(APPEND bench_exe_list ${bench_exe}) diff --git a/test/ubench/fvm_discretize.cpp b/test/ubench/fvm_discretize.cpp index ce9cc719c..e78558ac2 100644 --- a/test/ubench/fvm_discretize.cpp +++ b/test/ubench/fvm_discretize.cpp @@ -60,7 +60,6 @@ void run_cv_geom_explicit(benchmark::State& state) { while (state.KeepRunning()) { auto ends = cv_policy_every_segment().cv_boundary_points(c); auto ends2 = cv_policy_explicit(std::move(ends)).cv_boundary_points(c); - benchmark::DoNotOptimize(cv_geometry(c, ends2)); } } @@ -68,38 +67,28 @@ void run_cv_geom_explicit(benchmark::State& state) { void run_discretize(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; const std::size_t ncv_per_branch = state.range(0); - - decor dec; auto morpho = from_swc(SWCFILE); - dec.set_default(cv_policy_fixed_per_branch(ncv_per_branch)); - while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_fixed_per_branch(ncv_per_branch)}, gdflt)); } } void run_discretize_every_segment(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; - - decor dec; auto morpho = from_swc(SWCFILE); - dec.set_default(cv_policy_every_segment()); - while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_every_segment()}, gdflt)); } } void run_discretize_explicit(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; - decor dec; auto morpho = from_swc(SWCFILE); - auto ends = cv_policy_every_segment().cv_boundary_points(cable_cell{morpho, {}}); - dec.set_default(cv_policy_explicit(std::move(ends))); + auto ends = cv_policy_every_segment().cv_boundary_points(cable_cell{morpho, {}, {}, {}}); while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_explicit(ends)}, gdflt)); } } diff --git a/test/ubench/mech_vec.cpp b/test/ubench/mech_vec.cpp index 880774fda..d8ac02016 100644 --- a/test/ubench/mech_vec.cpp +++ b/test/ubench/mech_vec.cpp @@ -63,7 +63,6 @@ class recipe_expsyn_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::tagged(1), arb::density("pas")); - decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); auto distribution = std::uniform_real_distribution(0.f, 1.0f); for(unsigned i = 0; i < num_synapse_; i++) { @@ -71,7 +70,7 @@ class recipe_expsyn_1_branch: public recipe { decor.place(arb::mlocation{0, distribution(gen)}, arb::synapse("expsyn"), "syn"); } - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -110,9 +109,7 @@ class recipe_pas_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("pas")); - decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); - - return arb::cable_cell {arb::morphology(tree), decor}; + return arb::cable_cell {arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -153,9 +150,8 @@ class recipe_pas_3_branches: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("pas")); - decor.set_default(arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -194,9 +190,8 @@ class recipe_hh_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("hh")); - decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -237,9 +232,8 @@ class recipe_hh_3_branches: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("hh")); - decor.set_default(arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor}; + return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { diff --git a/test/unit-distributed/test_communicator.cpp b/test/unit-distributed/test_communicator.cpp index 0e46459e0..546bfe875 100644 --- a/test/unit-distributed/test_communicator.cpp +++ b/test/unit-distributed/test_communicator.cpp @@ -202,10 +202,9 @@ namespace { arb::segment_tree tree; tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10*arb::units::mV}, "src"); decor.place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "tgt"); - return arb::cable_cell(arb::morphology(tree), decor); + return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10)); } return arb::lif_cell("src", "tgt"); } @@ -274,10 +273,9 @@ namespace { arb::segment_tree tree; tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10*arb::units::mV}, "src"); decor.place(arb::ls::uniform(arb::reg::all(), 0, size_, gid), arb::synapse("expsyn"), "tgt"); - return arb::cable_cell(arb::morphology(tree), decor); + return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10)); } cell_kind get_cell_kind(cell_gid_type gid) const override { return cell_kind::cable; diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index c419b8ebc..070ebb783 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -191,7 +191,7 @@ make_catalogue_standalone( target_link_libraries(dummy-catalogue PRIVATE arbor-private-deps) add_dependencies(unit dummy-catalogue) -target_link_libraries(unit PRIVATE arbor-private-deps) +target_link_libraries(unit PRIVATE arbor-private-deps ext-gtest) target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") target_compile_definitions(unit PRIVATE "-DLIBDIR=\"${PROJECT_BINARY_DIR}/lib\"") target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/test/unit/test_cv_geom.cpp b/test/unit/test_cv_geom.cpp index 7d49151b4..4aec03149 100644 --- a/test/unit/test_cv_geom.cpp +++ b/test/unit/test_cv_geom.cpp @@ -584,8 +584,7 @@ TEST(region_cv, custom_geometry) { { decor d; // Discretize by segment - d.set_default(cv_policy_every_segment()); - auto cell = cable_cell(m, d, l); + auto cell = cable_cell(m, d, l, cv_policy_every_segment()); auto geom = cv_data(cell); EXPECT_TRUE(geom); @@ -642,8 +641,7 @@ TEST(region_cv, custom_geometry) { {2, 0.2}, {2, 1} }); - d.set_default(cv_policy_explicit(ls)); - auto cell = cable_cell(m, d, l); + auto cell = cable_cell(m, d, l, cv_policy_explicit(ls)); auto geom = cv_data(cell); EXPECT_TRUE(geom); diff --git a/test/unit/test_cv_policy.cpp b/test/unit/test_cv_policy.cpp index 95f225971..918285919 100644 --- a/test/unit/test_cv_policy.cpp +++ b/test/unit/test_cv_policy.cpp @@ -1,6 +1,3 @@ -#include -#include -#include #include #include @@ -43,8 +40,7 @@ TEST(cv_policy, single) { for (region reg: {reg::all(), reg::branch(2), reg::cable(3, 0.25, 1.), join(reg::cable(1, 0.75, 1), reg::branch(3), reg::cable(2, 0, 0.5)), - join(reg::cable(2, 0, 0.5), reg::branch(3), reg::cable(4, 0, 0.5))}) - { + join(reg::cable(2, 0, 0.5), reg::branch(3), reg::cable(4, 0, 0.5))}) { locset expected = ls::cboundary(reg); EXPECT_TRUE(locset_eq(cell.provider(), ls::cboundary(reg), cv_policy_single(reg).cv_boundary_points(cell))); } @@ -94,9 +90,7 @@ TEST(cv_policy, explicit_policy) { TEST(cv_policy, empty_morphology) { // Any policy applied to an empty morphology should give an empty locset. - - using namespace cv_policy_flag; - + using enum cv_policy_flag; cv_policy policies[] = { cv_policy_fixed_per_branch(3), cv_policy_fixed_per_branch(3, interior_forks), @@ -116,7 +110,7 @@ TEST(cv_policy, empty_morphology) { } TEST(cv_policy, fixed_per_branch) { - using namespace cv_policy_flag; + using enum cv_policy_flag; using L = mlocation; // Root branch only: @@ -200,7 +194,7 @@ TEST(cv_policy, fixed_per_branch) { } TEST(cv_policy, max_extent) { - using namespace cv_policy_flag; + using enum cv_policy_flag; using L = mlocation; // Root branch only: @@ -268,7 +262,7 @@ TEST(cv_policy, max_extent) { } TEST(cv_policy, every_segment) { - using namespace cv_policy_flag; + using enum cv_policy_flag; // Cell with root branch and two child branches, with multiple samples per branch. // Fork is at (0., 0., 4.0). @@ -317,7 +311,7 @@ TEST(cv_policy, every_segment) { } TEST(cv_policy, domain) { - using namespace cv_policy_flag; + using enum cv_policy_flag; region reg1 = join(reg::branch(1), reg::cable(2, 0, 0.5)); region reg2 = join(reg::branch(1), reg::cable(2, 0.5, 1), reg::cable(4, 0, 1)); diff --git a/test/unit/test_diffusion.cpp b/test/unit/test_diffusion.cpp index 264450ce7..8f51e38fa 100644 --- a/test/unit/test_diffusion.cpp +++ b/test/unit/test_diffusion.cpp @@ -37,7 +37,7 @@ constexpr int with_gpu = -1; struct linear: public recipe { linear(double x, double d, double c): extent{x}, diameter{d}, cv_length{c} { gprop.default_parameters = arb::neuron_parameter_defaults; - gprop.default_parameters.discretization = arb::cv_policy_max_extent{cv_length}; + gprop.default_parameters.discretization = arb::cv_policy_max_extent(cv_length); // Stick morphology // -----x----- segment_tree tree; diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp index 58ab02a6e..7d7a031cd 100644 --- a/test/unit/test_fvm_layout.cpp +++ b/test/unit/test_fvm_layout.cpp @@ -1402,7 +1402,7 @@ TEST(fvm_layout, density_norm_area_partial) { hh_end["gkbar"] = end_gkbar; auto desc = builder.make_cell(); - desc.decorations.set_default(cv_policy_fixed_per_branch(1)); + desc.discretization = cv_policy_fixed_per_branch(1); desc.decorations.paint(builder.cable({1, 0., 0.3}), density(hh_begin)); desc.decorations.paint(builder.cable({1, 0.4, 1.}), density(hh_end)); @@ -1693,8 +1693,7 @@ TEST(fvm_layout, vinterp_cable) { // CV midpoints at branch pos 0.1, 0.3, 0.5, 0.7, 0.9. // Expect voltage reference locations to be CV modpoints. - d.set_default(cv_policy_fixed_per_branch(5)); - cable_cell cell{m, d}; + cable_cell cell{m, d, {}, cv_policy_fixed_per_branch(5)}; fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Test locations, either side of CV midpoints plus extrema, CV boundaries. @@ -1753,8 +1752,7 @@ TEST(fvm_layout, vinterp_forked) { // CV 0 contains branch 0 and the fork point; CV 1 and CV 2 have CV 0 as parent, // and contain branches 1 and 2 respectively, excluding the fork point. mlocation_list cv_ends{{1, 0.}, {2, 0.}}; - d.set_default(cv_policy_explicit(cv_ends)); - cable_cell cell{m, d}; + cable_cell cell{m, d, {}, cv_policy_explicit(cv_ends)}; fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Points in branch 0 should only get CV 0 for interpolation. @@ -1806,12 +1804,10 @@ TEST(fvm_layout, iinterp) { if (p.second.empty()) continue; decor d; - d.set_default(cv_policy_fixed_per_branch(3)); - cells.emplace_back(cable_cell{p.second, d}); + cells.emplace_back(cable_cell{p.second, d, {}, cv_policy_fixed_per_branch(3)}); label.push_back(p.first+": forks-at-end"s); - d.set_default(cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)); - cells.emplace_back(cable_cell{p.second, d}); + cells.emplace_back(cable_cell{p.second, d, {}, cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)}); label.push_back(p.first+": interior-forks"s); } @@ -1859,8 +1855,7 @@ TEST(fvm_layout, iinterp) { // CV 0 contains branch 0 and the fork point; CV 1 and CV 2 have CV 0 as parent, // and contain branches 1 and 2 respectively, excluding the fork point. mlocation_list cv_ends{{1, 0.}, {2, 0.}}; - d.set_default(cv_policy_explicit(cv_ends)); - cable_cell cell{m, d}; + cable_cell cell{m, d, {}, cv_policy_explicit(cv_ends)}; D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Expect axial current interpolations on branches 1 and 2 to match CV 1 and 2 @@ -1925,7 +1920,7 @@ TEST(fvm_layout, inhomogeneous_parameters) { auto param = neuron_parameter_defaults; param.membrane_capacitance = 42.0; - param.discretization = cv_policy_fixed_per_branch{30}; + param.discretization = cv_policy_fixed_per_branch(30); auto expected = std::vector{{8.37758,385.369,2}, // constant {8.37758,385.369,2}, diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp index 2529b9841..3772f223b 100644 --- a/test/unit/test_fvm_lowered.cpp +++ b/test/unit/test_fvm_lowered.cpp @@ -227,9 +227,9 @@ TEST(fvm_lowered, target_handles) { // (in increasing target order) descriptions[0].decorations.place(mlocation{0, 0.7}, synapse("expsyn"), "syn0"); descriptions[0].decorations.place(mlocation{0, 0.3}, synapse("expsyn"), "syn1"); + descriptions[1].decorations.place(mlocation{2, 0.2}, synapse("exp2syn"), "syn2"); descriptions[1].decorations.place(mlocation{2, 0.8}, synapse("expsyn"), "syn3"); - descriptions[1].decorations.place(mlocation{0, 0}, threshold_detector{3.3*arb::units::mV}, "detector"); cable_cell cells[] = {descriptions[0], descriptions[1]}; @@ -268,7 +268,6 @@ TEST(fvm_lowered, target_handles) { fvm_cell fvcell1(*context); auto fvm_info1 = fvcell1.initialize({0, 1}, cable1d_recipe(cells, false)); test_target_handles(fvcell1); - } TEST(fvm_lowered, stimulus) { @@ -825,7 +824,6 @@ TEST(fvm_lowered, post_events_shared_state) { tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(ncv_)); auto ndetectors = detectors_per_cell_[gid]; auto offset = 1.0 / ndetectors; @@ -834,7 +832,7 @@ TEST(fvm_lowered, post_events_shared_state) { } decor.place(arb::mlocation{0, 0.5}, synapse_, "syanpse"); - return arb::cable_cell(arb::morphology(tree), decor); + return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(ncv_)); } cell_kind get_cell_kind(cell_gid_type gid) const override { @@ -921,22 +919,20 @@ TEST(fvm_lowered, label_data) { tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); { arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(uniform(all(), 0, 3, 42), arb::synapse("expsyn"), "4_synapses"); decor.place(uniform(all(), 4, 4, 42), arb::synapse("expsyn"), "1_synapse"); decor.place(uniform(all(), 5, 5, 42), arb::threshold_detector{10*arb::units::mV}, "1_detector"); - cells_.push_back(arb::cable_cell(arb::morphology(tree), decor)); + cells_.push_back(arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10))); } { arb::decor decor; - decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(uniform(all(), 0, 2, 24), arb::threshold_detector{10*arb::units::mV}, "3_detectors"); decor.place(uniform(all(), 3, 4, 24), arb::threshold_detector{10*arb::units::mV}, "2_detectors"); decor.place(uniform(all(), 5, 6, 24), arb::junction("gj"), "2_gap_junctions"); decor.place(uniform(all(), 7, 7, 24), arb::junction("gj"), "1_gap_junction"); - cells_.push_back(arb::cable_cell(arb::morphology(tree), decor)); + cells_.push_back(arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10))); } } diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 43449d017..99a6b249a 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -108,7 +108,7 @@ void run_v_i_probe_test(context ctx) { builder.add_branch(0, 200, 1.0/2, 1.0/2, 1, "dend"); auto bs = builder.make_cell(); - bs.decorations.set_default(cv_policy_fixed_per_branch(1)); + bs.discretization = cv_policy_fixed_per_branch(1); auto stim = i_clamp::box(0.*U::ms, 100*U::ms, 0.3*U::nA); bs.decorations.place(mlocation{1, 1}, stim, "clamp"); @@ -209,12 +209,11 @@ void run_v_cell_probe_test(context ctx) { {"interior fork", cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)}, }; - for (auto& testcase: test_policies) { - SCOPED_TRACE(testcase.first); + for (auto& [name, policy]: test_policies) { + SCOPED_TRACE(name); decor d; - d.set_default(testcase.second); - cable_cell cell(m, d); + cable_cell cell(m, d, {}, policy); cable1d_recipe rec(cell, false); rec.add_probe(0, "U_m", cable_probe_membrane_voltage_cell{}); @@ -236,7 +235,7 @@ void run_v_cell_probe_test(context ctx) { // Independetly discretize the cell so we can follow cable–CV relationship. - cv_geometry geom(cell, testcase.second.cv_boundary_points(cell)); + cv_geometry geom(cell, policy.cv_boundary_points(cell)); // For each cable in metadata, get CV from geom and confirm raw handle is // state voltage + CV. @@ -271,7 +270,7 @@ void run_expsyn_g_probe_test(context ctx) { auto bs = builder.make_cell(); bs.decorations.place(loc0, synapse("expsyn"), "syn0"); bs.decorations.place(loc1, synapse("expsyn"), "syn1"); - bs.decorations.set_default(cv_policy_fixed_per_branch(2)); + bs.discretization = cv_policy_fixed_per_branch(2); auto run_test = [&](bool coalesce_synapses) { cable1d_recipe rec(cable_cell(bs), coalesce_synapses); @@ -362,7 +361,6 @@ void run_expsyn_g_cell_probe_test(context ctx) { auto policy = cv_policy_fixed_per_branch(3); arb::decor decor; - decor.set_default(policy); std::unordered_map expsyn_target_loc_map; unsigned n_expsyn = 0; for (unsigned bid = 0; bid<3u; ++bid) { @@ -376,7 +374,7 @@ void run_expsyn_g_cell_probe_test(context ctx) { } } - std::vector cells(2, arb::cable_cell(make_y_morphology(), decor)); + std::vector cells(2, arb::cable_cell(make_y_morphology(), decor, {}, policy)); // Weight for target (gid, lid) auto weight = [](auto gid, auto tgt) -> float { return tgt + 100*gid; }; @@ -492,11 +490,9 @@ void run_ion_density_probe_test(context ctx) { auto m = make_stick_morphology(); decor d; - d.set_default(cv_policy_fixed_per_branch(3)); // Calcium ions everywhere, half written by write_ca1, half by write_ca2. // Sodium ions only on distal half. - d.paint(mcable{0, 0., 0.5}, density("write_ca1")); d.paint(mcable{0, 0.5, 1.}, density("write_ca2")); d.paint(mcable{0, 0.5, 1.}, density("write_na3")); @@ -507,7 +503,7 @@ void run_ion_density_probe_test(context ctx) { mlocation loc1{0, 0.5}; mlocation loc2{0, 0.9}; - cable1d_recipe rec(cable_cell(m, d)); + cable1d_recipe rec(cable_cell(m, d, {}, cv_policy_fixed_per_branch(3))); rec.catalogue() = cat; rec.add_probe(0, "cai-l0", cable_probe_ion_int_concentration{loc0, "ca"}); @@ -648,13 +644,8 @@ void run_partial_density_probe_test(context ctx) { cable_cell cells[2]; // Each cell is a simple constant diameter cable, with 3 CVs each. - auto m = make_stick_morphology(); - decor d0, d1; - d0.set_default(cv_policy_fixed_per_branch(3)); - d1.set_default(cv_policy_fixed_per_branch(3)); - // Paint the mechanism on every second 10% interval of each cell. // Expected values on a CV are the weighted mean of the parameter values // over the intersections of the support and the CV. @@ -675,20 +666,21 @@ void run_partial_density_probe_test(context ctx) { auto mk_mech = [](double param) { return density(mechanism_desc("param_as_state").set("p", param)); }; + decor d0; d0.paint(mcable{0, 0.0, 0.1}, mk_mech(2)); d0.paint(mcable{0, 0.2, 0.3}, mk_mech(3)); d0.paint(mcable{0, 0.4, 0.5}, mk_mech(4)); d0.paint(mcable{0, 0.6, 0.7}, mk_mech(5)); d0.paint(mcable{0, 0.8, 0.9}, mk_mech(6)); + cells[0] = cable_cell(m, d0, {}, cv_policy_fixed_per_branch(3)); + decor d1; d1.paint(mcable{0, 0.1, 0.2}, mk_mech(7)); d1.paint(mcable{0, 0.3, 0.4}, mk_mech(8)); d1.paint(mcable{0, 0.5, 0.6}, mk_mech(9)); d1.paint(mcable{0, 0.7, 0.8}, mk_mech(10)); d1.paint(mcable{0, 0.9, 1.0}, mk_mech(11)); - - cells[0] = cable_cell(m, d0); - cells[1] = cable_cell(m, d1); + cells[1] = cable_cell(m, d1, {}, cv_policy_fixed_per_branch(3)); // Place probes in the middle of each 10% interval, i.e. at 0.05, 0.15, etc. struct test_probe { @@ -771,7 +763,6 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) { const unsigned n_cv = 3; cv_policy policy = cv_policy_fixed_per_branch(n_cv); - d.set_default(policy); d.place(mlocation{0, 0}, i_clamp(0.3*U::nA), "clamp"); @@ -783,7 +774,7 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) { d.set_default(membrane_capacitance{0.01*U::F/U::m2}); // [F/m²] auto tau = 0.1*U::ms; - cable1d_recipe rec(cable_cell(m, d)); + cable1d_recipe rec(cable_cell(m, d, {}, policy)); rec.catalogue() = cat; cable_cell cell(m, d); @@ -972,7 +963,7 @@ void run_v_sampled_probe_test(context ctx) { builder.add_branch(0, 200, 1.0/2, 1.0/2, 1, "dend"); auto bs = builder.make_cell(); - bs.decorations.set_default(cv_policy_fixed_per_branch(1)); + bs.discretization = cv_policy_fixed_per_branch(1); auto d0 = bs.decorations; auto d1 = bs.decorations; @@ -1057,9 +1048,7 @@ void run_total_current_probe_test(context ctx) { auto run_cells = [&](bool interior_forks) { auto flags = interior_forks? cv_policy_flag::interior_forks: cv_policy_flag::none; cv_policy policy = cv_policy_fixed_per_branch(n_cv_per_branch, flags); - d0.set_default(policy); - d1.set_default(policy); - std::vector cells = {{m, d0}, {m, d1}}; + std::vector cells = {{m, d0, {}, policy}, {m, d1, {}, policy}}; for (unsigned i = 0; i<2; ++i) { @@ -1163,17 +1152,15 @@ void run_stimulus_probe_test(context ctx) { cv_policy policy = cv_policy_fixed_per_branch(3); decor d0, d1; - d0.set_default(policy); d0.place(mlocation{0, 0.5}, i_clamp::box(stim_from, stim_until, 10.*U::nA), "clamp0"); d0.place(mlocation{0, 0.5}, i_clamp::box(stim_from, stim_until, 20.*U::nA), "clamp1"); double expected_stim0 = 30; - d1.set_default(policy); d1.place(mlocation{0, 1}, i_clamp::box(stim_from, stim_until, 30.*U::nA), "clamp0"); d1.place(mlocation{0, 1}, i_clamp::box(stim_from, stim_until, -10.*U::nA), "clamp1"); double expected_stim1 = 20; - std::vector cells = {{m, d0}, {m, d1}}; + std::vector cells = {{m, d0, {}, policy}, {m, d1, {}, policy}}; // Sample the cells during the stimulus, and after. diff --git a/test/unit/test_s_expr.cpp b/test/unit/test_s_expr.cpp index f08607ce5..c45701fe4 100644 --- a/test/unit/test_s_expr.cpp +++ b/test/unit/test_s_expr.cpp @@ -235,11 +235,10 @@ std::string round_trip_network_value(const char* in) { } } - TEST(cv_policies, round_tripping) { auto literals = {"(every-segment (tag 42))", - "(fixed-per-branch 23 (segment 0) 1)", - "(max-extent 23.1 (segment 0) 1)", + "(fixed-per-branch 23 (segment 0) (flag-interior-forks))", + "(max-extent 23.1 (segment 0) (flag-interior-forks))", "(single (segment 0))", "(explicit (terminal) (segment 0))", "(join (every-segment (tag 42)) (single (segment 0)))", @@ -252,8 +251,8 @@ TEST(cv_policies, round_tripping) { TEST(cv_policies, literals) { EXPECT_NO_THROW("(every-segment (tag 42))"_cvp); - EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) 1)"_cvp); - EXPECT_NO_THROW("(max-extent 23.1 (segment 0) 1)"_cvp); + EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) (flag-interior-forks))"_cvp); + EXPECT_NO_THROW("(max-extent 23.1 (segment 0) (flag-interior-forks))"_cvp); EXPECT_NO_THROW("(single (segment 0))"_cvp); EXPECT_NO_THROW("(explicit (terminal) (segment 0))"_cvp); EXPECT_NO_THROW("(join (every-segment (tag 42)) (single (segment 0)))"_cvp); @@ -860,8 +859,7 @@ TEST(decor_literals, round_tripping) { "(scaled-mechanism (density (mechanism \"pas\" (\"g\" 0.02))) (\"g\" (exp (add (radius 2.1) (scalar 3.2)))))", }; auto default_literals = { - "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))", - "(cv-policy (single (segment 0)))" + "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))" }; auto place_literals = { "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 10 0.25)", @@ -923,8 +921,7 @@ TEST(decor_expressions, round_tripping) { "(default (ion-internal-concentration \"ca\" 5 (scalar 75.1)))", "(default (ion-external-concentration \"h\" 6 (scalar -50.1)))", "(default (ion-reversal-potential \"na\" 7 (scalar 30)))", - "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))", - "(default (cv-policy (max-extent 2 (region \"soma\") 2)))" + "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))" }; auto decorate_place_literals = { "(place (location 3 0.2) (current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 0.5 0.25) \"clamp\")", @@ -1004,11 +1001,6 @@ TEST(decor, round_tripping) { " (default \n" " (ion-reversal-potential-method \"na\" \n" " (mechanism \"nernst\")))\n" - " (default \n" - " (cv-policy \n" - " (fixed-per-branch 10 \n" - " (all)\n" - " 1)))\n" " (paint \n" " (region \"dend\")\n" " (density \n" @@ -1274,7 +1266,11 @@ TEST(cable_cell, round_tripping) { " (110.000000 0.500000)\n" " (110.000000 0.000000))\n" " 0.000000 0.000000)\n" - " \"iclamp\"))))"; + " \"iclamp\"))\n" + " (cv-policy \n" + " (fixed-per-branch 10 \n" + " (all)\n" + " (flag-interior-forks)))))"; EXPECT_EQ(component_str, round_trip_component(component_str.c_str())); diff --git a/test/unit/test_sde.cpp b/test/unit/test_sde.cpp index ed9b1a1ef..30637dfeb 100644 --- a/test/unit/test_sde.cpp +++ b/test/unit/test_sde.cpp @@ -254,15 +254,13 @@ class simple_sde_recipe: public simple_recipe_base { // set cvs explicitly double const cv_size = 1.0; - dec.set_default(cv_policy_max_extent(cv_size)); - // generate cells unsigned const n1 = ncvs/2; for (unsigned int i=0; i Date: Fri, 18 Oct 2024 15:46:14 +0200 Subject: [PATCH 13/30] Allow probing of point state by tag. (#2417) This concludes the introduction of 'labels' for synapse identification / placements started back in PR #1504 by @noraabiakar. Labels are now used when probing a synapse's state. --- .github/workflows/benchmarks.yml | 4 +- arbor/cable_cell.cpp | 2 +- arbor/fvm_lowered_cell_impl.hpp | 68 ++++++++---- arbor/include/arbor/cable_cell.hpp | 25 ++++- doc/python/probe_sample.rst | 172 +++++++++++++++++------------ example/lfp/lfp.cpp | 2 +- example/probe-demo/probe-demo.cpp | 89 +++++++++------ python/example/single_cell_stdp.py | 4 +- python/probes.cpp | 14 ++- python/test/unit/test_probes.py | 6 +- test/unit/test_probe.cpp | 20 ++-- 11 files changed, 253 insertions(+), 153 deletions(-) diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index d30677469..be1014b85 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -12,8 +12,8 @@ jobs: strategy: fail-fast: false env: - CC: gcc-11 - CXX: g++-11 + CC: gcc-12 + CXX: g++-12 steps: - name: Get build dependencies run: | diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp index a8098ab14..41e9c892a 100644 --- a/arbor/cable_cell.cpp +++ b/arbor/cable_cell.cpp @@ -126,7 +126,7 @@ struct cable_cell_impl { cell_lid_type first = lid; for (const auto& loc: locs) { - placed p{loc, lid++, item}; + placed p{loc, lid++, item, label}; mm.push_back(p); } auto range = lid_range(first, lid); diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index 730471d10..d69503566 100644 --- a/arbor/fvm_lowered_cell_impl.hpp +++ b/arbor/fvm_lowered_cell_impl.hpp @@ -14,6 +14,7 @@ #include #include +#include #include #include #include @@ -882,15 +883,15 @@ void resolve_probe(const cable_probe_density_state_cell& p, probe_resolution_dat } inline -auto point_info_of(cell_lid_type target, +auto point_info_of(cell_tag_type target, + cell_lid_type lid, int mech_index, const mlocation_map& instances, const std::vector& multiplicity) { - - auto opt_i = util::binary_search_index(instances, target, [](auto& item) { return item.lid; }); + auto opt_i = util::binary_search_index(instances, lid, [](auto& item) { return item.lid; }); if (!opt_i) throw arbor_internal_error("inconsistent mechanism state"); - - return cable_probe_point_info {target, + return cable_probe_point_info {std::move(target), + lid, multiplicity.empty() ? 1u: multiplicity.at(mech_index), instances[*opt_i].loc}; } @@ -903,6 +904,7 @@ void resolve_probe(const cable_probe_point_state& p, probe_resolution_data& R const auto& mech = p.mechanism; const auto& state = p.state; const auto& target = p.target; + const auto& t_hash = hash_value(target); const auto& data = R.mechanism_state(mech, state); if (!R.mech_instance_by_name.count(mech)) return; const auto mech_id = R.mech_instance_by_name.at(mech)->mechanism_id(); @@ -913,17 +915,27 @@ void resolve_probe(const cable_probe_point_state& p, probe_resolution_data& R // Convert cell-local target number to cellgroup target number. const auto& divs = R.M.target_divs; auto cell = R.cell_idx; - auto cg = target + divs.at(cell); - if (cg >= divs.at(cell + 1)) return; - - const auto& handle = R.handles.at(cg); - if (handle.mech_id != mech_id) return; - auto mech_index = handle.mech_index; - R.result.push_back(fvm_probe_scalar{{data + mech_index}, - point_info_of(target, - mech_index, - synapses.at(mech), - R.M.mechanisms.at(mech).multiplicity)}); + auto cg_lo = divs.at(cell); + auto cg_hi = divs.at(cell + 1); + const auto& [lr_beg, lr_end] = R.cell + .synapse_ranges() + .equal_range(t_hash); + for (auto lr = lr_beg; lr != lr_end; ++lr) { + const auto& [lid_beg, lid_end] = lr->second; + for (auto lid = lid_beg; lid != lid_end; ++lid) { + auto cg = lid + cg_lo; + if (cg >= cg_hi) continue; + const auto& handle = R.handles.at(cg); + if (handle.mech_id != mech_id) return; + auto mech_index = handle.mech_index; + R.result.push_back(fvm_probe_scalar{{data + mech_index}, + point_info_of(target, + lid, + mech_index, + synapses.at(mech), + R.M.mechanisms.at(mech).multiplicity)}); + } + } } template @@ -944,25 +956,35 @@ void resolve_probe(const cable_probe_point_state_cell& p, probe_resolution_data< auto cell_targets_beg = R.M.target_divs.at(R.cell_idx); auto cell_targets_end = R.M.target_divs.at(R.cell_idx + 1); - fvm_probe_multi r; - std::vector metadata; + const auto& decor = R.cell.decorations(); + fvm_probe_multi result; + std::vector metadata; + cell_lid_type lid = 0; for (auto target: util::make_span(cell_targets_beg, cell_targets_end)) { const auto& handle = R.handles.at(target); if (handle.mech_id != mech_id) continue; auto mech_index = handle.mech_index; - r.raw_handles.push_back(data + mech_index); + result.raw_handles.push_back(data + mech_index); + + // Convert to cell-local target index. + const auto& ins = placed_instances.at(lid); + auto lid = target - cell_targets_beg; + auto tag = decor.tag_of(ins.tag); - metadata.push_back(point_info_of(target - cell_targets_beg, // Convert to cell-local target index. + metadata.push_back(point_info_of(tag, + lid, mech_index, placed_instances, multiplicity)); + ++lid; } - r.metadata = std::move(metadata); - r.shrink_to_fit(); - R.result.push_back(std::move(r)); + + result.metadata = std::move(metadata); + result.shrink_to_fit(); + R.result.push_back(std::move(result)); } template diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 3fd84f7b4..3ef19cbcf 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -53,7 +53,8 @@ using cable_sample_range = std::pair; // // Metadata for point process probes. struct ARB_SYMBOL_VISIBLE cable_probe_point_info { - cell_lid_type target; // Target number of point process instance on cell. + cell_tag_type target; // Target tag of point process instance on cell. + cell_lid_type lid; // Target lid of point process instance on cell. unsigned multiplicity; // Number of combined instances at this site. mlocation loc; // Point on cell morphology where instance is placed. }; @@ -109,6 +110,7 @@ struct ARB_SYMBOL_VISIBLE cable_probe_density_state { }; // Value of state variable `state` in density mechanism `mechanism` across components of the cell. +// // Sample value type: `cable_sample_range` // Sample metadata type: `mcable_list` struct ARB_SYMBOL_VISIBLE cable_probe_density_state_cell { @@ -117,16 +119,30 @@ struct ARB_SYMBOL_VISIBLE cable_probe_density_state_cell { }; // Value of state variable `key` in point mechanism `source` at target `target`. +// // Sample value type: `double` // Sample metadata type: `cable_probe_point_info` struct ARB_SYMBOL_VISIBLE cable_probe_point_state { - cell_lid_type target; + cell_tag_type target; std::string mechanism; std::string state; + + // Engage in minimal hygeine. Ideally, we'd disable all nullptr constructors. + cable_probe_point_state(std::nullptr_t, std::string, std::string) = delete; + cable_probe_point_state() = delete; + + constexpr cable_probe_point_state(cell_tag_type t, std::string m, std::string s): + target(std::move(t)), mechanism(std::move(m)), state(std::move(s)) {} + constexpr cable_probe_point_state(const cable_probe_point_state&) = default; + constexpr cable_probe_point_state(cable_probe_point_state&&) = default; + constexpr cable_probe_point_state& operator=(const cable_probe_point_state&) = default; + constexpr cable_probe_point_state& operator=(cable_probe_point_state&&) = default; }; -// Value of state variable `key` in point mechanism `source` at every target with this mechanism. -// Metadata has one entry of type cable_probe_point_info for each matched (possibly coalesced) instance. +// Value of state variable `key` in point mechanism `source` at every target +// with this mechanism. Metadata has one entry of type cable_probe_point_info +// for each matched (possibly coalesced) instance. +// // Sample value type: `cable_sample_range` // Sample metadata type: `std::vector` struct ARB_SYMBOL_VISIBLE cable_probe_point_state_cell { @@ -224,6 +240,7 @@ struct placed { mlocation loc; cell_lid_type lid; T item; + hash_type tag; }; // Note: lid fields of elements of mlocation_map used in cable_cell are strictly increasing. diff --git a/doc/python/probe_sample.rst b/doc/python/probe_sample.rst index a5adf4f19..202857c06 100644 --- a/doc/python/probe_sample.rst +++ b/doc/python/probe_sample.rst @@ -96,39 +96,39 @@ Example return cell def probes(self, gid): - return [A.cable_probe_membrane_voltage('(location 0 0.5)'), - A.cable_probe_membrane_voltage_cell(), - A.cable_probe_membrane_voltage('(join (location 0 0) (location 0 1))'), + return [A.cable_probe_membrane_voltage('(location 0 0.5)', tag="Um-soma"), + A.cable_probe_membrane_voltage_cell(tag="Um-cell"), + A.cable_probe_membrane_voltage('(join (location 0 0) (location 0 1))', tag="Um-ends"), ] - # (4.6) Override the global_properties method + # Override the global_properties method def global_properties(self, kind): return A.neuron_cable_properties() recipe = single_recipe() sim = A.simulation(recipe) - handles = [sim.sample((0, n), A.regular_schedule(0.1*U.ms)) - for n in range(3) ] + handles = {tag: sim.sample((0, n), A.regular_schedule(0.1*U.ms)) + for tag in ["Um-soma", "Um-cell", "Um-ends"]} sim.run(tfinal=1*U.ms) - for hd in handles: - print("Handle", hd) + for tag, hd in handles.items(): + print(f"Handle {hd} Tag '{}'") for d, m in sim.samples(hd): print(" * Meta:", m) print(" * Payload:", d.shape) -This script has a single (scalar) probe, a single vector probe, and a probeset involving two scalar probes. +This script has a scalar probe, a vector probe, and a probeset involving two scalar probes. The script is complete and can be run with Arbor installed, and will output: .. code-block:: - Handle 0 + Handle 0 Tag 'Um-soma' * Meta: (location 0 0.5) * Payload: (10, 2) - Handle 1 + Handle 1 Tag 'Um-cell' * Meta: [(cable 0 0 1), (cable 0 1 1), (cable 1 0 0), (cable 2 0 0), (cable 1 0 1), (cable 2 0 1)] * Payload: (10, 7) - Handle 2 + Handle 2 Tag 'Um-ends' * Meta: (location 0 0) * Payload: (10, 2) * Meta: (location 0 1) @@ -143,177 +143,213 @@ API An opaque object that is the Python representation of :cpp:class:`probe_info`. - See below for ways to create probes. + See below for ways to create probes. In general, all probes are named via + the ``tag`` argument, as seen above. This tag is later used to retrieve the + data collected by the associated probes. Membrane voltage - .. py:function:: cable_probe_membrane_voltage(where) +^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_membrane_voltage(where, tag) Cell membrane potential (mV) at the sites specified by the location expression string ``where``. This value is spatially interpolated. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_membrane_voltage_cell() + .. py:function:: cable_probe_membrane_voltage_cell(tag) Cell membrane potential (mV) associated with each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Axial current - .. py:function:: cable_probe_axial_current(where) +^^^^^^^^^^^^^ + + .. py:function:: cable_probe_axial_current(where, tag) Estimation of intracellular current (nA) in the distal direction at the sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. Ionic current - .. py:function:: cable_probe_ion_current_density(where, ion) +^^^^^^^^^^^^^ + + .. py:function:: cable_probe_ion_current_density(where, ion, tag) Transmembrane current density (A/m²) associated with the given ``ion`` at sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_current_cell(ion) + .. py:function:: cable_probe_ion_current_cell(ion, tag) Transmembrane current (nA) associated with the given ``ion`` across each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Total ionic current - .. py:function:: cable_probe_total_ion_current_density(where) +^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_total_ion_current_density(where, tag) Transmembrane current density (A/m²) _excluding_ capacitive currents at the sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_total_ion_current_cell() + .. py:function:: cable_probe_total_ion_current_cell(tag) Transmembrane current (nA) _excluding_ capacitive currents across each cable in each CV of the cell discretization. Stimulus currents are not included. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Total transmembrane current - .. py:function:: cable_probe_total_current_cell() +^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_total_current_cell(tag) Transmembrane current (nA) *including* capacitive currents across each cable in each CV of the cell discretization. Stimulus currents are not included. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Total stimulus current - .. py:function:: cable_probe_stimulus_current_cell() +^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_stimulus_current_cell(tag) Total stimulus current (nA) across each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Density mechanism state variable - .. py:function:: cable_probe_density_state(where, mechanism, state) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_density_state(where, mechanism, state, tag) The value of the state variable ``state`` in the density mechanism ``mechanism`` at the sites specified by the location expression ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_density_state_cell(mechanism, state) + .. py:function:: cable_probe_density_state_cell(mechanism, state, tag) The value of the state variable ``state`` in the density mechanism ``mechanism`` on each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Point process state variable - .. py:function:: cable_probe_point_state(target, mechanism, state) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_point_state(target, mechanism, state, tag) The value of the state variable ``state`` in the point process ``mechanism`` associated with the target index ``target`` on the cell. If the given mechanism is not associated with the target index, no probe will be generated. - Metadata: an object of type :class:`cable_point_probe_info`, comprising three fields: + **Metadata**: + + .. py:class:: cable_point_probe_info - * ``target``: target index on the cell; + .. py:attribute:: target - * ``multiplicity``: number of targets sharing the same state in the discretization; + tag of target mechanism on the cell - * ``location``: :class:`location` object corresponding to the target site. + .. py:attribute:: lid - .. py:function:: cable_probe_point_state_cell(mechanism, state) + local id of target; + + .. py:attribute:: multiplicity + + number of targets sharing the same state in the discretization; + + .. py:attribute:: location + + :class:`location` object corresponding to the target site. + + .. py:function:: cable_probe_point_state_cell(mechanism, state, tag) The value of the state variable ``state`` in the point process ``mechanism`` at each of the targets where that mechanism is defined. - Metadata: a list of :class:`cable_point_probe_info` values, one for each matching + **Metadata**: a list of :class:`cable_point_probe_info` values, one for each matching target. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Ionic internal concentration - .. py:function:: cable_probe_ion_int_concentration(where, ion) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_ion_int_concentration(where, ion, tag) Ionic internal concentration (mmol/L) of the given ``ion`` at the sites specified by the location expression string ``where``. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_int_concentration_cell(ion) + .. py:function:: cable_probe_ion_int_concentration_cell(ion, tag) Ionic internal concentration (mmol/L) of the given ``ion`` in each cable in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Ionic external concentration - .. py:function:: cable_probe_ion_ext_concentration(where, ion) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - Ionic external concentration (mmol/L) of the given ``ion`` at the - sites specified by the location expression string ``where``. + .. py:function:: cable_probe_ion_ext_concentration(where, ion, tag) - Metadata: the explicit :class:`location` of the sample site. + Ionic external concentration (mM) of the given ``ion`` at the sites specified + by the location expression string ``where``. - .. py:function:: cable_probe_ion_ext_concentration_cell(ion) + **Metadata**: the explicit :class:`location` of the sample site. + + .. py:function:: cable_probe_ion_ext_concentration_cell(ion, tag) Ionic external concentration (mmol/L) of the given ``ion`` in each able in each CV of the cell discretization. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. Ionic diffusion concrentration - .. py:function:: cable_probe_ion_diff_concentration_cell(ion) +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + + .. py:function:: cable_probe_ion_diff_concentration_cell(ion, tag) Diffusive ionic concentration of the given ``ion`` for each cable in each CV. - Metadata: the explicit :class:`location` of the sample site. + **Metadata**: the explicit :class:`location` of the sample site. - Kind: :term:`vector probe`. + **Kind**: :term:`vector probe`. - .. py:function:: cable_probe_ion_diff_concentration(where, ion) + .. py:function:: cable_probe_ion_diff_concentration(where, ion, tag) Diffusive ionic concentration of the given ``ion`` at the sites specified by the location expression string ``where``. - Metadata: the list of corresponding :class:`cable` objects. + **Metadata**: the list of corresponding :class:`cable` objects. .. _pycablecell-probesample-lif: @@ -321,8 +357,8 @@ LIF Cell probing ================ Membrane voltage - .. py:function:: lif_probe_voltage() + .. py:function:: lif_probe_voltage(tag) Current cell membrane potential (mV). - Metadata: none + **Metadata**: none diff --git a/example/lfp/lfp.cpp b/example/lfp/lfp.cpp index f87cfd960..e32a1a3b1 100644 --- a/example/lfp/lfp.cpp +++ b/example/lfp/lfp.cpp @@ -46,7 +46,7 @@ struct lfp_demo_recipe: public arb::recipe { return {{arb::cable_probe_total_current_cell{}, "Itotal"}, {arb::cable_probe_membrane_voltage{synapse_location_}, "Um"}, {arb::cable_probe_total_ion_current_density{synapse_location_}, "Iion"}, - {arb::cable_probe_point_state{0, "expsyn", "g"}, "expsyn-g"}}; + {arb::cable_probe_point_state{"syn", "expsyn", "g"}, "expsyn-g"}}; } arb::cell_kind get_cell_kind(cell_gid_type) const override { diff --git a/example/probe-demo/probe-demo.cpp b/example/probe-demo/probe-demo.cpp index ed58297ae..2a108ab28 100644 --- a/example/probe-demo/probe-demo.cpp +++ b/example/probe-demo/probe-demo.cpp @@ -215,53 +215,72 @@ bool parse_options(options& opt, int& argc, char** argv) { using L = arb::mlocation; // Map probe argument to output variable name, scalarity, and a lambda that makes specific probe address from a location. - std::pair>> probe_tbl[] { + + using probe_spec_t = std::tuple>; + + std::pair probe_tbl[] { // located probes - {"v", {"v", true, [](double x) { return arb::cable_probe_membrane_voltage{L{0, x}}; }}}, - {"i_axial", {"i_axial", true, [](double x) { return arb::cable_probe_axial_current{L{0, x}}; }}}, - {"j_ion", {"j_ion", true, [](double x) { return arb::cable_probe_total_ion_current_density{L{0, x}}; }}}, - {"j_na", {"j_na", true, [](double x) { return arb::cable_probe_ion_current_density{L{0, x}, "na"}; }}}, - {"j_k", {"j_k", true, [](double x) { return arb::cable_probe_ion_current_density{L{0, x}, "k"}; }}}, - {"c_na", {"c_na", true, [](double x) { return arb::cable_probe_ion_int_concentration{L{0, x}, "na"}; }}}, - {"c_k", {"c_k", true, [](double x) { return arb::cable_probe_ion_int_concentration{L{0, x}, "k"}; }}}, - {"hh_m", {"hh_m", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "m"}; }}}, - {"hh_h", {"hh_h", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "h"}; }}}, - {"hh_n", {"hh_n", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "n"}; }}}, - {"expsyn_g", {"expsyn_ g", true, [](arb::cell_lid_type i) { return arb::cable_probe_point_state{i, "expsyn", "g"}; }}}, + {"v", {"v", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_membrane_voltage{L{0, x}}; }}}, + {"i_axial", {"i_axial", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_axial_current{L{0, x}}; }}}, + {"j_ion", {"j_ion", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_total_ion_current_density{L{0, x}}; }}}, + {"j_na", {"j_na", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_current_density{L{0, x}, "na"}; }}}, + {"j_k", {"j_k", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_current_density{L{0, x}, "k"}; }}}, + {"c_na", {"c_na", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_int_concentration{L{0, x}, "na"}; }}}, + {"c_k", {"c_k", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_int_concentration{L{0, x}, "k"}; }}}, + {"hh_m", {"hh_m", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "m"}; }}}, + {"hh_h", {"hh_h", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "h"}; }}}, + {"hh_n", {"hh_n", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "n"}; }}}, + {"expsyn_g", {"expsyn_ g", true, [](std::any a) { auto t = std::any_cast(a); return arb::cable_probe_point_state{t, "expsyn", "g"}; }}}, // all-of-cell probes - {"all_v", {"v", false, [](double) { return arb::cable_probe_membrane_voltage_cell{}; }}}, - {"all_i_ion", {"i_ion", false, [](double) { return arb::cable_probe_total_ion_current_cell{}; }}}, - {"all_i_na", {"i_na", false, [](double) { return arb::cable_probe_ion_current_cell{"na"}; }}}, - {"all_i_k", {"i_k", false, [](double) { return arb::cable_probe_ion_current_cell{"k"}; }}}, - {"all_i", {"i", false, [](double) { return arb::cable_probe_total_current_cell{}; }}}, - {"all_c_na", {"c_na", false, [](double) { return arb::cable_probe_ion_int_concentration_cell{"na"}; }}}, - {"all_c_k", {"c_k", false, [](double) { return arb::cable_probe_ion_int_concentration_cell{"k"}; }}}, - {"all_hh_m", {"hh_m", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "m"}; }}}, - {"all_hh_h", {"hh_h", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "h"}; }}}, - {"all_hh_n", {"hh_n", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "n"}; }}}, - {"all_expsyn_g", {"expsyn_ g", false, [](arb::cell_lid_type) { return arb::cable_probe_point_state_cell{"expsyn", "g"}; }}}, + {"all_v", {"v", false, [](std::any) { return arb::cable_probe_membrane_voltage_cell{}; }}}, + {"all_i_ion", {"i_ion", false, [](std::any) { return arb::cable_probe_total_ion_current_cell{}; }}}, + {"all_i_na", {"i_na", false, [](std::any) { return arb::cable_probe_ion_current_cell{"na"}; }}}, + {"all_i_k", {"i_k", false, [](std::any) { return arb::cable_probe_ion_current_cell{"k"}; }}}, + {"all_i", {"i", false, [](std::any) { return arb::cable_probe_total_current_cell{}; }}}, + {"all_c_na", {"c_na", false, [](std::any) { return arb::cable_probe_ion_int_concentration_cell{"na"}; }}}, + {"all_c_k", {"c_k", false, [](std::any) { return arb::cable_probe_ion_int_concentration_cell{"k"}; }}}, + {"all_hh_m", {"hh_m", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "m"}; }}}, + {"all_hh_h", {"hh_h", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "h"}; }}}, + {"all_hh_n", {"hh_n", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "n"}; }}}, + {"all_expsyn_g", {"expsyn_ g", false, [](std::any) { return arb::cable_probe_point_state_cell{"expsyn", "g"}; }}}, }; - std::tuple> probe_spec; - double probe_pos = 0.5; + probe_spec_t probe_spec; + std::any p_pos; + + auto double_or_string = [](const char* arg) -> to::maybe { + try { + return {{std::stod(arg)}}; + } + catch (const std::exception& e) { + return {{std::string(arg)}}; + } + }; to::option cli_opts[] = { { to::action(do_help), to::flag, to::exit, "-h", "--help" }, + { opt.sim_dt, "--dt" }, + { opt.sim_end, "--until" }, + { opt.sample_dt, "-t", "--sample" }, + { to::sink(p_pos, double_or_string), "-x", "--at" }, + { opt.n_cv, "-n", "--n-cv" }, { {probe_spec, to::keywords(probe_tbl)}, to::single }, - { opt.sim_dt, "--dt" }, - { opt.sim_end, "--until" }, - { opt.sample_dt, "-t", "--sample" }, - { probe_pos, "-x", "--at" }, - { opt.n_cv, "-n", "--n-cv" } }; + const auto& [p_name, p_scalar, p_addr] = probe_spec; + if (!p_pos.has_value() && (p_name == "exp_syn_g")) { + p_pos = "synapse0"; + } + else { + p_pos = 0.5; + } + if (!to::run(cli_opts, argc, argv+1)) return false; - if (!get<2>(probe_spec)) throw to::user_option_error("missing PROBE"); + if (!p_addr) throw to::user_option_error("missing PROBE"); if (argv[1]) throw to::user_option_error("unrecognized option"); - - opt.value_name = get<0>(probe_spec); - opt.scalar_probe = get<1>(probe_spec); - opt.probe_addr = get<2>(probe_spec)(probe_pos); + opt.value_name = p_name; + opt.scalar_probe = p_scalar; + opt.probe_addr = p_addr(p_pos); return true; } diff --git a/python/example/single_cell_stdp.py b/python/example/single_cell_stdp.py index d460fbd7d..2df25e81b 100755 --- a/python/example/single_cell_stdp.py +++ b/python/example/single_cell_stdp.py @@ -59,7 +59,9 @@ def event_generators(self, gid): def probes(self, gid): def mk(s, t): - return A.cable_probe_point_state(1, "expsyn_stdp", state=s, tag=t) + return A.cable_probe_point_state( + "stpd_synapse", "expsyn_stdp", state=s, tag=t + ) return [ A.cable_probe_membrane_voltage('"center"', "Um"), diff --git a/python/probes.cpp b/python/probes.cpp index ad734fff8..bb264688d 100644 --- a/python/probes.cpp +++ b/python/probes.cpp @@ -194,7 +194,7 @@ arb::probe_info cable_probe_density_state_cell(const char* mechanism, const char return {arb::cable_probe_density_state_cell{mechanism, state}, tag}; }; -arb::probe_info cable_probe_point_state(arb::cell_lid_type target, const char* mechanism, const char* state, const std::string& tag) { +arb::probe_info cable_probe_point_state(const arb::cell_tag_type& target, const char* mechanism, const char* state, const std::string& tag) { return {arb::cable_probe_point_state{target, mechanism, state}, tag}; } @@ -257,15 +257,17 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { cable_probe_point_info .def_readwrite("target", &arb::cable_probe_point_info::target, - "The target index of the point process instance on the cell.") + "The tag of the point process instance on the cell.") + .def_readwrite("lid", &arb::cable_probe_point_info::lid, + "The local index of the point process instance on the cell.") .def_readwrite("multiplicity", &arb::cable_probe_point_info::multiplicity, "Number of coalesced point processes (linear synapses) associated with this instance.") .def_readwrite("location", &arb::cable_probe_point_info::loc, "Location of point process instance on cell.") .def("__str__", [](arb::cable_probe_point_info m) { - return pprintf("", m.target, m.multiplicity, m.loc);}) + return pprintf("", m.target, m.lid, m.multiplicity, m.loc);}) .def("__repr__",[](arb::cable_probe_point_info m) { - return pprintf("", m.target, m.multiplicity, m.loc);}); + return pprintf("", m.target, m.lid, m.multiplicity, m.loc);}); // Probe address constructors: @@ -306,8 +308,8 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { "mechanism"_a, "state"_a, "tag"_a); m.def("cable_probe_point_state", &cable_probe_point_state, - "Probe specification for a cable cell point mechanism state variable value at a given target index.", - "target"_a, "mechanism"_a, "state"_a, "tag"_a); + "Probe specification for a cable cell point mechanism state variable value at a given target index.", + "target"_a, "mechanism"_a, "state"_a, "tag"_a); m.def("cable_probe_point_state_cell", &cable_probe_point_state_cell, "Probe specification for a cable cell point mechanism state variable value at every corresponding target.", diff --git a/python/test/unit/test_probes.py b/python/test/unit/test_probes.py index f72bfb4e3..c35a53154 100644 --- a/python/test/unit/test_probes.py +++ b/python/test/unit/test_probes.py @@ -58,7 +58,7 @@ def probes(self, _): ), A.cable_probe_density_state_cell(mechanism="hh", state="n", tag="hh-n-all"), A.cable_probe_point_state( - target=0, mechanism="expsyn", state="g", tag="expsyn-g" + target="syn0", mechanism="expsyn", state="g", tag="expsyn-g" ), A.cable_probe_point_state_cell( mechanism="exp2syn", state="B", tag="expsyn-B-all" @@ -127,14 +127,14 @@ def test_probe_addr_metadata(self): self.assertEqual(1, len(m)) self.assertEqual(A.location(0, 0.08), m[0].location) self.assertEqual(1, m[0].multiplicity) - self.assertEqual(0, m[0].target) + self.assertEqual("syn0", m[0].target) m = sim.probe_metadata((0, "expsyn-B-all")) self.assertEqual(1, len(m)) self.assertEqual(1, len(m[0])) self.assertEqual(A.location(0, 0.09), m[0][0].location) self.assertEqual(1, m[0][0].multiplicity) - self.assertEqual(1, m[0][0].target) + self.assertEqual("syn1", m[0][0].target) m = sim.probe_metadata((0, "ina")) self.assertEqual(1, len(m)) diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 99a6b249a..f9dc34ccb 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -274,8 +274,8 @@ void run_expsyn_g_probe_test(context ctx) { auto run_test = [&](bool coalesce_synapses) { cable1d_recipe rec(cable_cell(bs), coalesce_synapses); - rec.add_probe(0, "expsyn-g-1", cable_probe_point_state{0u, "expsyn", "g"}); - rec.add_probe(0, "expsyn-g-2", cable_probe_point_state{1u, "expsyn", "g"}); + rec.add_probe(0, "expsyn-g-1", cable_probe_point_state{"syn0", "expsyn", "g"}); + rec.add_probe(0, "expsyn-g-2", cable_probe_point_state{"syn1", "expsyn", "g"}); fvm_cell lcell(*ctx); auto fvm_info = lcell.initialize({0}, rec); @@ -365,11 +365,11 @@ void run_expsyn_g_cell_probe_test(context ctx) { unsigned n_expsyn = 0; for (unsigned bid = 0; bid<3u; ++bid) { for (unsigned j = 0; j<10; ++j) { - auto idx = (bid*10+j)*2; + auto idx = 2*(bid*10 + j); mlocation expsyn_loc{bid, 0.1*j}; decor.place(expsyn_loc, synapse("expsyn"), "syn"+std::to_string(idx)); expsyn_target_loc_map[2*n_expsyn] = expsyn_loc; - decor.place(mlocation{bid, 0.1*j+0.05}, synapse("exp2syn"), "syn"+std::to_string(idx+1)); + decor.place(mlocation{bid, 0.1*j + 0.05}, synapse("exp2syn"), "syn"+std::to_string(idx+1)); ++n_expsyn; } } @@ -377,7 +377,7 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::vector cells(2, arb::cable_cell(make_y_morphology(), decor, {}, policy)); // Weight for target (gid, lid) - auto weight = [](auto gid, auto tgt) -> float { return tgt + 100*gid; }; + auto weight = [](cell_gid_type gid, cell_lid_type lid) -> float { return lid + 100*gid; }; // Manually send an event to each expsyn synapse and integrate for a tiny time step. // Set up one stream per cell. @@ -429,14 +429,16 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::unordered_map cv_expsyn_count; for (unsigned j = 0; j Date: Mon, 21 Oct 2024 11:43:41 +0200 Subject: [PATCH 14/30] Reduce even more GPU allocations (#2394) Bubbling up ion access information from modcc allows us to skip allocation of `Xi` and `Xo` iff no mechanism reads those values. A minor problem: Sampling may try to touch data that doesn't exist; however who'd ask for `Xi` if they never use it? Stacking this PR on top of #2393 the memory consumption drops by a further 5% down to 77% if the original value. ### Todo - [x] Guard against sampling non-existing `Xi`/`Xo` --------- Co-authored-by: Jannik Luboeinski <33398515+jlubo@users.noreply.github.com> --- arbor/backends/gpu/matrix_state_fine.hpp | 47 +++++++------------ arbor/backends/gpu/shared_state.cpp | 46 ++++++++++++++----- arbor/backends/gpu/shared_state.hpp | 11 +++-- arbor/backends/multicore/cable_solver.hpp | 7 +-- arbor/backends/multicore/shared_state.cpp | 49 ++++++++++++++------ arbor/backends/multicore/shared_state.hpp | 11 +++-- arbor/backends/shared_state_base.hpp | 7 +-- arbor/fvm_layout.cpp | 55 ++++++++++++++--------- arbor/fvm_layout.hpp | 2 + arbor/fvm_lowered_cell_impl.hpp | 34 +++++++------- arbor/include/arbor/mechanism_abi.h | 4 +- arbor/include/arbor/mechinfo.hpp | 4 +- arbor/mechinfo.cpp | 18 ++++---- arbor/morph/embed_pwlin.cpp | 2 +- modcc/blocks.hpp | 11 ++++- modcc/printer/infoprinter.cpp | 13 +++--- python/test/unit/test_probes.py | 17 ++++--- test/unit/test_math.cpp | 16 +++---- test/unit/test_matrix.cpp | 21 ++++----- test/unit/test_probe.cpp | 48 +++++++++++++++++--- test/unit/test_sde.cpp | 13 +++--- 21 files changed, 270 insertions(+), 166 deletions(-) diff --git a/arbor/backends/gpu/matrix_state_fine.hpp b/arbor/backends/gpu/matrix_state_fine.hpp index 714e1f399..f052db0d9 100644 --- a/arbor/backends/gpu/matrix_state_fine.hpp +++ b/arbor/backends/gpu/matrix_state_fine.hpp @@ -3,7 +3,6 @@ #include #include -#include #include @@ -37,15 +36,11 @@ struct matrix_state_fine { array rhs; // [nA] // Required for matrix assembly - array cv_area; // [μm^2] array cv_capacitance; // [pF] // Invariant part of the matrix diagonal array invariant_d; // [μS] - // Solution in unpacked format - array solution_; - // Maximum number of branches in each level per block unsigned max_branches_per_level; @@ -82,16 +77,13 @@ struct matrix_state_fine { // `solver_format[perm[i]] = external_format[i]` iarray perm; - matrix_state_fine() = default; // constructor for fine-grained matrix. matrix_state_fine(const std::vector& p, - const std::vector& cell_cv_divs, - const std::vector& cap, - const std::vector& face_conductance, - const std::vector& area) - { + const std::vector& cell_cv_divs, + const std::vector& cap, + const std::vector& face_conductance) { using util::make_span; constexpr unsigned npos = unsigned(-1); @@ -360,7 +352,6 @@ struct matrix_state_fine { // cv_capacitance : flat // invariant_d : flat // cv_to_cell : flat - // area : flat // the invariant part of d is stored in in flat form std::vector invariant_d_tmp(matrix_size, 0); @@ -386,9 +377,6 @@ struct matrix_state_fine { // transform u_shuffled values into packed u vector. flat_to_packed(u_shuffled, u); - // the invariant part of d and cv_area are in flat form - cv_area = memory::make_const_view(area); - // the cv_capacitance can be copied directly because it is // to be stored in flat format cv_capacitance = memory::make_const_view(cap); @@ -408,19 +396,18 @@ struct matrix_state_fine { // voltage [mV] // current density [A/m²] // conductivity [kS/m²] - void assemble(const T dt, const_view voltage, const_view current, const_view conductivity) { - assemble_matrix_fine( - d.data(), - rhs.data(), - invariant_d.data(), - voltage.data(), - current.data(), - conductivity.data(), - cv_capacitance.data(), - cv_area.data(), - dt, - perm.data(), - size()); + void assemble(const T dt, const_view voltage, const_view current, const_view conductivity, const_view area_um2) { + assemble_matrix_fine(d.data(), + rhs.data(), + invariant_d.data(), + voltage.data(), + current.data(), + conductivity.data(), + cv_capacitance.data(), + area_um2.data(), + dt, + perm.data(), + size()); } void solve(array& to) { @@ -441,8 +428,8 @@ struct matrix_state_fine { void solve(array& voltage, - const T dt, const_view current, const_view conductivity) { - assemble(dt, voltage, current, conductivity); + const T dt, const_view current, const_view conductivity, const_view area_um2) { + assemble(dt, voltage, current, conductivity, area_um2); solve(voltage); } diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp index 928c938ba..c6213b54a 100644 --- a/arbor/backends/gpu/shared_state.cpp +++ b/arbor/backends/gpu/shared_state.cpp @@ -49,23 +49,42 @@ ion_state::ion_state(const fvm_ion_config& ion_data, write_eX_(ion_data.revpot_written), write_Xo_(ion_data.econc_written), write_Xi_(ion_data.iconc_written), + write_Xd_(ion_data.is_diffusive), + read_Xo_(ion_data.econc_written || ion_data.econc_read), // ensure that if we have W access, also R access is flagged + read_Xi_(ion_data.iconc_written || ion_data.iconc_read), node_index_(make_const_view(ion_data.cv)), iX_(ion_data.cv.size(), NAN), eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end()), - Xi_(ion_data.init_iconc.begin(), ion_data.init_iconc.end()), - Xd_(ion_data.cv.size(), NAN), - Xo_(ion_data.init_econc.begin(), ion_data.init_econc.end()), gX_(ion_data.cv.size(), NAN), - init_Xi_(make_const_view(ion_data.init_iconc)), - init_Xo_(make_const_view(ion_data.init_econc)), - reset_Xi_(make_const_view(ion_data.reset_iconc)), - reset_Xo_(make_const_view(ion_data.reset_econc)), - init_eX_(make_const_view(ion_data.init_revpot)), charge(1u, static_cast(ion_data.charge)), solver(std::move(ptr)) { - arb_assert(node_index_.size()==init_Xi_.size()); - arb_assert(node_index_.size()==init_Xo_.size()); - arb_assert(node_index_.size()==init_eX_.size()); + // We don't need to allocate these if we never use them... + if (read_Xi_) { + Xi_ = make_const_view(ion_data.init_iconc); + } + if (read_Xo_) { + Xo_ = make_const_view(ion_data.init_econc); + } + if (write_Xi_ || write_Xd_) { + // ... but this is used by Xd and Xi! + reset_Xi_ = make_const_view(ion_data.reset_iconc); + } + if (write_Xi_) { + init_Xi_ = make_const_view(ion_data.init_iconc); + arb_assert(node_index_.size()==init_Xi_.size()); + } + if (write_Xo_) { + init_Xo_ = make_const_view(ion_data.init_econc); + reset_Xo_ = make_const_view(ion_data.reset_econc); + arb_assert(node_index_.size()==init_Xo_.size()); + } + if (write_eX_) { + init_eX_ = make_const_view(ion_data.init_revpot); + arb_assert(node_index_.size()==init_eX_.size()); + } + if (write_Xd_) { + Xd_ = array(ion_data.cv.size(), NAN); + } } void ion_state::init_concentration() { @@ -81,10 +100,13 @@ void ion_state::zero_current() { void ion_state::reset() { zero_current(); - memory::copy(reset_Xi_, Xd_); if (write_Xi_) memory::copy(reset_Xi_, Xi_); if (write_Xo_) memory::copy(reset_Xo_, Xo_); if (write_eX_) memory::copy(init_eX_, eX_); + // This goes _last_ or at least after Xi since we might have removed reset_Xi + // when Xi is constant. Thus conditionally resetting Xi first and then copying + // Xi -> Xd is save in all cases. + if (write_Xd_) memory::copy(reset_Xi_, Xd_); } // istim_state methods: diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp index 030cbcdab..792fc7476 100644 --- a/arbor/backends/gpu/shared_state.hpp +++ b/arbor/backends/gpu/shared_state.hpp @@ -40,9 +40,14 @@ struct ARB_ARBOR_API ion_state { using solver_type = arb::gpu::diffusion_state; using solver_ptr = std::unique_ptr; - bool write_eX_; // is eX written? - bool write_Xo_; // is Xo written? - bool write_Xi_; // is Xi written? + bool write_eX_:1; // is eX written? + bool write_Xo_:1; // is Xo written? + bool write_Xi_:1; // is Xi written? + bool write_Xd_:1; // is Xd written? + bool read_eX_:1; // is eX read? + bool read_Xo_:1; // is Xo read? + bool read_Xi_:1; // is Xi read? + bool read_Xd_:1; // is Xd read? iarray node_index_; // Instance to CV map. array iX_; // (A/m²) current density diff --git a/arbor/backends/multicore/cable_solver.hpp b/arbor/backends/multicore/cable_solver.hpp index 3622ee688..bc748486a 100644 --- a/arbor/backends/multicore/cable_solver.hpp +++ b/arbor/backends/multicore/cable_solver.hpp @@ -23,7 +23,6 @@ struct cable_solver { array d; // [μS] array u; // [μS] array cv_capacitance; // [pF] - array cv_area; // [μm^2] array invariant_d; // [μS] invariant part of matrix diagonal cable_solver() = default; @@ -36,13 +35,11 @@ struct cable_solver { cable_solver(const std::vector& p, const std::vector& cell_cv_divs, const std::vector& cap, - const std::vector& cond, - const std::vector& area): + const std::vector& cond): parent_index(p.begin(), p.end()), cell_cv_divs(cell_cv_divs.begin(), cell_cv_divs.end()), d(size(), 0), u(size(), 0), cv_capacitance(cap.begin(), cap.end()), - cv_area(area.begin(), area.end()), invariant_d(size(), 0) { // Sanity check @@ -67,7 +64,7 @@ struct cable_solver { // * expects the voltage from its first argument // * will likewise overwrite the first argument with the solction template - void solve(T& rhs, const value_type dt, const_view current, const_view conductivity) { + void solve(T& rhs, const value_type dt, const_view current, const_view conductivity, const_view cv_area) { value_type * const ARB_NO_ALIAS d_ = d.data(); value_type * const ARB_NO_ALIAS r_ = rhs.data(); diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp index 17a57789f..e625c5b97 100644 --- a/arbor/backends/multicore/shared_state.cpp +++ b/arbor/backends/multicore/shared_state.cpp @@ -1,6 +1,5 @@ #include #include -#include #include #include #include @@ -58,24 +57,43 @@ ion_state::ion_state(const fvm_ion_config& ion_data, write_eX_(ion_data.revpot_written), write_Xo_(ion_data.econc_written), write_Xi_(ion_data.iconc_written), + write_Xd_(ion_data.is_diffusive), + read_Xo_(ion_data.econc_written || ion_data.econc_read), // ensure that if we have W access, also R access is flagged + read_Xi_(ion_data.iconc_written || ion_data.iconc_read), + read_Xd_(ion_data.is_diffusive), node_index_(ion_data.cv.begin(), ion_data.cv.end(), pad(alignment)), iX_(ion_data.cv.size(), NAN, pad(alignment)), eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)), - Xi_(ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)), - Xd_(ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)), - Xo_(ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)), gX_(ion_data.cv.size(), NAN, pad(alignment)), - init_Xi_(ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)), - init_Xo_(ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)), - reset_Xi_(ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)), - reset_Xo_(ion_data.reset_econc.begin(), ion_data.reset_econc.end(), pad(alignment)), - init_eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)), charge(1u, ion_data.charge, pad(alignment)), solver(std::move(ptr)) { - arb_assert(node_index_.size()==init_Xi_.size()); - arb_assert(node_index_.size()==init_Xo_.size()); - arb_assert(node_index_.size()==eX_.size()); - arb_assert(node_index_.size()==init_eX_.size()); + // We don't need to allocate these if we never use them... + if (read_Xi_) { + Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; + } + if (read_Xo_) { + Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; + } + if (write_Xi_ || write_Xd_) { + // ... but this is used by Xd and Xi! + reset_Xi_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; + } + if (write_Xi_) { + init_Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; + arb_assert(node_index_.size()==init_Xi_.size()); + } + if (write_Xo_) { + init_Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; + reset_Xo_ = {ion_data.reset_econc.begin(), ion_data.reset_econc.end(), pad(alignment)}; + arb_assert(node_index_.size()==init_Xo_.size()); + } + if (write_eX_) { + init_eX_ = {ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)}; + arb_assert(node_index_.size()==init_eX_.size()); + } + if (read_Xd_) { + Xd_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; + } } void ion_state::init_concentration() { @@ -91,10 +109,13 @@ void ion_state::zero_current() { void ion_state::reset() { zero_current(); - std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xd_.begin()); if (write_Xi_) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xi_.begin()); if (write_Xo_) std::copy(reset_Xo_.begin(), reset_Xo_.end(), Xo_.begin()); if (write_eX_) std::copy(init_eX_.begin(), init_eX_.end(), eX_.begin()); + // This goes _last_ or at least after Xi since we might have removed reset_Xi + // when Xi is constant. Thus conditionally resetting Xi first and then copying + // Xi -> Xd is safe in all cases. + if (write_Xd_) std::copy(Xi_.begin(), Xi_.end(), Xd_.begin()); } // istim_state methods: diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp index 99bbe37b5..81a10bcc8 100644 --- a/arbor/backends/multicore/shared_state.hpp +++ b/arbor/backends/multicore/shared_state.hpp @@ -50,9 +50,14 @@ struct ARB_ARBOR_API ion_state { unsigned alignment = 1; // Alignment and padding multiple. - bool write_eX_; // is eX written? - bool write_Xo_; // is Xo written? - bool write_Xi_; // is Xi written? + bool write_eX_:1; // is eX written? + bool write_Xo_:1; // is Xo written? + bool write_Xi_:1; // is Xi written? + bool write_Xd_:1; // is Xd written? + bool read_eX_:1; // is eX read? + bool read_Xo_:1; // is Xo read? + bool read_Xi_:1; // is Xi read? + bool read_Xd_:1; // is Xd read? iarray node_index_; // Instance to CV map. array iX_; // (A/m²) current density diff --git a/arbor/backends/shared_state_base.hpp b/arbor/backends/shared_state_base.hpp index c20247d36..92b3a5cb5 100644 --- a/arbor/backends/shared_state_base.hpp +++ b/arbor/backends/shared_state_base.hpp @@ -7,8 +7,9 @@ #include "backends/common_types.hpp" #include "fvm_layout.hpp" -#include "event_lane.hpp" +#include "util/rangeutil.hpp" #include "timestep_range.hpp" +#include "event_lane.hpp" namespace arb { @@ -48,7 +49,7 @@ struct shared_state_base { void configure_solver(const fvm_cv_discretization& disc) { auto d = static_cast(this); - d->solver = {disc.geometry.cv_parent, disc.geometry.cell_cv_divs, disc.cv_capacitance, disc.face_conductance, disc.cv_area}; + d->solver = {disc.geometry.cv_parent, disc.geometry.cell_cv_divs, disc.cv_capacitance, disc.face_conductance}; } void add_ion(const std::string& ion_name, @@ -134,7 +135,7 @@ struct shared_state_base { void integrate_cable_state() { auto d = static_cast(this); - d->solver.solve(d->voltage, d->dt, d->current_density, d->conductivity); + d->solver.solve(d->voltage, d->dt, d->current_density, d->conductivity, d->area_um2); for (auto& [ion, data]: d->ion_data) { if (data.solver) { data.solver->solve(data.Xd_, diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index b20463a2f..e5c809d04 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -717,9 +717,11 @@ fvm_mechanism_data& append(fvm_mechanism_data& left, const fvm_mechanism_data& r append(L.reset_econc, R.reset_econc); append(L.init_revpot, R.init_revpot); append(L.face_diffusivity, R.face_diffusivity); - L.is_diffusive |= R.is_diffusive; + L.is_diffusive |= R.is_diffusive; L.econc_written |= R.econc_written; L.iconc_written |= R.iconc_written; + L.econc_read |= R.econc_read; + L.iconc_read |= R.iconc_read; L.revpot_written |= R.revpot_written; } @@ -822,14 +824,26 @@ struct fvm_ion_build_data { mcable_map init_econc_mask; bool write_xi = false; bool write_xo = false; + bool read_xi = false; + bool read_xo = false; std::vector support; - void add_to_support(const std::vector& cvs) { + auto& add_to_support(const std::vector& cvs) { arb_assert(util::is_sorted(cvs)); support = unique_union(support, cvs); + return *this; + } + + auto& add_ion_dep(const ion_dependency& dep) { + write_xi |= dep.write_concentration_int; + write_xo |= dep.write_concentration_ext; + read_xi |= dep.read_concentration_int; + read_xo |= dep.read_concentration_ext; + return *this; } }; + using fvm_mechanism_config_map = std::map; using fvm_ion_config_map = std::unordered_map; using fvm_ion_map = std::unordered_map; @@ -909,7 +923,7 @@ make_gj_mechanism_config(const std::unordered_map make_revpot_mechanism_config(const std::unordered_map& method, - const std::unordered_map& ions, + std::unordered_map& ions, const cell_build_data& data, fvm_mechanism_config_map&); @@ -1224,10 +1238,9 @@ make_density_mechanism_config(const region_assignment& assignments, apply_parameters_on_cv(config, data, param_maps, support); for (const auto& [ion, dep]: info.ions) { - auto& build_data = ion_build_data[ion]; - build_data.write_xi |= dep.write_concentration_int; - build_data.write_xo |= dep.write_concentration_ext; - build_data.add_to_support(config.cv); + auto& build_data = ion_build_data[ion] + .add_ion_dep(dep) + .add_to_support(config.cv); auto ok = true; if (dep.write_concentration_int) { @@ -1339,6 +1352,8 @@ make_ion_config(fvm_ion_map build_data, config.econc_written = build_data.write_xo; config.iconc_written = build_data.write_xi; + config.econc_read = build_data.read_xo; + config.iconc_read = build_data.read_xi; if (!config.cv.empty()) result[ion] = std::move(config); } } @@ -1524,10 +1539,9 @@ make_point_mechanism_config(const std::unordered_map make_revpot_mechanism_config(const std::unordered_map& method, - const std::unordered_map& ions, + std::unordered_map& ions, const cell_build_data& data, fvm_mechanism_config_map& result) { std::unordered_map revpot_tbl; @@ -1663,10 +1676,13 @@ make_revpot_mechanism_config(const std::unordered_map(n_cv, val)); } - if (!config.cv.empty()) result[name] = std::move(config); } + ion_conf.econc_read |= dep.read_concentration_ext; + ion_conf.iconc_read |= dep.read_concentration_int; } } diff --git a/arbor/fvm_layout.hpp b/arbor/fvm_layout.hpp index 6c2684ca7..73c5c03b9 100644 --- a/arbor/fvm_layout.hpp +++ b/arbor/fvm_layout.hpp @@ -242,6 +242,8 @@ struct fvm_ion_config { bool revpot_written = false; bool iconc_written = false; bool econc_written = false; + bool iconc_read = false; + bool econc_read = false; // Ordered CV indices where ion must be present. std::vector cv; diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index d69503566..adcbab1a9 100644 --- a/arbor/fvm_lowered_cell_impl.hpp +++ b/arbor/fvm_lowered_cell_impl.hpp @@ -289,14 +289,12 @@ void fvm_lowered_cell_impl::update_ion_state() { template void fvm_lowered_cell_impl::assert_voltage_bounded(arb_value_type bound) { - auto v_minmax = state_->voltage_bounds(); - if (v_minmax.first>=-bound && v_minmax.second<=bound) { - return; - } + const auto& [vmin, vmax] = state_->voltage_bounds(); + if (vmin >= -bound && vmax <= bound) return; throw range_check_failure( util::pprintf("voltage solution out of bounds for at t = {}", state_->time), - v_minmax.first<-bound? v_minmax.first: v_minmax.second); + vmin < -bound ? vmin : vmax); } inline @@ -1024,31 +1022,35 @@ void resolve_probe(const cable_probe_ion_current_cell& p, probe_resolution_data< template void resolve_probe(const cable_probe_ion_int_concentration& p, probe_resolution_data& R) { + const auto& ion = p.ion; + const auto& xi = R.state->ion_data.at(ion).Xi_; + if (xi.empty()) return; for (mlocation loc: thingify(p.locations, R.cell.provider())) { - auto opt_i = R.ion_location_index(p.ion, loc); + auto opt_i = R.ion_location_index(ion, loc); if (!opt_i) continue; - - R.result.push_back(fvm_probe_scalar{{R.state->ion_data.at(p.ion).Xi_.data()+*opt_i}, loc}); + R.result.push_back(fvm_probe_scalar{{xi.data() + *opt_i}, loc}); } } template void resolve_probe(const cable_probe_ion_ext_concentration& p, probe_resolution_data& R) { + const auto& ion = p.ion; + const auto& xo = R.state->ion_data.at(ion).Xo_; for (mlocation loc: thingify(p.locations, R.cell.provider())) { - auto opt_i = R.ion_location_index(p.ion, loc); + auto opt_i = R.ion_location_index(ion, loc); if (!opt_i) continue; - - R.result.push_back(fvm_probe_scalar{{R.state->ion_data.at(p.ion).Xo_.data()+*opt_i}, loc}); + R.result.push_back(fvm_probe_scalar{{xo.data() + *opt_i}, loc}); } } template void resolve_probe(const cable_probe_ion_diff_concentration& p, probe_resolution_data& R) { + const auto& ion = p.ion; + const auto& xd = R.state->ion_data.at(ion).Xd_; for (mlocation loc: thingify(p.locations, R.cell.provider())) { - auto opt_i = R.ion_location_index(p.ion, loc); + auto opt_i = R.ion_location_index(ion, loc); if (!opt_i) continue; - - R.result.push_back(fvm_probe_scalar{{R.state->ion_data.at(p.ion).Xd_.data()+*opt_i}, loc}); + R.result.push_back(fvm_probe_scalar{{xd.data() + *opt_i}, loc}); } } @@ -1057,7 +1059,6 @@ template void resolve_ion_conc_common(const std::vector& ion_cvs, const arb_value_type* src, probe_resolution_data& R) { fvm_probe_multi r; mcable_list cables; - for (auto i: util::count_along(ion_cvs)) { for (auto cable: R.D.geometry.cables(ion_cvs[i])) { if (cable.prox_pos!=cable.dist_pos) { @@ -1074,18 +1075,21 @@ void resolve_ion_conc_common(const std::vector& ion_cvs, const a template void resolve_probe(const cable_probe_ion_int_concentration_cell& p, probe_resolution_data& R) { if (!R.state->ion_data.count(p.ion)) return; + if (R.state->ion_data.at(p.ion).Xi_.empty()) return; resolve_ion_conc_common(R.M.ions.at(p.ion).cv, R.state->ion_data.at(p.ion).Xi_.data(), R); } template void resolve_probe(const cable_probe_ion_ext_concentration_cell& p, probe_resolution_data& R) { if (!R.state->ion_data.count(p.ion)) return; + if (R.state->ion_data.at(p.ion).Xo_.empty()) return; resolve_ion_conc_common(R.M.ions.at(p.ion).cv, R.state->ion_data.at(p.ion).Xo_.data(), R); } template void resolve_probe(const cable_probe_ion_diff_concentration_cell& p, probe_resolution_data& R) { if (!R.state->ion_data.count(p.ion)) return; + if (R.state->ion_data.at(p.ion).Xd_.empty()) return; resolve_ion_conc_common(R.M.ions.at(p.ion).cv, R.state->ion_data.at(p.ion).Xd_.data(), R); } diff --git a/arbor/include/arbor/mechanism_abi.h b/arbor/include/arbor/mechanism_abi.h index bf0a9b30b..b5bca6f24 100644 --- a/arbor/include/arbor/mechanism_abi.h +++ b/arbor/include/arbor/mechanism_abi.h @@ -20,7 +20,7 @@ extern "C" { // Version #define ARB_MECH_ABI_VERSION_MAJOR 0 -#define ARB_MECH_ABI_VERSION_MINOR 6 +#define ARB_MECH_ABI_VERSION_MINOR 7 #define ARB_MECH_ABI_VERSION_PATCH 0 #define ARB_MECH_ABI_VERSION ((ARB_MECH_ABI_VERSION_MAJOR * 10000L * 10000L) + (ARB_MECH_ABI_VERSION_MAJOR * 10000L) + ARB_MECH_ABI_VERSION_PATCH) @@ -193,6 +193,8 @@ typedef struct arb_ion_info { const char* name; bool write_int_concentration; bool write_ext_concentration; + bool read_int_concentration; + bool read_ext_concentration; bool use_diff_concentration; bool write_rev_potential; bool read_rev_potential; diff --git a/arbor/include/arbor/mechinfo.hpp b/arbor/include/arbor/mechinfo.hpp index 6d717a65f..a4527ed99 100644 --- a/arbor/include/arbor/mechinfo.hpp +++ b/arbor/include/arbor/mechinfo.hpp @@ -7,8 +7,6 @@ #include #include #include -#include -#include #include #include @@ -35,6 +33,8 @@ struct mechanism_field_spec { struct ion_dependency { bool write_concentration_int = false; bool write_concentration_ext = false; + bool read_concentration_int = false; + bool read_concentration_ext = false; bool access_concentration_diff = false; diff --git a/arbor/mechinfo.cpp b/arbor/mechinfo.cpp index 1d5a15205..b96cca7bd 100644 --- a/arbor/mechinfo.cpp +++ b/arbor/mechinfo.cpp @@ -22,14 +22,16 @@ mechanism_info::mechanism_info(const arb_mechanism_type& m) { } for (auto idx: util::make_span(m.n_ions)) { const auto& v = m.ions[idx]; - ions[v.name] = { v.write_int_concentration, - v.write_ext_concentration, - v.use_diff_concentration, - v.read_rev_potential, - v.write_rev_potential, - v.read_valence, - v.verify_valence, - v.expected_valence }; + ions[v.name] = {v.write_int_concentration, + v.write_ext_concentration, + v.read_int_concentration, + v.read_ext_concentration, + v.use_diff_concentration, + v.read_rev_potential, + v.write_rev_potential, + v.read_valence, + v.verify_valence, + v.expected_valence }; } for (auto idx: util::make_span(m.n_random_variables)) { const auto& rv = m.random_variables[idx]; diff --git a/arbor/morph/embed_pwlin.cpp b/arbor/morph/embed_pwlin.cpp index 945dffdf6..02a4da607 100644 --- a/arbor/morph/embed_pwlin.cpp +++ b/arbor/morph/embed_pwlin.cpp @@ -103,7 +103,7 @@ struct embed_pwlin_data { template double interpolate(double pos, const pw_ratpoly& f) { - auto [extent, poly] = f(pos); + const auto& [extent, poly] = f(pos); auto [left, right] = extent; return left==right? poly[0]: poly((pos-left)/(right-left)); diff --git a/modcc/blocks.hpp b/modcc/blocks.hpp index 1e12b5428..448e2f2d4 100644 --- a/modcc/blocks.hpp +++ b/modcc/blocks.hpp @@ -4,10 +4,8 @@ #include #include #include -#include #include "identifier.hpp" -#include "location.hpp" #include "token.hpp" #include @@ -48,6 +46,15 @@ struct IonDep { bool writes_concentration_ext() const { return writes_variable(name + "o"); }; + bool reads_current() const { + return reads_variable("i" + name); + }; + bool reads_concentration_int() const { + return reads_variable(name + "i"); + }; + bool reads_concentration_ext() const { + return reads_variable(name + "o"); + }; bool writes_rev_potential() const { return writes_variable("e" + name); }; diff --git a/modcc/printer/infoprinter.cpp b/modcc/printer/infoprinter.cpp index f59f52f54..f201fd5fb 100644 --- a/modcc/printer/infoprinter.cpp +++ b/modcc/printer/infoprinter.cpp @@ -49,12 +49,13 @@ ARB_LIBMODCC_API std::string build_info_header(const Module& m, const printer_op id.unit_string(), val, lo, hi); }; auto fmt_ion = [&](const auto& ion) { - return fmt::format(FMT_COMPILE("{{ \"{}\", {}, {}, {}, {}, {}, {}, {}, {} }}"), - ion.name, - ion.writes_concentration_int(), ion.writes_concentration_ext(), - ion.uses_concentration_diff(), - ion.writes_rev_potential(), ion.uses_rev_potential(), - ion.uses_valence(), ion.verifies_valence(), ion.expected_valence); + return fmt::format(FMT_COMPILE("{{ \"{}\", {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }}"), + ion.name, + ion.writes_concentration_int(), ion.writes_concentration_ext(), + ion.reads_concentration_int(), ion.reads_concentration_ext(), + ion.uses_concentration_diff(), + ion.writes_rev_potential(), ion.uses_rev_potential(), + ion.uses_valence(), ion.verifies_valence(), ion.expected_valence); }; auto fmt_random_variable = [&](const auto& rv_id) { return fmt::format(FMT_COMPILE("{{ \"{}\", {} }}"), rv_id.first.name(), rv_id.second); diff --git a/python/test/unit/test_probes.py b/python/test/unit/test_probes.py index c35a53154..d38d4ce3d 100644 --- a/python/test/unit/test_probes.py +++ b/python/test/unit/test_probes.py @@ -19,12 +19,15 @@ def __init__(self): st = A.segment_tree() st.append(A.mnpos, (0, 0, 0, 10), (1, 0, 0, 10), 1) - dec = A.decor() - - dec.place("(location 0 0.08)", A.synapse("expsyn"), "syn0") - dec.place("(location 0 0.09)", A.synapse("exp2syn"), "syn1") - dec.place("(location 0 0.1)", A.iclamp(20.0 * U.nA), "iclamp") - dec.paint("(all)", A.density("hh")) + dec = ( + A.decor() + # This ensures we _read_ nai/nao and can probe them, too + .set_ion(ion="na", method="nernst/x=na") + .place("(location 0 0.08)", A.synapse("expsyn"), "syn0") + .place("(location 0 0.09)", A.synapse("exp2syn"), "syn1") + .place("(location 0 0.1)", A.iclamp(20.0 * U.nA), "iclamp") + .paint("(all)", A.density("hh")) + ) self.cell = A.cable_cell(st, dec) @@ -40,7 +43,7 @@ def cell_kind(self, gid): def global_properties(self, kind): return self.props - def probes(self, _): + def probes(self, gid): # Use keyword arguments to check that the wrappers have actually declared keyword arguments correctly. # Place single-location probes at (location 0 0.01*j) where j is the index of the probe address in # the returned list. diff --git a/test/unit/test_math.cpp b/test/unit/test_math.cpp index 755e325c6..22e788b5b 100644 --- a/test/unit/test_math.cpp +++ b/test/unit/test_math.cpp @@ -97,15 +97,13 @@ TEST(math, infinity) { EXPECT_GT(ldinf, 0.0l); // check default value promotes correctly (i.e., acts like INFINITY) - struct { - float f; - double d; - long double ld; - } check = {infinity<>, infinity<>, infinity<>}; - - EXPECT_EQ(std::numeric_limits::infinity(), check.f); - EXPECT_EQ(std::numeric_limits::infinity(), check.d); - EXPECT_EQ(std::numeric_limits::infinity(), check.ld); + float f = infinity<>; + double d = infinity<>; + long double ld = infinity<>; + + EXPECT_EQ(std::numeric_limits::infinity(), f); + EXPECT_EQ(std::numeric_limits::infinity(), d); + EXPECT_EQ(std::numeric_limits::infinity(), ld); } TEST(math, signum) { diff --git a/test/unit/test_matrix.cpp b/test/unit/test_matrix.cpp index 4a7a3b320..c6a42f1c1 100644 --- a/test/unit/test_matrix.cpp +++ b/test/unit/test_matrix.cpp @@ -21,10 +21,9 @@ using value_type = arb_value_type; using vvec = std::vector; -TEST(matrix, construct_from_parent_only) -{ +TEST(matrix, construct_from_parent_only) { std::vector p = {0,0,1}; - solver_type m(p, {0, 3}, vvec(3), vvec(3), vvec(3)); + solver_type m(p, {0, 3}, vvec(3), vvec(3)); EXPECT_EQ(m.num_cells(), 1u); EXPECT_EQ(m.size(), 3u); EXPECT_EQ(p.size(), 3u); @@ -35,14 +34,13 @@ TEST(matrix, construct_from_parent_only) EXPECT_EQ(mp[2], index_type(1)); } -TEST(matrix, solve_host) -{ +TEST(matrix, solve_host) { using util::make_span; using util::fill; // trivial case : 1x1 matrix { - solver_type m({0}, {0,1}, vvec(1), vvec(1), vvec(1)); + solver_type m({0}, {0,1}, vvec(1), vvec(1)); fill(m.d, 2); fill(m.u, -1); array x({1}); @@ -56,7 +54,7 @@ TEST(matrix, solve_host) for(auto n : make_span(2, 1001)) { auto p = std::vector(n); std::iota(p.begin()+1, p.end(), 0); - solver_type m(p, {0, n}, vvec(n), vvec(n), vvec(n)); + solver_type m(p, {0, n}, vvec(n), vvec(n)); EXPECT_EQ(m.size(), (unsigned)n); EXPECT_EQ(m.num_cells(), 1u); @@ -78,8 +76,7 @@ TEST(matrix, solve_host) } } -TEST(matrix, solve_multi_matrix) -{ +TEST(matrix, solve_multi_matrix) { // Use assemble method to construct same zero-diagonal // test case from CV data. @@ -107,7 +104,7 @@ TEST(matrix, solve_multi_matrix) // Initial voltage of zero; currents alone determine rhs. auto v = vvec{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - vvec area(7, 1.0); + array area(7, 1.0); // (Scaled) membrane conductances contribute to diagonal. array mg = { 1000, 2000, 3000, 4000, 5000, 6000, 7000 }; @@ -121,9 +118,9 @@ TEST(matrix, solve_multi_matrix) // Expected solution: // x = [ 4 5 6 7 8 9 10 ] - solver_type m(p, c, Cm, g, area); + solver_type m(p, c, Cm, g); std::vector expected = {4, 5, 6, 7, 8, 9, 10}; - m.solve(v, dt, i, mg); + m.solve(v, dt, i, mg, area); EXPECT_TRUE(testing::seq_almost_eq(expected, v)); } diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index f9dc34ccb..67de60f5f 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -475,6 +475,18 @@ void run_expsyn_g_cell_probe_test(context ctx) { } } +template +typename B::array mk_array(size_t n, size_t a) { + return typename B::array(n, 0, util::padded_allocator<>(a)); +} + +#ifdef ARB_GPU_ENABLED +template<> +typename arb::gpu::backend::array mk_array(size_t n, size_t a) { + return arb::gpu::backend::array(n, 0); +} +#endif + template void run_ion_density_probe_test(context ctx) { using fvm_cell = typename backend_access::fvm_cell; @@ -528,15 +540,39 @@ void run_ion_density_probe_test(context ctx) { fvm_cell lcell(*ctx); + using array = typename Backend::array; + auto fvm_info = lcell.initialize({0}, rec); // We skipped FVM layout here, so we need to set these manually auto& state = backend_access::state(lcell); - state.ion_data["ca"].write_Xi_ = true; - state.ion_data["ca"].write_Xo_ = true; - state.ion_data["ca"].init_concentration(); - state.ion_data["na"].write_Xi_ = true; - state.ion_data["na"].write_Xo_ = true; - state.ion_data["na"].init_concentration(); + auto align = state.alignment; + + auto& ca = state.ion_data["ca"]; + auto nca = ca.node_index_.size(); + auto cai = mk_array(nca, align); + ca.write_Xi_ = true; + ca.Xi_ = cai; + ca.init_Xi_ = cai; + ca.reset_Xi_ = cai; + auto cao = mk_array(nca, align); + ca.write_Xo_ = true; + ca.Xo_ = cao; + ca.init_Xo_ = cao; + ca.reset_Xo_ = cao; + + auto& na = state.ion_data["na"]; + auto nna = na.node_index_.size(); + auto nai = mk_array(nna, align); + na.write_Xi_ = true; + na.Xi_ = nai; + na.init_Xi_ = nai; + na.reset_Xi_ = nai; + auto nao = mk_array(nna, align); + na.write_Xo_ = true; + na.Xo_ = nao; + na.init_Xo_ = nao; + na.reset_Xo_ = nao; + // Now, re-init cell lcell.reset(); diff --git a/test/unit/test_sde.cpp b/test/unit/test_sde.cpp index 30637dfeb..e0f097db9 100644 --- a/test/unit/test_sde.cpp +++ b/test/unit/test_sde.cpp @@ -729,8 +729,7 @@ TEST(sde, solver) { // context auto context = make_context({arbenv::default_concurrency(), -1}); - for (unsigned s=0; s Date: Tue, 22 Oct 2024 10:21:02 +0200 Subject: [PATCH 15/30] event handling: fix a bug and clean house (#2421) - fix event stream when more than one cell in a cell group have same synapse - events would previously no longer necessarily be sorted by time - in order to simplify: also sort with respect to mechanism index (as was previously only required for the gpu backend) - add pertinent test - while cleaning up: overhauled the event related files and data structures - removed dead code - made event handling less generic (this feature was not used anywhere) --- arbor/backends/event.hpp | 9 - arbor/backends/event_stream_base.hpp | 99 ++++++-- arbor/backends/gpu/event_stream.hpp | 126 ++--------- arbor/backends/gpu/fvm.hpp | 2 - arbor/backends/gpu/gpu_store_types.hpp | 3 - arbor/backends/gpu/shared_state.cpp | 12 +- arbor/backends/gpu/shared_state.hpp | 7 +- arbor/backends/multicore/event_stream.hpp | 59 +---- arbor/backends/multicore/fvm.hpp | 2 - arbor/backends/multicore/multicore_common.hpp | 3 - arbor/backends/multicore/shared_state.cpp | 10 +- arbor/backends/multicore/shared_state.hpp | 7 +- arbor/backends/shared_state_base.hpp | 4 +- arbor/include/arbor/event_generator.hpp | 1 - arbor/include/arbor/generic_event.hpp | 91 -------- arbor/simulation.cpp | 11 +- test/ubench/event_binning.cpp | 1 - {arbor => test/ubench}/event_queue.hpp | 21 +- test/ubench/event_setup.cpp | 3 +- test/ubench/fvm_discretize.cpp | 1 - test/unit/CMakeLists.txt | 1 - test/unit/test_event_queue.cpp | 160 ------------- test/unit/test_event_stream.cpp | 115 ++-------- test/unit/test_event_stream.hpp | 212 ++++++++++++++++++ test/unit/test_event_stream_gpu.cpp | 135 ++--------- 25 files changed, 388 insertions(+), 707 deletions(-) delete mode 100644 arbor/include/arbor/generic_event.hpp rename {arbor => test/ubench}/event_queue.hpp (86%) delete mode 100644 test/unit/test_event_queue.cpp create mode 100644 test/unit/test_event_stream.hpp diff --git a/arbor/backends/event.hpp b/arbor/backends/event.hpp index 10249aa34..7fd9eb849 100644 --- a/arbor/backends/event.hpp +++ b/arbor/backends/event.hpp @@ -4,7 +4,6 @@ #include #include #include -#include // Structures for the representation of event delivery targets and // staged events. @@ -46,9 +45,6 @@ struct deliverable_event { ARB_SERDES_ENABLE(deliverable_event, time, weight, handle); }; -template<> -struct has_event_index : public std::true_type {}; - // Subset of event information required for mechanism delivery. struct deliverable_event_data { cell_local_size_type mech_index; // same as target_handle::mech_index @@ -61,11 +57,6 @@ struct deliverable_event_data { weight); }; -// Stream index accessor function for multi_event_stream: -inline cell_local_size_type event_index(const arb_deliverable_event_data& ed) { - return ed.mech_index; -} - // Delivery data accessor function for multi_event_stream: inline arb_deliverable_event_data event_data(const deliverable_event& ev) { return {ev.handle.mech_index, ev.weight}; diff --git a/arbor/backends/event_stream_base.hpp b/arbor/backends/event_stream_base.hpp index f825b503f..a81254a78 100644 --- a/arbor/backends/event_stream_base.hpp +++ b/arbor/backends/event_stream_base.hpp @@ -2,10 +2,8 @@ #include -#include #include - #include "backends/event.hpp" #include "backends/event_stream_state.hpp" #include "event_lane.hpp" @@ -18,15 +16,13 @@ namespace arb { template struct event_stream_base { - using size_type = std::size_t; using event_type = Event; - using event_time_type = ::arb::event_time_type; - using event_data_type = ::arb::event_data_type; + using event_data_type = decltype(event_data(std::declval())); protected: // members std::vector ev_data_; std::vector ev_spans_ = {0}; - size_type index_ = 0; + std::size_t index_ = 0; event_data_type* base_ptr_ = nullptr; public: @@ -62,24 +58,32 @@ struct event_stream_base { index_ = 0; } - // Construct a mapping of mech_id to a stream s.t. streams are partitioned into - // time step buckets by `ev_span` + protected: + // backend specific initializations + virtual void init() = 0; +}; + +struct spike_event_stream_base : event_stream_base { template - static std::enable_if_t> - multi_event_stream(const event_lane_subrange& lanes, - const std::vector& handles, - const std::vector& divs, - const timestep_range& steps, - std::unordered_map& streams) { + friend void initialize(const event_lane_subrange& lanes, + const std::vector& handles, + const std::vector& divs, + const timestep_range& steps, + std::unordered_map& streams) { arb_assert(lanes.size() < divs.size()); + // reset streams and allocate sufficient space for temporaries auto n_steps = steps.size(); - std::unordered_map> dt_sizes; for (auto& [k, v]: streams) { v.clear(); - dt_sizes[k].resize(n_steps, 0); + v.spike_counter_.clear(); + v.spike_counter_.resize(steps.size(), 0); + v.spikes_.clear(); + // ev_data_ has been cleared during v.clear(), so we use its capacity + v.spikes_.reserve(v.ev_data_.capacity()); } + // loop over lanes: group events by mechanism and sort them by time auto cell = 0; for (const auto& lane: lanes) { auto div = divs[cell]; @@ -94,16 +98,71 @@ struct event_stream_base { if (step >= n_steps) break; arb_assert(div + target < handles.size()); const auto& handle = handles[div + target]; - streams[handle.mech_id].ev_data_.push_back({handle.mech_index, weight}); - dt_sizes[handle.mech_id][step]++; + auto& stream = streams[handle.mech_id]; + stream.spikes_.push_back(spike_data{step, handle.mech_index, time, weight}); + // insertion sort with last element as pivot + // ordering: first w.r.t. step, within a step: mech_index, within a mech_index: time + auto first = stream.spikes_.begin(); + auto last = stream.spikes_.end(); + auto pivot = std::prev(last, 1); + std::rotate(std::upper_bound(first, pivot, *pivot), pivot, last); + // increment count in current time interval + stream.spike_counter_[step]++; } } for (auto& [id, stream]: streams) { - util::make_partition(stream.ev_spans_, dt_sizes[id]); - stream.init(); + // copy temporary deliverable_events into stream's ev_data_ + stream.ev_data_.reserve(stream.spikes_.size()); + std::transform(stream.spikes_.begin(), stream.spikes_.end(), std::back_inserter(stream.ev_data_), + [](auto const& e) noexcept -> arb_deliverable_event_data { + return {e.mech_index, e.weight}; }); + // scan over spike_counter_ and written to ev_spans_ + util::make_partition(stream.ev_spans_, stream.spike_counter_); + // delegate to derived class init: static cast necessary to access protected init() + static_cast(stream).init(); } } + + protected: // members + struct spike_data { + arb_size_type step = 0; + cell_local_size_type mech_index = 0; + time_type time = 0; + float weight = 0; + auto operator<=>(spike_data const&) const noexcept = default; + }; + std::vector spikes_; + std::vector spike_counter_; +}; + +struct sample_event_stream_base : event_stream_base { + friend void initialize(const std::vector>& staged, + sample_event_stream_base& stream) { + // clear previous data + stream.clear(); + + // return if there are no timestep bins + if (!staged.size()) return; + + // return if there are no events + auto num_events = util::sum_by(staged, [] (const auto& v) {return v.size();}); + if (!num_events) return; + + // allocate space for spans and data + stream.ev_spans_.reserve(staged.size() + 1); + stream.ev_data_.reserve(num_events); + + // add event data and spans + for (const auto& v : staged) { + for (const auto& ev: v) stream.ev_data_.push_back(ev.raw); + stream.ev_spans_.push_back(stream.ev_data_.size()); + } + + arb_assert(num_events == stream.ev_data_.size()); + arb_assert(staged.size() + 1 == stream.ev_spans_.size()); + stream.init(); + } }; } // namespace arb diff --git a/arbor/backends/gpu/event_stream.hpp b/arbor/backends/gpu/event_stream.hpp index 0045d9381..e332bedbc 100644 --- a/arbor/backends/gpu/event_stream.hpp +++ b/arbor/backends/gpu/event_stream.hpp @@ -2,113 +2,33 @@ // Indexed collection of pop-only event queues --- CUDA back-end implementation. -#include - #include "backends/event_stream_base.hpp" -#include "util/transform.hpp" -#include "threading/threading.hpp" -#include "timestep_range.hpp" #include "memory/memory.hpp" namespace arb { namespace gpu { -template -struct event_stream: public event_stream_base { -public: - using base = event_stream_base; - using size_type = typename base::size_type; - using event_data_type = typename base::event_data_type; - using device_array = memory::device_vector; - - using base::clear; - using base::ev_data_; - using base::ev_spans_; - using base::base_ptr_; - - event_stream() = default; - event_stream(task_system_handle t): base(), thread_pool_{t} {} - - // Initialize event streams from a vector of vector of events - // Outer vector represents time step bins - void init(const std::vector>& staged) { - // clear previous data - clear(); - - // return if there are no timestep bins - if (!staged.size()) return; - - // return if there are no events - const size_type num_events = util::sum_by(staged, [] (const auto& v) {return v.size();}); - if (!num_events) return; - - // allocate space for spans and data - ev_spans_.resize(staged.size() + 1); - ev_data_.resize(num_events); - resize(device_ev_data_, num_events); - - // compute offsets by exclusive scan over staged events - util::make_partition(ev_spans_, - util::transform_view(staged, [](const auto& v) { return v.size(); }), - 0ull); - - // assign, copy to device (and potentially sort) the event data in parallel - arb_assert(thread_pool_); - arb_assert(ev_spans_.size() == staged.size() + 1); - threading::parallel_for::apply(0, ev_spans_.size() - 1, thread_pool_.get(), - [this, &staged](size_type i) { - const auto beg = ev_spans_[i]; - const auto end = ev_spans_[i + 1]; - arb_assert(end >= beg); - const auto len = end - beg; - - auto host_span = memory::make_view(ev_data_)(beg, end); - - // make event data and copy - std::copy_n(util::transform_view(staged[i], - [](const auto& x) { return event_data(x); }).begin(), - len, - host_span.begin()); - // sort if necessary - if constexpr (has_event_index::value) { - util::stable_sort_by(host_span, - [](const event_data_type& ed) { return event_index(ed); }); - } - // copy to device - auto device_span = memory::make_view(device_ev_data_)(beg, end); - memory::copy_async(host_span, device_span); - }); - - base_ptr_ = device_ev_data_.data(); +template +struct event_stream : BaseEventStream { + public: + ARB_SERDES_ENABLE(event_stream, + ev_data_, + ev_spans_, + device_ev_data_, + index_); - arb_assert(num_events == device_ev_data_.size()); - arb_assert(num_events == ev_data_.size()); + protected: + void init() override final { + resize(this->device_ev_data_, this->device_ev_data_.size()); + memory::copy_async(this->ev_data_, this->device_ev_data_); + this->base_ptr_ = this->device_ev_data_.data(); } - // Initialize event stream assuming ev_data_ and ev_span_ has - // been set previously (e.g. by `base::multi_event_stream`) - void init() { - resize(device_ev_data_, ev_data_.size()); - base_ptr_ = device_ev_data_.data(); - - threading::parallel_for::apply(0, ev_spans_.size() - 1, thread_pool_.get(), - [this](size_type i) { - const auto beg = ev_spans_[i]; - const auto end = ev_spans_[i + 1]; - arb_assert(end >= beg); - - auto host_span = memory::make_view(ev_data_)(beg, end); - auto device_span = memory::make_view(device_ev_data_)(beg, end); + private: // device memory + using event_data_type = typename BaseEventStream::event_data_type; + using device_array = memory::device_vector; - // sort if necessary - if constexpr (has_event_index::value) { - util::stable_sort_by(host_span, - [](const event_data_type& ed) { return event_index(ed); }); - } - // copy to device - memory::copy_async(host_span, device_span); - }); - } + device_array device_ev_data_; template static void resize(D& d, std::size_t size) { @@ -117,16 +37,10 @@ struct event_stream: public event_stream_base { d = D(size); } } - - ARB_SERDES_ENABLE(event_stream, - ev_data_, - ev_spans_, - device_ev_data_, - index_); - - task_system_handle thread_pool_; - device_array device_ev_data_; }; +using spike_event_stream = event_stream; +using sample_event_stream = event_stream; + } // namespace gpu } // namespace arb diff --git a/arbor/backends/gpu/fvm.hpp b/arbor/backends/gpu/fvm.hpp index 37023c4f4..96de41dab 100644 --- a/arbor/backends/gpu/fvm.hpp +++ b/arbor/backends/gpu/fvm.hpp @@ -37,8 +37,6 @@ struct backend { using threshold_watcher = arb::gpu::threshold_watcher; using cable_solver = arb::gpu::matrix_state_fine; using diffusion_solver = arb::gpu::diffusion_state; - using deliverable_event_stream = arb::gpu::deliverable_event_stream; - using sample_event_stream = arb::gpu::sample_event_stream; using shared_state = arb::gpu::shared_state; using ion_state = arb::gpu::ion_state; diff --git a/arbor/backends/gpu/gpu_store_types.hpp b/arbor/backends/gpu/gpu_store_types.hpp index 386f8bac7..b8ce5369d 100644 --- a/arbor/backends/gpu/gpu_store_types.hpp +++ b/arbor/backends/gpu/gpu_store_types.hpp @@ -18,9 +18,6 @@ using array = memory::device_vector; using iarray = memory::device_vector; using sarray = memory::device_vector; -using deliverable_event_stream = arb::gpu::event_stream; -using sample_event_stream = arb::gpu::event_stream; - } // namespace gpu } // namespace arb diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp index c6213b54a..091bcde51 100644 --- a/arbor/backends/gpu/shared_state.cpp +++ b/arbor/backends/gpu/shared_state.cpp @@ -214,7 +214,7 @@ shared_state::shared_state(task_system_handle tp, time_since_spike(n_cell*n_detector), src_to_spike(make_const_view(src_to_spike_)), cbprng_seed(cbprng_seed_), - sample_events(thread_pool), + sample_events(), watcher{n_cv_, src_to_spike.data(), detector_info} { memory::fill(time_since_spike, -1.0); @@ -262,7 +262,7 @@ void shared_state::instantiate(mechanism& m, if (storage.count(id)) throw arb::arbor_internal_error("Duplicate mech id in shared state"); auto& store = storage.emplace(id, mech_storage{}).first->second; - streams[id] = deliverable_event_stream{thread_pool}; + streams[id] = spike_event_stream{}; // Allocate view pointers store.state_vars_ = std::vector(m.mech_.n_state_vars); @@ -410,14 +410,6 @@ void shared_state::take_samples() { } } -void shared_state::init_events(const event_lane_subrange& lanes, - const std::vector& handles, - const std::vector& divs, - const timestep_range& dts) { - arb::gpu::event_stream::multi_event_stream(lanes, handles, divs, dts, streams); -} - - // Debug interface ARB_ARBOR_API std::ostream& operator<<(std::ostream& o, shared_state& s) { using io::csv; diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp index 792fc7476..1ca224ae9 100644 --- a/arbor/backends/gpu/shared_state.hpp +++ b/arbor/backends/gpu/shared_state.hpp @@ -174,7 +174,7 @@ struct ARB_ARBOR_API shared_state: shared_state_base ion_data; std::unordered_map storage; - std::unordered_map streams; + std::unordered_map streams; shared_state() = default; @@ -245,11 +245,6 @@ struct ARB_ARBOR_API shared_state: shared_state_base& handles, - const std::vector& divs, - const timestep_range& dts); }; // For debugging only diff --git a/arbor/backends/multicore/event_stream.hpp b/arbor/backends/multicore/event_stream.hpp index f280777db..a72d4a518 100644 --- a/arbor/backends/multicore/event_stream.hpp +++ b/arbor/backends/multicore/event_stream.hpp @@ -2,61 +2,26 @@ // Indexed collection of pop-only event queues --- multicore back-end implementation. -#include "arbor/spike_event.hpp" #include "backends/event_stream_base.hpp" -#include "timestep_range.hpp" namespace arb { namespace multicore { -template -struct event_stream: public event_stream_base { - using base = event_stream_base; - using size_type = typename base::size_type; - - using base::clear; - using base::ev_spans_; - using base::ev_data_; - using base::base_ptr_; - - event_stream() = default; - - // Initialize event stream from a vector of vector of events - // Outer vector represents time step bins - void init(const std::vector>& staged) { - // clear previous data - clear(); - - // return if there are no timestep bins - if (!staged.size()) return; - - // return if there are no events - const size_type num_events = util::sum_by(staged, [] (const auto& v) {return v.size();}); - if (!num_events) return; - - // allocate space for spans and data - ev_spans_.reserve(staged.size() + 1); - ev_data_.reserve(num_events); - - // add event data and spans - for (const auto& v : staged) { - for (const auto& ev: v) ev_data_.push_back(event_data(ev)); - ev_spans_.push_back(ev_data_.size()); - } - - arb_assert(num_events == ev_data_.size()); - arb_assert(staged.size() + 1 == ev_spans_.size()); - base_ptr_ = ev_data_.data(); - } - - // Initialize event stream assuming ev_data_ and ev_span_ has - // been set previously (e.g. by `base::multi_event_stream`) - void init() { base_ptr_ = ev_data_.data(); } - - ARB_SERDES_ENABLE(event_stream, +template +struct event_stream : BaseEventStream { + public: + ARB_SERDES_ENABLE(event_stream, ev_data_, ev_spans_, index_); + protected: + void init() override final { + this->base_ptr_ = this->ev_data_.data(); + } }; + +using spike_event_stream = event_stream; +using sample_event_stream = event_stream; + } // namespace multicore } // namespace arb diff --git a/arbor/backends/multicore/fvm.hpp b/arbor/backends/multicore/fvm.hpp index a845a52bf..89e724211 100644 --- a/arbor/backends/multicore/fvm.hpp +++ b/arbor/backends/multicore/fvm.hpp @@ -38,8 +38,6 @@ struct backend { using cable_solver = arb::multicore::cable_solver; using diffusion_solver = arb::multicore::diffusion_solver; using threshold_watcher = arb::multicore::threshold_watcher; - using deliverable_event_stream = arb::multicore::deliverable_event_stream; - using sample_event_stream = arb::multicore::sample_event_stream; using shared_state = arb::multicore::shared_state; using ion_state = arb::multicore::ion_state; diff --git a/arbor/backends/multicore/multicore_common.hpp b/arbor/backends/multicore/multicore_common.hpp index 9186628eb..fca714c45 100644 --- a/arbor/backends/multicore/multicore_common.hpp +++ b/arbor/backends/multicore/multicore_common.hpp @@ -23,9 +23,6 @@ using padded_vector = std::vector>; using array = padded_vector; using iarray = padded_vector; -using deliverable_event_stream = arb::multicore::event_stream; -using sample_event_stream = arb::multicore::event_stream; - } // namespace multicore } // namespace arb diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp index e625c5b97..b70089c4e 100644 --- a/arbor/backends/multicore/shared_state.cpp +++ b/arbor/backends/multicore/shared_state.cpp @@ -403,7 +403,7 @@ void shared_state::instantiate(arb::mechanism& m, util::padded_allocator<> pad(m.data_alignment()); if (storage.count(id)) throw arbor_internal_error("Duplicate mechanism id in MC shared state."); - streams[id] = deliverable_event_stream{}; + streams[id] = spike_event_stream{}; auto& store = storage[id]; auto width = pos_data.cv.size(); // Assign non-owning views onto shared state: @@ -556,13 +556,5 @@ void shared_state::instantiate(arb::mechanism& m, } } -void shared_state::init_events(const event_lane_subrange& lanes, - const std::vector& handles, - const std::vector& divs, - const timestep_range& dts) { - arb::multicore::event_stream::multi_event_stream(lanes, handles, divs, dts, streams); -} - - } // namespace multicore } // namespace arb diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp index 81a10bcc8..1bb3c59f2 100644 --- a/arbor/backends/multicore/shared_state.hpp +++ b/arbor/backends/multicore/shared_state.hpp @@ -180,7 +180,7 @@ struct ARB_ARBOR_API shared_state: istim_state stim_data; std::unordered_map ion_data; std::unordered_map storage; - std::unordered_map streams; + std::unordered_map streams; shared_state() = default; @@ -250,11 +250,6 @@ struct ARB_ARBOR_API shared_state: sample_time_host = util::range_pointer_view(sample_time); sample_value_host = util::range_pointer_view(sample_value); } - - void init_events(const event_lane_subrange& lanes, - const std::vector& handles, - const std::vector& divs, - const timestep_range& dts); }; // For debugging only: diff --git a/arbor/backends/shared_state_base.hpp b/arbor/backends/shared_state_base.hpp index 92b3a5cb5..0c1742a0d 100644 --- a/arbor/backends/shared_state_base.hpp +++ b/arbor/backends/shared_state_base.hpp @@ -35,14 +35,14 @@ struct shared_state_base { const std::vector& divs) { auto d = static_cast(this); // events - d->init_events(lanes, handles, divs, dts); + initialize(lanes, handles, divs, dts, d->streams); // samples auto n_samples = util::sum_by(samples, [] (const auto& s) {return s.size();}); if (d->sample_time.size() < n_samples) { d->sample_time = array(n_samples); d->sample_value = array(n_samples); } - d->sample_events.init(samples); + initialize(samples, d->sample_events); // thresholds d->watcher.clear_crossings(); } diff --git a/arbor/include/arbor/event_generator.hpp b/arbor/include/arbor/event_generator.hpp index cab7e5fe2..d809fbb2d 100644 --- a/arbor/include/arbor/event_generator.hpp +++ b/arbor/include/arbor/event_generator.hpp @@ -4,7 +4,6 @@ #include #include -#include #include #include #include diff --git a/arbor/include/arbor/generic_event.hpp b/arbor/include/arbor/generic_event.hpp deleted file mode 100644 index 9a139a670..000000000 --- a/arbor/include/arbor/generic_event.hpp +++ /dev/null @@ -1,91 +0,0 @@ -#pragma once - -#include -#include - -// Generic accessors for event types used in `event_queue` and -// `event_stream`. -// -// 1. event_time(const Event&): -// -// Returns ordered type (typically `time_type`) representing -// the event time. Default implementation returns `e.time` -// for an event `e`. -// -// 2. event_index(const Event&): -// -// Returns the stream index associated with the event (an -// unsigned index type), for use with `event_stream`. -// Default implementation returns `e.index` for an event `e`. -// -// 3. event_data(const Event&): -// -// Returns the event _payload_, viz. the event data that -// does not include (necessarily) the time or index. This -// is used with `event_stream`. -// Default implementation returns `e.data` for an event `e`. -// -// The type aliases event_time_type, event_index_type and event_data_type -// give the corresponding return types. -// -// The accessors act as customization points, in that they can be -// specialized for a particular event class. In order for ADL -// to work correctly across namespaces, the accessor functions -// should be brought into scope with a `using` declaration. -// -// Example use: -// -// template -// bool is_before(const Event& a, const Event& b) { -// using ::arb::event_time; -// return event_time(a) -auto event_time(const Event& ev) { - return ev.time; -} - -struct event_time_less { - template ::value>> - bool operator() (T l, const Event& r) { - return l::value>> - bool operator() (const Event& l, T r) { - return event_time(l) - using event_time_type = decltype(event_time(std::declval())); - - template - using event_data_type = decltype(event_data(std::declval())); - - template - using event_index_type = decltype(event_index(std::declval>())); -} - -template -using event_time_type = impl::event_time_type; - -template -using event_index_type = impl::event_index_type; - -template -using event_data_type = impl::event_data_type; - -template -struct has_event_index : public std::false_type {}; - -} // namespace arb - diff --git a/arbor/simulation.cpp b/arbor/simulation.cpp index 998963790..9945e2508 100644 --- a/arbor/simulation.cpp +++ b/arbor/simulation.cpp @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -43,7 +42,11 @@ ARB_ARBOR_API void merge_cell_events(time_type t_from, pse_vector& new_events) { PE(communication:enqueue:setup); new_events.clear(); - old_events = split_sorted_range(old_events, t_from, event_time_less()).second; + constexpr auto event_time_less = [](auto const& l, auto const& r) noexcept { + if constexpr (std::is_floating_point_v>) { return l < r.time; } + else { return l.time < r; } + }; + old_events = split_sorted_range(old_events, t_from, event_time_less).second; PL(); if (!generators.empty()) { @@ -53,8 +56,8 @@ ARB_ARBOR_API void merge_cell_events(time_type t_from, std::vector spanbuf; spanbuf.reserve(2+generators.size()); - auto old_split = split_sorted_range(old_events, t_to, event_time_less()); - auto pending_split = split_sorted_range(pending, t_to, event_time_less()); + auto old_split = split_sorted_range(old_events, t_to, event_time_less); + auto pending_split = split_sorted_range(pending, t_to, event_time_less); spanbuf.push_back(old_split.first); spanbuf.push_back(pending_split.first); diff --git a/test/ubench/event_binning.cpp b/test/ubench/event_binning.cpp index 115328ca8..e56057e34 100644 --- a/test/ubench/event_binning.cpp +++ b/test/ubench/event_binning.cpp @@ -11,7 +11,6 @@ #include -#include "event_queue.hpp" #include "backends/event.hpp" diff --git a/arbor/event_queue.hpp b/test/ubench/event_queue.hpp similarity index 86% rename from arbor/event_queue.hpp rename to test/ubench/event_queue.hpp index 5e314f8d9..8964ee415 100644 --- a/arbor/event_queue.hpp +++ b/test/ubench/event_queue.hpp @@ -10,22 +10,27 @@ #include #include -#include namespace arb { /* Event classes `Event` used with `event_queue` must be move and copy constructible, - * and either have a public field `time` that returns the time value, or provide an - * overload of `event_time(const Event&)` which returns this value (see generic_event.hpp). + * and either have a public field `time` that returns the time value, or provide a + * projection to a time value through the EventTime functor. * * Time values must be well ordered with respect to `operator>`. */ -template +struct default_event_time { + template + auto operator()(Event const& e) const noexcept { return e.time; } +}; + +template class event_queue { public: + static constexpr EventTime event_time = {}; using value_type = Event; - using event_time_type = ::arb::event_time_type; + using event_time_type = decltype(event_time(std::declval())); event_queue() = default; @@ -50,8 +55,6 @@ class event_queue { if (queue_.empty()) { return std::nullopt; } - - using ::arb::event_time; auto t = event_time(queue_.top()); return t_until > t? std::optional(t): std::nullopt; } @@ -60,7 +63,6 @@ class event_queue { // queue non-empty and the head satisfies predicate. template std::optional pop_if(Pred&& pred) { - using ::arb::event_time; if (!queue_.empty() && pred(queue_.top())) { auto ev = queue_.top(); queue_.pop(); @@ -73,7 +75,6 @@ class event_queue { // Pop and return top event `ev` of queue if `t_until` > `event_time(ev)`. std::optional pop_if_before(const event_time_type& t_until) { - using ::arb::event_time; return pop_if( [&t_until](const value_type& ev) { return t_until > event_time(ev); } ); @@ -81,7 +82,6 @@ class event_queue { // Pop and return top event `ev` of queue unless `event_time(ev)` > `t_until` std::optional pop_if_not_after(const event_time_type& t_until) { - using ::arb::event_time; return pop_if( [&t_until](const value_type& ev) { return !(event_time(ev) > t_until); } ); @@ -95,7 +95,6 @@ class event_queue { private: struct event_greater { bool operator()(const Event& a, const Event& b) { - using ::arb::event_time; return event_time(a) > event_time(b); } }; diff --git a/test/ubench/event_setup.cpp b/test/ubench/event_setup.cpp index 1b5ff57ce..035effc04 100644 --- a/test/ubench/event_setup.cpp +++ b/test/ubench/event_setup.cpp @@ -17,9 +17,10 @@ #include -#include "event_queue.hpp" #include "backends/event.hpp" +#include "event_queue.hpp" + using namespace arb; std::vector> generate_inputs(size_t ncells, size_t ev_per_cell) { diff --git a/test/ubench/fvm_discretize.cpp b/test/ubench/fvm_discretize.cpp index e78558ac2..2531a38d8 100644 --- a/test/ubench/fvm_discretize.cpp +++ b/test/ubench/fvm_discretize.cpp @@ -10,7 +10,6 @@ #include -#include "event_queue.hpp" #include "fvm_layout.hpp" #ifndef DATADIR diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 070ebb783..206f29fd4 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -72,7 +72,6 @@ set(unit_sources test_dry_run_context.cpp test_event_delivery.cpp test_event_generators.cpp - test_event_queue.cpp test_event_stream.cpp test_expected.cpp test_filter.cpp diff --git a/test/unit/test_event_queue.cpp b/test/unit/test_event_queue.cpp deleted file mode 100644 index 2020d5155..000000000 --- a/test/unit/test_event_queue.cpp +++ /dev/null @@ -1,160 +0,0 @@ -#include - -#include -#include -#include - -#include - -#include "event_queue.hpp" - -using namespace arb; - -TEST(event_queue, push) { - using ps_event_queue = event_queue; - - ps_event_queue q; - - q.push({0u, 2.f, 2.f}); - q.push({1u, 1.f, 2.f}); - q.push({2u, 20.f, 2.f}); - q.push({3u, 8.f, 2.f}); - - EXPECT_EQ(4u, q.size()); - - std::vector times; - float maxtime(INFINITY); - while (!q.empty()) { - times.push_back(q.pop_if_before(maxtime)->time); - } - - EXPECT_TRUE(std::is_sorted(times.begin(), times.end())); -} - -TEST(event_queue, pop_if_before) { - using ps_event_queue = event_queue; - - cell_lid_type target[4] = {0u, 1u, 2u, 3u}; - - spike_event events[] = { - {target[0], 1.f, 2.f}, - {target[1], 2.f, 2.f}, - {target[2], 3.f, 2.f}, - {target[3], 4.f, 2.f} - }; - - ps_event_queue q; - for (const auto& ev: events) { - q.push(ev); - } - - EXPECT_EQ(4u, q.size()); - - auto e1 = q.pop_if_before(0.); - EXPECT_FALSE(e1); - EXPECT_EQ(4u, q.size()); - - auto e2 = q.pop_if_before(5.); - EXPECT_TRUE(e2); - EXPECT_EQ(e2->target, target[0]); - EXPECT_EQ(3u, q.size()); - - auto e3 = q.pop_if_before(5.); - EXPECT_TRUE(e3); - EXPECT_EQ(e3->target, target[1]); - EXPECT_EQ(2u, q.size()); - - auto e4 = q.pop_if_before(2.5); - EXPECT_FALSE(e4); - EXPECT_EQ(2u, q.size()); - - auto e5 = q.pop_if_before(5.); - EXPECT_TRUE(e5); - EXPECT_EQ(e5->target, target[2]); - EXPECT_EQ(1u, q.size()); - - q.pop_if_before(5.); - EXPECT_EQ(0u, q.size()); - EXPECT_TRUE(q.empty()); - - // empty queue should always return "false" - auto e6 = q.pop_if_before(100.); - EXPECT_FALSE(e6); -} - -TEST(event_queue, pop_if_not_after) { - struct event { - int time; - - event(int t): time(t) {} - }; - - event_queue queue; - - queue.push(1); - queue.push(3); - queue.push(5); - - auto e1 = queue.pop_if_not_after(2); - EXPECT_TRUE(e1); - EXPECT_EQ(1, e1->time); - - auto e2 = queue.pop_if_before(3); - EXPECT_FALSE(e2); - - auto e3 = queue.pop_if_not_after(3); - EXPECT_TRUE(e3); - EXPECT_EQ(3, e3->time); - - auto e4 = queue.pop_if_not_after(4); - EXPECT_FALSE(e4); -} - -// Event queues can be defined for arbitrary copy-constructible events -// for which `event_time(ev)` returns the corresponding time. Time values just -// need to be well-ordered on '>'. - -struct wrapped_float { - wrapped_float() {} - wrapped_float(float f): f(f) {} - - float f; - bool operator>(wrapped_float x) const { return f>x.f; } -}; - -struct minimal_event { - wrapped_float value; - explicit minimal_event(float x): value(x) {} -}; - -const wrapped_float& event_time(const minimal_event& ev) { return ev.value; } - -TEST(event_queue, minimal_event_impl) { - minimal_event events[] = { - minimal_event(3.f), - minimal_event(2.f), - minimal_event(2.f), - minimal_event(10.f) - }; - - std::vector expected; - for (const auto& ev: events) { - expected.push_back(ev.value.f); - } - std::sort(expected.begin(), expected.end()); - - event_queue q; - for (auto& ev: events) { - q.push(ev); - } - - wrapped_float maxtime(INFINITY); - - std::vector times; - while (q.size()) { - times.push_back(q.pop_if_before(maxtime)->value.f); - } - - EXPECT_EQ(expected, times); -} - diff --git a/test/unit/test_event_stream.cpp b/test/unit/test_event_stream.cpp index c6b53e04d..b7f8d762e 100644 --- a/test/unit/test_event_stream.cpp +++ b/test/unit/test_event_stream.cpp @@ -1,110 +1,25 @@ -#include -#include - -#include "timestep_range.hpp" -#include "backends/event.hpp" #include "backends/multicore/event_stream.hpp" -#include "util/rangeutil.hpp" - -using namespace arb; +#include "./test_event_stream.hpp" namespace { - constexpr cell_local_size_type mech = 13u; - - target_handle handle[4] = { - target_handle(mech, 0u), - target_handle(mech, 1u), - target_handle(mech, 4u), - target_handle(mech, 2u) - }; - std::vector common_events = { - deliverable_event(2.0, handle[1], 2.f), - deliverable_event(3.0, handle[3], 4.f), - deliverable_event(3.0, handle[0], 1.f), - deliverable_event(5.0, handle[2], 3.f), - deliverable_event(5.5, handle[2], 6.f) - }; - - bool event_matches(const arb_deliverable_event_data& e, unsigned i) { - const auto& expected = common_events[i]; - return (e.weight == expected.weight); +template +void check(Result result) { + for (std::size_t step=0; step; - - event_stream m; - - arb_deliverable_event_stream s; - - timestep_range dts{0, 6, 1.0}; - EXPECT_EQ(dts.size(), 6u); - - std::vector> events(dts.size()); - for (const auto& ev : common_events) { - events[dts.find(event_time(ev))-dts.begin()].push_back(ev); - } - arb_assert(util::sum_by(events, [] (const auto& v) {return v.size();}) == common_events.size()); - - m.init(events); - - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 0: no events - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 1: no events - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 2: 1 event at mech_index 1 - EXPECT_FALSE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 1u); - EXPECT_TRUE(event_matches(s.begin[0], 0u)); - - m.mark(); - // current time is 3: 2 events at mech_index 0 and 2 - EXPECT_FALSE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 2u); - // the order of these 2 events will be inverted on GPU due to sorting - EXPECT_TRUE(event_matches(s.begin[0], 1u)); - EXPECT_TRUE(event_matches(s.begin[1], 2u)); - - m.mark(); - // current time is 4: no events - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 5: 2 events at mech_index 4 - EXPECT_FALSE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 2u); - EXPECT_TRUE(event_matches(s.begin[0], 3u)); - EXPECT_TRUE(event_matches(s.begin[1], 4u)); +} - m.mark(); - // current time is past time range - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); +TEST(event_stream, single_step) { + check(single_step()); +} - m.clear(); - // no events after clear - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); +TEST(event_stream, multi_step) { + check(multi_step()); } diff --git a/test/unit/test_event_stream.hpp b/test/unit/test_event_stream.hpp new file mode 100644 index 000000000..412e5f723 --- /dev/null +++ b/test/unit/test_event_stream.hpp @@ -0,0 +1,212 @@ +#pragma once + +#include +#include +#include + +#include "timestep_range.hpp" +#include "backends/event.hpp" +#include "util/rangeutil.hpp" + +namespace { + +using namespace arb; + +void check_result(arb_deliverable_event_data const* results, std::vector const& expected) { + for (std::size_t i=0; i +struct result { + timestep_range steps; + std::unordered_map streams; + std::unordered_map>> expected; +}; + +template +result single_step() { + // events for 3 cells and 2 mechanisms and according targets + // + // target handles | events + // =================================================================== + // target cell div mech_id mech_index lid | t=[0,1) + // =================================================================== + // 0 0 0 0 0 0 | e@t=0.0,w=0.0 + // ------------------------------------------------------------------- + // 1 0 1 0 1 | e@t=0.1,w=1.0 + // =================================================================== + // 2 1 2 0 0 0 | e@t=0.5,w=0.2 + // 3 1 0 1 1 | e@t=0.1,w=0.3 + // ------------------------------------------------------------------- + // 4 1 1 0 2 | e@t=0.4,w=1.2 + // =================================================================== + // 5 3 5 0 0 0 | e@t=0.4,w=0.1 + // 6 3 0 1 1 | + // ------------------------------------------------------------------- + // 7 3 1 0 2 | e@t=0.3,w=1.1 + // 8 3 1 1 3 | + // 9 3 1 2 4 | e@t=0.3,w=1.3 + // =================================================================== + // 10 + + const std::vector handles = { + {0, 0}, + {1, 0}, + {0, 0}, + {0, 1}, + {1, 0}, + {0, 0}, + {0, 1}, + {1, 0}, + {1, 1}, + {1, 2} + }; + + const std::vector divs = {0, 2, 5, 10}; + + std::vector> events = { + {{0, 0.0, 0.0f}, {1, 0.0, 1.0f}}, + {{1, 0.1, 0.3f}, {2, 0.4, 1.2f}, {0, 0.5, 0.2f}}, + {{2, 0.3, 1.1f}, {4, 0.3, 1.3f}, {0, 0.4, 0.1f}} + }; + + // prepare return value + result res { + timestep_range{0,1,1}, + {{0u, Stream{}}, {1u, Stream{}}}, + {} + }; + + // expected outcome: one stream per mechanism, events ordered + res.expected[0u] = std::vector>{ + { {0, 0.0f}, {0, 0.1f}, {0, 0.2f}, {1, 0.3f} } }; + res.expected[1u] = std::vector>{ + { {0, 1.0f}, {0, 1.1f}, {0, 1.2f}, {2, 1.3f} } }; + + // initialize event streams + auto lanes = util::subrange_view(events, 0u, events.size()); + initialize(lanes, handles, divs, res.steps, res.streams); + + return res; +} + +template +result multi_step() { + // number of events, cells, mechanisms and targets + std::size_t num_events = 500; + std::size_t num_cells = 20; + std::size_t num_mechanisms = 5; + std::size_t num_targets_per_mechanism_and_cell = 8; + std::size_t num_steps = 200; + double end_time = 1.0; + + result res { + timestep_range(0.0, end_time, end_time/num_steps), + {}, + {} + }; + for (std::size_t mech_id=0; mech_id divs(num_cells+1, 0u); + std::vector handles; + handles.reserve(num_cells*num_mechanisms*num_targets_per_mechanism_and_cell); + for (std::size_t cell=0; cell(mech_id), static_cast(mech_index)); + } + } + divs[cell+1] = divs[cell] + num_mechanisms*num_targets_per_mechanism_and_cell; + } + + // events are binned by cell + std::vector> events(num_cells); + + // generate random events + std::mt19937 gen(42); + std::uniform_int_distribution<> cell_dist(0, num_cells-1); + std::uniform_int_distribution<> mech_id_dist(0, num_mechanisms-1); + std::uniform_int_distribution<> mech_index_dist(0, num_targets_per_mechanism_and_cell-1); + std::uniform_real_distribution<> time_dist(0.0, end_time); + for (std::size_t i=0; i(target), time, weight); + } + + // sort events by time + for (auto& v : events) { + std::stable_sort(v.begin(), v.end(), [](auto const& l, auto const& r) { return l.time < r.time; }); + } + + // compute expected order as permutation of a pair which indexes into events: + // first index is cell id, second index is item index + std::vector> expected_order; + expected_order.reserve(num_events); + for (std::size_t cell=0; cellt_begin(); + auto r_t0 = res.steps.find(r_event.time)->t_begin(); + auto const& l_handle = handles[divs[l_cell] + l_event.target]; + auto const& r_handle = handles[divs[r_cell] + r_event.target]; + auto l_mech_id = l_handle.mech_id; + auto r_mech_id = r_handle.mech_id; + auto l_mech_index = l_handle.mech_index; + auto r_mech_index = r_handle.mech_index; + + // sort by mech_id + if (l_mech_id < r_mech_id) return true; + if (l_mech_id > r_mech_id) return false; + // if same mech_id, sort by step + if (l_t0 < r_t0) return true; + if (l_t0 > r_t0) return false; + // if same step, sort by mech_index + if (l_mech_index < r_mech_index) return true; + if (l_mech_index > r_mech_index) return false; + // if same mech_index sort by time + return l_event.time < r_event.time; + }); + + // expected results are now mapped by mechanism id -> vector of vector of deliverable event data + // the outer vector represents time step bins, the inner vector the ordered stream of events + for (std::size_t mech_id=0; mech_id>(num_steps); + } + + // create expected results from the previously defined expected order and choose unique event weight + std::size_t cc=0; + for (auto [cell, idx] : expected_order) { + auto& event = events[cell][idx]; + auto step = res.steps.find(event.time) - res.steps.begin(); + auto const& handle = handles[divs[cell] + event.target]; + auto mech_id = handle.mech_id; + auto mech_index = handle.mech_index; + event.weight = cc++; + res.expected[mech_id][step].push_back(arb_deliverable_event_data{mech_index, event.weight}); + } + + // initialize event streams + auto lanes = util::subrange_view(events, 0u, events.size()); + initialize(lanes, handles, divs, res.steps, res.streams); + + return res; +} + +} diff --git a/test/unit/test_event_stream_gpu.cpp b/test/unit/test_event_stream_gpu.cpp index 4b548f91b..56b9c44a9 100644 --- a/test/unit/test_event_stream_gpu.cpp +++ b/test/unit/test_event_stream_gpu.cpp @@ -1,125 +1,38 @@ -#include -#include - -#include "timestep_range.hpp" -#include "backends/event.hpp" #include "backends/gpu/event_stream.hpp" #include "memory/memory.hpp" #include "memory/gpu_wrappers.hpp" #include "util/rangeutil.hpp" - -using namespace arb; +#include "./test_event_stream.hpp" namespace { - constexpr cell_local_size_type mech = 13u; - - target_handle handle[4] = { - target_handle(mech, 0u), - target_handle(mech, 1u), - target_handle(mech, 4u), - target_handle(mech, 2u) - }; - - std::vector common_events = { - deliverable_event(2.0, handle[1], 2.f), - deliverable_event(3.0, handle[3], 4.f), - deliverable_event(3.0, handle[0], 1.f), - deliverable_event(5.0, handle[2], 3.f), - deliverable_event(5.5, handle[2], 6.f) - }; - - bool event_matches(const arb_deliverable_event_data& e, unsigned i) { - const auto& expected = common_events[i]; - return (e.weight == expected.weight); - } - template - void cpy_d2h(T* dst, const T* src) { - memory::gpu_memcpy_d2h(dst, src, sizeof(T)); - } +template +void cpy_d2h(T* dst, const T* src, std::size_t n) { + memory::gpu_memcpy_d2h(dst, src, sizeof(T)*n); } -TEST(event_stream_gpu, mark) { - using event_stream = gpu::event_stream; - - auto thread_pool = std::make_shared(); - - event_stream m(thread_pool); - - arb_deliverable_event_stream s; - arb_deliverable_event_data d; - - timestep_range dts{0, 6, 1.0}; - EXPECT_EQ(dts.size(), 6u); - - std::vector> events(dts.size()); - for (const auto& ev : common_events) { - events[dts.find(event_time(ev))-dts.begin()].push_back(ev); +template +void check(Result result) { + for (std::size_t step=0; step host_data(marked.end - marked.begin); + EXPECT_EQ(host_data.size(), result.expected[mech_id][step].size()); + if (host_data.size()) { + cpy_d2h(host_data.data(), marked.begin, host_data.size()); + check_result(host_data.data(), result.expected[mech_id][step]); + } + } } - arb_assert(util::sum_by(events, [] (const auto& v) {return v.size();}) == common_events.size()); - - m.init(events); - - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 0: no events - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 1: no events - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); - - m.mark(); - // current time is 2: 1 event at mech_index 1 - EXPECT_FALSE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 1u); - cpy_d2h(&d, s.begin+0); - EXPECT_TRUE(event_matches(d, 0u)); - - m.mark(); - // current time is 3: 2 events at mech_index 0 and 2 - EXPECT_FALSE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 2u); - // the order of these 2 events is inverted on GPU due to sorting - cpy_d2h(&d, s.begin+0); - EXPECT_TRUE(event_matches(d, 2u)); - cpy_d2h(&d, s.begin+1); - EXPECT_TRUE(event_matches(d, 1u)); - - m.mark(); - // current time is 4: no events - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); +} - m.mark(); - // current time is 5: 2 events at mech_index 4 - EXPECT_FALSE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 2u); - cpy_d2h(&d, s.begin+0); - EXPECT_TRUE(event_matches(d, 3u)); - cpy_d2h(&d, s.begin+1); - EXPECT_TRUE(event_matches(d, 4u)); +} - m.mark(); - // current time is past time range - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); +TEST(event_stream_gpu, single_step) { + check(single_step()); +} - m.clear(); - // no events after clear - EXPECT_TRUE(m.empty()); - s = m.marked_events(); - EXPECT_EQ(s.end - s.begin, 0u); +TEST(event_stream_gpu, multi_step) { + check(multi_step()); } From fd2718c3e16d66342239130b586867df3d38afe4 Mon Sep 17 00:00:00 2001 From: boeschf <48126478+boeschf@users.noreply.github.com> Date: Tue, 22 Oct 2024 17:47:50 +0200 Subject: [PATCH 16/30] use supported compiler for codeql (#2422) We require at least gcc12 --- .github/workflows/codeql.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 4974cd45e..1731e63a1 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,7 +66,7 @@ jobs: run: | mkdir build cd build - cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_COMPILER=gcc -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON + cmake .. -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON make -j4 tests examples pyarb cd - From b8b768d6aed3aa1e72b91912753d98bbc17fb44c Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Wed, 23 Oct 2024 19:38:47 +0200 Subject: [PATCH 17/30] Reduce redundant GPU allocations (#2393) # Introduction - Do not allocate and fill storage for resetting ion concentrations if these are never changed. Reasoning: If concentrations are never changed, we do not reset them and thus do not need to store the values. - Remove a duplicate of the CV area in solver and shared state and pass a reference during solving - Remove redundant `solution` from GPU solver - Do not store / allocate diffusion values if not needed. This saves **per CV** - `1 x 8B` for `cv_area` unconditionally - `1 x 8B` for `Xd` for each ion with no diffusion is in use (majority of cases) - `2 x 8B` for `Xi` for each ion (`reset` and `init`) if not written (reasonably often) - `2 x 8B` for `Xo` for each ion (`reset` and `init`) if not written (majority of cases) - `1 x 8B` for `eX` reset for each ion if not read (majority) - `1 x 8B` for `eX` for each ion if not read (rarely) In my standard benchmark, `busyring` with complex cells, this saves about 18% of the total GPU allocation for the cell data (`shared_state`). This has become a mixed bag, fixing a few additional things that came up during testing this: - a bug in event handling on GPU - pybind11 stub generation --------- Co-authored-by: Jannik Luboeinski <33398515+jlubo@users.noreply.github.com> Co-authored-by: boeschf <48126478+boeschf@users.noreply.github.com> --- .github/workflows/codeql.yml | 2 +- .github/workflows/sanitize.yml | 2 +- .github/workflows/test-matrix.yml | 2 +- .github/workflows/test-spack.yml | 4 +- CMakeLists.txt | 4 +- arbor/backends/common_types.hpp | 41 +++++++++++++++ arbor/backends/gpu/event_stream.hpp | 2 +- arbor/backends/gpu/shared_state.cpp | 63 +++++++--------------- arbor/backends/gpu/shared_state.hpp | 42 ++++++--------- arbor/backends/multicore/shared_state.cpp | 64 +++++++---------------- arbor/backends/multicore/shared_state.hpp | 43 +++++++-------- arbor/fvm_layout.cpp | 44 ++++++++++------ arbor/fvm_layout.hpp | 1 + python/CMakeLists.txt | 26 ++++----- python/mechanism.cpp | 16 ++++-- spack/package.py | 3 ++ test/unit/test_probe.cpp | 10 ++-- 17 files changed, 184 insertions(+), 185 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 1731e63a1..fdf5c37ec 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,7 +66,7 @@ jobs: run: | mkdir build cd build - cmake .. -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON + cmake .. -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON -DARB_BUILD_PYTHON_STUBS=OFF make -j4 tests examples pyarb cd - diff --git a/.github/workflows/sanitize.yml b/.github/workflows/sanitize.yml index 4cb6e0e72..ff51846b7 100644 --- a/.github/workflows/sanitize.yml +++ b/.github/workflows/sanitize.yml @@ -62,7 +62,7 @@ jobs: mkdir build cd build export SAN="-fsanitize=${{ matrix.sanitizer }} -fno-omit-frame-pointer" - cmake .. -GNinja -DCMAKE_BUILD_TYPE=debug -DCMAKE_CXX_FLAGS="$SAN" -DCMAKE_C_FLAGS="$SAN" -DCMAKE_EXE_LINKER_FLAGS="$SAN" -DCMAKE_MODULE_LINKER_FLAGS="$SAN" -DARB_WITH_ASSERTIONS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_VECTORIZE=${{ matrix.simd }} -DARB_WITH_MPI=OFF -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` + cmake .. -GNinja -DCMAKE_BUILD_TYPE=debug -DCMAKE_CXX_FLAGS="$SAN" -DCMAKE_C_FLAGS="$SAN" -DCMAKE_EXE_LINKER_FLAGS="$SAN" -DCMAKE_MODULE_LINKER_FLAGS="$SAN" -DARB_BUILD_PYTHON_STUBS=OFF -DARB_WITH_ASSERTIONS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_VECTORIZE=${{ matrix.simd }} -DARB_WITH_MPI=OFF -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` ninja -j4 -v tests examples pyarb cd - - name: Run unit tests diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index 343978e16..50edbf104 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -116,7 +116,7 @@ jobs: - name: Update pip run: python -m pip install --upgrade pip - name: Install Python packages - run: pip install numpy sphinx svgwrite sphinx-rtd-theme mpi4py pandas seaborn + run: pip install numpy sphinx svgwrite sphinx-rtd-theme mpi4py pandas seaborn pybind11-stubgen - name: Clone w/ submodules uses: actions/checkout@v4 with: diff --git a/.github/workflows/test-spack.yml b/.github/workflows/test-spack.yml index d913be5b2..df3856c79 100644 --- a/.github/workflows/test-spack.yml +++ b/.github/workflows/test-spack.yml @@ -74,9 +74,9 @@ jobs: - name: build and install arbor through spack dev-build run: | source spack/share/spack/setup-env.sh - spack install --no-check-signature --only dependencies arbor@develop target=x86_64_v2 +python ^python@${{ matrix.python-version }} + spack install --no-check-signature --only dependencies arbor@develop target=x86_64_v2 +python ~pystubs ^python@${{ matrix.python-version }} cd arbor - spack dev-build arbor@develop target=x86_64_v2 +python ^python@${{ matrix.python-version }} + spack dev-build arbor@develop target=x86_64_v2 +python ~pystubs ^python@${{ matrix.python-version }} - name: load arbor and verify installation, python examples. run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index ac4f24c33..bae8235cf 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,7 +7,6 @@ set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ON CACHE INTERNAL "" FORCE) # Make CUDA support throw errors if architectures remain unclear cmake_policy(SET CMP0104 NEW) - # Ensure CMake is aware of the policies for modern RPATH behavior cmake_policy(SET CMP0072 NEW) @@ -46,6 +45,9 @@ if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) endif() endif() +# Use pybind11-stubgen to make type stubs. +cmake_dependent_option(ARB_BUILD_PYTHON_STUBS "Use pybind11-stubgen to build type stubs." ON "ARB_WITH_PYTHON" OFF) + # Turn on this option to force the compilers to produce color output when output is # redirected from the terminal (e.g. when using ninja or a pager). diff --git a/arbor/backends/common_types.hpp b/arbor/backends/common_types.hpp index 357975c64..18f258604 100644 --- a/arbor/backends/common_types.hpp +++ b/arbor/backends/common_types.hpp @@ -2,6 +2,7 @@ #include +#include "fvm_layout.hpp" #include "util/range.hpp" #include "backends/threshold_crossing.hpp" #include "execution_context.hpp" @@ -21,4 +22,44 @@ struct fvm_detector_info { execution_context ctx; }; +struct ion_data_flags { + // flags for resetting ion states after W access + bool write_eX_:1 = false; // is eX written? + bool write_Xo_:1 = false; // is Xo written? + bool write_Xi_:1 = false; // is Xi written? + bool write_Xd_:1 = false; // is Xd written? + bool read_eX_:1 = false; // is eX read? + bool read_Xo_:1 = false; // is Xo read? + bool read_Xi_:1 = false; // is Xi read? + bool read_Xd_:1 = false; // is Xd read? + + ion_data_flags(const fvm_ion_config& config): + write_eX_(config.revpot_written), + write_Xo_(config.econc_written), + write_Xi_(config.iconc_written), + write_Xd_(config.is_diffusive), + read_eX_(config.revpot_read), + read_Xo_(config.econc_read), + read_Xi_(config.iconc_read), + read_Xd_(config.is_diffusive) + {} + + ion_data_flags() = default; + ion_data_flags(const ion_data_flags&) = default; + ion_data_flags& operator=(const ion_data_flags&) = default; + + bool xi() const { return read_Xi_ || write_Xi_; } + bool reset_xi() const { return write_Xi_; } + + bool xo() const { return read_Xo_ || write_Xo_; } + bool reset_xo() const { return write_Xo_; } + + bool xd() const { return read_Xd_ || write_Xd_; } + bool reset_xd() const { return write_Xd_; } + + bool ex() const { return read_eX_ || write_eX_; } + bool reset_ex() const { return write_eX_; } +}; + + } diff --git a/arbor/backends/gpu/event_stream.hpp b/arbor/backends/gpu/event_stream.hpp index e332bedbc..3260fbace 100644 --- a/arbor/backends/gpu/event_stream.hpp +++ b/arbor/backends/gpu/event_stream.hpp @@ -19,7 +19,7 @@ struct event_stream : BaseEventStream { protected: void init() override final { - resize(this->device_ev_data_, this->device_ev_data_.size()); + resize(this->device_ev_data_, this->ev_data_.size()); memory::copy_async(this->ev_data_, this->device_ev_data_); this->base_ptr_ = this->device_ev_data_.data(); } diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp index 091bcde51..b7f427710 100644 --- a/arbor/backends/gpu/shared_state.cpp +++ b/arbor/backends/gpu/shared_state.cpp @@ -46,51 +46,31 @@ std::pair minmax_value_impl(arb_size_type n, con ion_state::ion_state(const fvm_ion_config& ion_data, unsigned, // alignment/padding ignored. solver_ptr ptr): - write_eX_(ion_data.revpot_written), - write_Xo_(ion_data.econc_written), - write_Xi_(ion_data.iconc_written), - write_Xd_(ion_data.is_diffusive), - read_Xo_(ion_data.econc_written || ion_data.econc_read), // ensure that if we have W access, also R access is flagged - read_Xi_(ion_data.iconc_written || ion_data.iconc_read), + flags_(ion_data), node_index_(make_const_view(ion_data.cv)), iX_(ion_data.cv.size(), NAN), - eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end()), gX_(ion_data.cv.size(), NAN), charge(1u, static_cast(ion_data.charge)), solver(std::move(ptr)) { - // We don't need to allocate these if we never use them... - if (read_Xi_) { - Xi_ = make_const_view(ion_data.init_iconc); - } - if (read_Xo_) { - Xo_ = make_const_view(ion_data.init_econc); - } - if (write_Xi_ || write_Xd_) { - // ... but this is used by Xd and Xi! - reset_Xi_ = make_const_view(ion_data.reset_iconc); - } - if (write_Xi_) { - init_Xi_ = make_const_view(ion_data.init_iconc); - arb_assert(node_index_.size()==init_Xi_.size()); - } - if (write_Xo_) { - init_Xo_ = make_const_view(ion_data.init_econc); - reset_Xo_ = make_const_view(ion_data.reset_econc); - arb_assert(node_index_.size()==init_Xo_.size()); - } - if (write_eX_) { - init_eX_ = make_const_view(ion_data.init_revpot); - arb_assert(node_index_.size()==init_eX_.size()); - } - if (write_Xd_) { - Xd_ = array(ion_data.cv.size(), NAN); - } + if (flags_.reset_xi() + ||flags_.reset_xd()) reset_Xi_ = make_const_view(ion_data.reset_iconc); + if (flags_.reset_xi()) init_Xi_ = make_const_view(ion_data.init_iconc); + if (flags_.xi()) Xi_ = make_const_view(ion_data.init_iconc); + + if (flags_.reset_xo()) reset_Xo_ = make_const_view(ion_data.reset_econc); + if (flags_.reset_xo()) init_Xo_ = make_const_view(ion_data.init_econc); + if (flags_.xo()) Xo_ = make_const_view(ion_data.init_econc); + + if (flags_.reset_ex()) init_eX_ = make_const_view(ion_data.init_revpot); + if (flags_.ex()) eX_ = make_const_view(ion_data.init_revpot); + + if (flags_.xd()) Xd_ = make_const_view(ion_data.reset_iconc); } void ion_state::init_concentration() { // NB. not resetting Xd here, it's controlled via the solver. - if (write_Xi_) memory::copy(init_Xi_, Xi_); - if (write_Xo_) memory::copy(init_Xo_, Xo_); + if (flags_.reset_xi()) memory::copy(init_Xi_, Xi_); + if (flags_.reset_xo()) memory::copy(init_Xo_, Xo_); } void ion_state::zero_current() { @@ -100,13 +80,10 @@ void ion_state::zero_current() { void ion_state::reset() { zero_current(); - if (write_Xi_) memory::copy(reset_Xi_, Xi_); - if (write_Xo_) memory::copy(reset_Xo_, Xo_); - if (write_eX_) memory::copy(init_eX_, eX_); - // This goes _last_ or at least after Xi since we might have removed reset_Xi - // when Xi is constant. Thus conditionally resetting Xi first and then copying - // Xi -> Xd is save in all cases. - if (write_Xd_) memory::copy(reset_Xi_, Xd_); + if (flags_.reset_xi()) memory::copy(reset_Xi_, Xi_); + if (flags_.reset_xo()) memory::copy(reset_Xo_, Xo_); + if (flags_.reset_ex()) memory::copy(init_eX_, eX_); + if (flags_.reset_xd()) memory::copy(reset_Xi_, Xd_); } // istim_state methods: diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp index 1ca224ae9..39f330202 100644 --- a/arbor/backends/gpu/shared_state.hpp +++ b/arbor/backends/gpu/shared_state.hpp @@ -24,7 +24,6 @@ namespace arb { namespace gpu { - /* * Ion state fields correspond to NMODL ion variables, where X * is replaced with the name of the ion. E.g. for calcium 'ca': @@ -40,30 +39,23 @@ struct ARB_ARBOR_API ion_state { using solver_type = arb::gpu::diffusion_state; using solver_ptr = std::unique_ptr; - bool write_eX_:1; // is eX written? - bool write_Xo_:1; // is Xo written? - bool write_Xi_:1; // is Xi written? - bool write_Xd_:1; // is Xd written? - bool read_eX_:1; // is eX read? - bool read_Xo_:1; // is Xo read? - bool read_Xi_:1; // is Xi read? - bool read_Xd_:1; // is Xd read? - - iarray node_index_; // Instance to CV map. - array iX_; // (A/m²) current density - array eX_; // (mV) reversal potential - array Xi_; // (mM) internal concentration - array Xd_; // (mM) diffusive concentration - array Xo_; // (mM) external concentration - array gX_; // (kS/m²) per-species conductivity - - array init_Xi_; // (mM) area-weighted initial internal concentration - array init_Xo_; // (mM) area-weighted initial external concentration - array reset_Xi_; // (mM) area-weighted user-set internal concentration - array reset_Xo_; // (mM) area-weighted user-set internal concentration - array init_eX_; // (mM) initial reversal potential - - array charge; // charge of ionic species (global, length 1) + ion_data_flags flags_; // Track what and when to reset / allocate + + iarray node_index_; // Instance to CV map. + array iX_; // (A/m²) current density + array eX_; // (mV) reversal potential + array Xi_; // (mM) internal concentration + array Xd_; // (mM) diffusive concentration + array Xo_; // (mM) external concentration + array gX_; // (kS/m²) per-species conductivity + + array init_Xi_; // (mM) area-weighted initial internal concentration + array init_Xo_; // (mM) area-weighted initial external concentration + array reset_Xi_; // (mM) area-weighted user-set internal concentration + array reset_Xo_; // (mM) area-weighted user-set internal concentration + array init_eX_; // (mM) initial reversal potential + + array charge; // charge of ionic species (global, length 1) solver_ptr solver = nullptr; diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp index b70089c4e..c55c762a1 100644 --- a/arbor/backends/multicore/shared_state.cpp +++ b/arbor/backends/multicore/shared_state.cpp @@ -54,52 +54,31 @@ ion_state::ion_state(const fvm_ion_config& ion_data, unsigned align, solver_ptr ptr): alignment(min_alignment(align)), - write_eX_(ion_data.revpot_written), - write_Xo_(ion_data.econc_written), - write_Xi_(ion_data.iconc_written), - write_Xd_(ion_data.is_diffusive), - read_Xo_(ion_data.econc_written || ion_data.econc_read), // ensure that if we have W access, also R access is flagged - read_Xi_(ion_data.iconc_written || ion_data.iconc_read), - read_Xd_(ion_data.is_diffusive), + flags_{ion_data}, node_index_(ion_data.cv.begin(), ion_data.cv.end(), pad(alignment)), iX_(ion_data.cv.size(), NAN, pad(alignment)), - eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)), gX_(ion_data.cv.size(), NAN, pad(alignment)), charge(1u, ion_data.charge, pad(alignment)), solver(std::move(ptr)) { - // We don't need to allocate these if we never use them... - if (read_Xi_) { - Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; - } - if (read_Xo_) { - Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; - } - if (write_Xi_ || write_Xd_) { - // ... but this is used by Xd and Xi! - reset_Xi_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; - } - if (write_Xi_) { - init_Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; - arb_assert(node_index_.size()==init_Xi_.size()); - } - if (write_Xo_) { - init_Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; - reset_Xo_ = {ion_data.reset_econc.begin(), ion_data.reset_econc.end(), pad(alignment)}; - arb_assert(node_index_.size()==init_Xo_.size()); - } - if (write_eX_) { - init_eX_ = {ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)}; - arb_assert(node_index_.size()==init_eX_.size()); - } - if (read_Xd_) { - Xd_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; - } + if (flags_.reset_xi() + ||flags_.reset_xd()) reset_Xi_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; + if (flags_.reset_xi()) init_Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; + if (flags_.xi()) Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; + + if (flags_.reset_xo()) reset_Xo_ = {ion_data.reset_econc.begin(), ion_data.reset_econc.end(), pad(alignment)}; + if (flags_.reset_xo()) init_Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; + if (flags_.xo()) Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; + + if (flags_.reset_ex()) init_eX_ = {ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)}; + if (flags_.ex()) eX_ = {ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)}; + + if (flags_.xd()) Xd_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; } void ion_state::init_concentration() { // NB. not resetting Xd here, it's controlled via the solver. - if (write_Xi_) std::copy(init_Xi_.begin(), init_Xi_.end(), Xi_.begin()); - if (write_Xo_) std::copy(init_Xo_.begin(), init_Xo_.end(), Xo_.begin()); + if (flags_.reset_xi()) std::copy(init_Xi_.begin(), init_Xi_.end(), Xi_.begin()); + if (flags_.reset_xo()) std::copy(init_Xo_.begin(), init_Xo_.end(), Xo_.begin()); } void ion_state::zero_current() { @@ -109,13 +88,10 @@ void ion_state::zero_current() { void ion_state::reset() { zero_current(); - if (write_Xi_) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xi_.begin()); - if (write_Xo_) std::copy(reset_Xo_.begin(), reset_Xo_.end(), Xo_.begin()); - if (write_eX_) std::copy(init_eX_.begin(), init_eX_.end(), eX_.begin()); - // This goes _last_ or at least after Xi since we might have removed reset_Xi - // when Xi is constant. Thus conditionally resetting Xi first and then copying - // Xi -> Xd is safe in all cases. - if (write_Xd_) std::copy(Xi_.begin(), Xi_.end(), Xd_.begin()); + if (flags_.reset_xi()) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xi_.begin()); + if (flags_.reset_xo()) std::copy(reset_Xo_.begin(), reset_Xo_.end(), Xo_.begin()); + if (flags_.reset_ex()) std::copy(init_eX_.begin(), init_eX_.end(), eX_.begin()); + if (flags_.reset_xd()) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xd_.begin()); } // istim_state methods: diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp index 1bb3c59f2..3674d635d 100644 --- a/arbor/backends/multicore/shared_state.hpp +++ b/arbor/backends/multicore/shared_state.hpp @@ -48,32 +48,25 @@ struct ARB_ARBOR_API ion_state { using solver_type = diffusion_solver; using solver_ptr = std::unique_ptr; - unsigned alignment = 1; // Alignment and padding multiple. + unsigned alignment = 1; // Alignment and padding multiple. + + ion_data_flags flags_; // Track what and when to reset / allocate + + iarray node_index_; // Instance to CV map. + array iX_; // (A/m²) current density + array eX_; // (mV) reversal potential + array Xi_; // (mM) internal concentration + array Xd_; // (mM) diffusive internal concentration + array Xo_; // (mM) external concentration + array gX_; // (kS/m²) per-species conductivity + + array init_Xi_; // (mM) area-weighted initial internal concentration + array init_Xo_; // (mM) area-weighted initial external concentration + array reset_Xi_; // (mM) area-weighted user-set internal concentration + array reset_Xo_; // (mM) area-weighted user-set internal concentration + array init_eX_; // (mV) initial reversal potential - bool write_eX_:1; // is eX written? - bool write_Xo_:1; // is Xo written? - bool write_Xi_:1; // is Xi written? - bool write_Xd_:1; // is Xd written? - bool read_eX_:1; // is eX read? - bool read_Xo_:1; // is Xo read? - bool read_Xi_:1; // is Xi read? - bool read_Xd_:1; // is Xd read? - - iarray node_index_; // Instance to CV map. - array iX_; // (A/m²) current density - array eX_; // (mV) reversal potential - array Xi_; // (mM) internal concentration - array Xd_; // (mM) diffusive internal concentration - array Xo_; // (mM) external concentration - array gX_; // (kS/m²) per-species conductivity - - array init_Xi_; // (mM) area-weighted initial internal concentration - array init_Xo_; // (mM) area-weighted initial external concentration - array reset_Xi_; // (mM) area-weighted user-set internal concentration - array reset_Xo_; // (mM) area-weighted user-set internal concentration - array init_eX_; // (mV) initial reversal potential - - array charge; // charge of ionic species (global value, length 1) + array charge; // charge of ionic species (global value, length 1) solver_ptr solver = nullptr; diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index e5c809d04..158edc4fb 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -707,8 +707,8 @@ fvm_mechanism_data& append(fvm_mechanism_data& left, const fvm_mechanism_data& r arb_size_type target_offset = left.n_target; - for (const auto& [k, R]: right.ions) { - fvm_ion_config& L = left.ions[k]; + for (const auto& [ion, R]: right.ions) { + fvm_ion_config& L = left.ions[ion]; append(L.cv, R.cv); append(L.init_iconc, R.init_iconc); @@ -723,6 +723,7 @@ fvm_mechanism_data& append(fvm_mechanism_data& left, const fvm_mechanism_data& r L.econc_read |= R.econc_read; L.iconc_read |= R.iconc_read; L.revpot_written |= R.revpot_written; + L.revpot_read |= R.revpot_read; } for (const auto& kv: right.mechanisms) { @@ -826,6 +827,7 @@ struct fvm_ion_build_data { bool write_xo = false; bool read_xi = false; bool read_xo = false; + bool read_ex = false; std::vector support; auto& add_to_support(const std::vector& cvs) { @@ -839,6 +841,7 @@ struct fvm_ion_build_data { write_xo |= dep.write_concentration_ext; read_xi |= dep.read_concentration_int; read_xo |= dep.read_concentration_ext; + read_ex |= dep.read_reversal_potential; return *this; } }; @@ -920,10 +923,16 @@ make_gj_mechanism_config(const std::unordered_map +// Build reversal potential configs. Returns { X | X ion && eX is written; Xi / Xo read } +struct revpot_ion_config { + bool read_Xi = false; + bool read_Xo = false; + bool write_eX = false; +}; + +std::unordered_map make_revpot_mechanism_config(const std::unordered_map& method, - std::unordered_map& ions, + const std::unordered_map& ions, const cell_build_data& data, fvm_mechanism_config_map&); @@ -1083,8 +1092,12 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& auto method = dflt.reversal_potential_method; method.insert(global_dflt.reversal_potential_method.begin(), global_dflt.reversal_potential_method.end()); - auto written = make_revpot_mechanism_config(method, M.ions, data, M.mechanisms); - for (const auto& ion: written) M.ions[ion].revpot_written = true; + auto confs = make_revpot_mechanism_config(method, M.ions, data, M.mechanisms); + for (const auto& [ion, conf]: confs) { + M.ions[ion].revpot_written |= conf.write_eX; + M.ions[ion].iconc_read |= conf.read_Xi; + M.ions[ion].econc_read |= conf.read_Xo; + } } M.target_divs = {0u, M.n_target}; @@ -1354,6 +1367,7 @@ make_ion_config(fvm_ion_map build_data, config.iconc_written = build_data.write_xi; config.econc_read = build_data.read_xo; config.iconc_read = build_data.read_xi; + config.revpot_read = build_data.read_ex; if (!config.cv.empty()) result[ion] = std::move(config); } } @@ -1634,13 +1648,13 @@ make_gj_mechanism_config(const std::unordered_map +std::unordered_map make_revpot_mechanism_config(const std::unordered_map& method, - std::unordered_map& ions, + const std::unordered_map& ions, const cell_build_data& data, fvm_mechanism_config_map& result) { std::unordered_map revpot_tbl; - std::unordered_set written; + std::unordered_map ex_config; for (const auto& ion: util::keys(data.ion_species)) { if (!method.count(ion)) continue; @@ -1672,7 +1686,7 @@ make_revpot_mechanism_config(const std::unordered_map()) .def_readonly("write_int_con", &arb::ion_dependency::write_concentration_int) .def_readonly("write_ext_con", &arb::ion_dependency::write_concentration_ext) + .def_readonly("read_int_con", &arb::ion_dependency::read_concentration_int) + .def_readonly("read_ext_con", &arb::ion_dependency::read_concentration_ext) .def_readonly("write_rev_pot", &arb::ion_dependency::write_reversal_potential) .def_readonly("read_rev_pot", &arb::ion_dependency::read_reversal_potential) .def("__repr__", [](const arb::ion_dependency& dep) { auto tf = [](bool x) {return x? "True": "False";}; - return util::pprintf("{write_int_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", - tf(dep.write_concentration_int), tf(dep.write_concentration_ext), + return util::pprintf("{read_int_con: {}, write_int_con: {}, read_ext_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", + tf(dep.read_concentration_int), + tf(dep.write_concentration_int), + tf(dep.read_concentration_ext), + tf(dep.write_concentration_ext), tf(dep.write_reversal_potential), tf(dep.read_reversal_potential)); }) .def("__str__", [](const arb::ion_dependency& dep) { auto tf = [](bool x) {return x? "True": "False";}; - return util::pprintf("{write_int_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", - tf(dep.write_concentration_int), tf(dep.write_concentration_ext), + return util::pprintf("{read_int_con: {}, write_int_con: {}, read_ext_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", + tf(dep.read_concentration_int), + tf(dep.write_concentration_int), + tf(dep.read_concentration_ext), + tf(dep.write_concentration_ext), tf(dep.write_reversal_potential), tf(dep.read_reversal_potential)); }) ; diff --git a/spack/package.py b/spack/package.py index 7b9f9d82e..54381c199 100644 --- a/spack/package.py +++ b/spack/package.py @@ -65,6 +65,7 @@ class Arbor(CMakePackage, CudaPackage): variant("doc", default=False, description="Build documentation.") variant("mpi", default=False, description="Enable MPI support") variant("python", default=True, description="Enable Python frontend support") + variant("pystubs", default=True, when="@0.11:", description="Python stub generation") variant( "vectorize", default=False, @@ -122,6 +123,7 @@ class Arbor(CMakePackage, CudaPackage): depends_on("py-pybind11@2.10.1:", when="@0.7.1:", type="build") depends_on("py-pybind11@2.10.1:", when="@0.7.1:", type="build") depends_on("py-pybind11@2.10.1:", when="@2.11.1:", type="build") + depends_on("py-pybind11-stubgen@2.5:", when="+pystubs", type="build") # sphinx based documentation with when("+doc"): @@ -140,6 +142,7 @@ def cmake_args(self): self.define_from_variant("ARB_WITH_PYTHON", "python"), self.define_from_variant("ARB_VECTORIZE", "vectorize"), self.define_from_variant("ARB_USE_HWLOC", "hwloc"), + self.define_from_variant("ARB_BUILD_PYTHON_STUBS", "pystubs"), ] if "+cuda" in self.spec: diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 67de60f5f..489f9adaa 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -540,8 +540,6 @@ void run_ion_density_probe_test(context ctx) { fvm_cell lcell(*ctx); - using array = typename Backend::array; - auto fvm_info = lcell.initialize({0}, rec); // We skipped FVM layout here, so we need to set these manually auto& state = backend_access::state(lcell); @@ -550,12 +548,12 @@ void run_ion_density_probe_test(context ctx) { auto& ca = state.ion_data["ca"]; auto nca = ca.node_index_.size(); auto cai = mk_array(nca, align); - ca.write_Xi_ = true; + ca.flags_.write_Xi_ = true; ca.Xi_ = cai; ca.init_Xi_ = cai; ca.reset_Xi_ = cai; auto cao = mk_array(nca, align); - ca.write_Xo_ = true; + ca.flags_.write_Xo_ = true; ca.Xo_ = cao; ca.init_Xo_ = cao; ca.reset_Xo_ = cao; @@ -563,12 +561,12 @@ void run_ion_density_probe_test(context ctx) { auto& na = state.ion_data["na"]; auto nna = na.node_index_.size(); auto nai = mk_array(nna, align); - na.write_Xi_ = true; + na.flags_.write_Xi_ = true; na.Xi_ = nai; na.init_Xi_ = nai; na.reset_Xi_ = nai; auto nao = mk_array(nna, align); - na.write_Xo_ = true; + na.flags_.write_Xo_ = true; na.Xo_ = nao; na.init_Xo_ = nao; na.reset_Xo_ = nao; From 9ba170a01860962866a43ec7b9b80eb21e082a70 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Thu, 24 Oct 2024 07:14:15 +0200 Subject: [PATCH 18/30] =?UTF-8?q?=F0=9F=A7=B9=20Fix=20GCC=20complaints=20(?= =?UTF-8?q?#2400)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Tree Fixes --- likely a spurious --- warning in `tree.cpp`: ``` [2/6] Building CXX object arbor/CMakeFiles/arbor.dir/tree.cpp.o In file included from /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/algorithm:60, from /Users/hater/src/arbor/arbor/tree.cpp:1: In function 'typename __gnu_cxx::__enable_if::__value, void>::__type std::__fill_a1(_ForwardIterator, _ForwardIterator, const _Tp&) [with _ForwardIterator = unsigned int*; _Tp = unsigned int]', inlined from 'void std::__fill_a(_FIte, _FIte, const _Tp&) [with _FIte = unsigned int*; _Tp = unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algobase.h:998:21, inlined from '_OutputIterator std::__fill_n_a(_OutputIterator, _Size, const _Tp&, random_access_iterator_tag) [with _OutputIterator = unsigned int*; _Size = long unsigned int; _Tp = unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algobase.h:1151:20, inlined from '_OI std::fill_n(_OI, _Size, const _Tp&) [with _OI = unsigned int*; _Size = long unsigned int; _Tp = unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algobase.h:1180:29, inlined from 'static _ForwardIterator std::__uninitialized_default_n_1::__uninit_default_n(_ForwardIterator, _Size) [with _ForwardIterator = unsigned int*; _Size = long unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_uninitialized.h:668:29, inlined from 'static _ForwardIterator std::__uninitialized_default_n_1::__uninit_default_n(_ForwardIterator, _Size) [with _ForwardIterator = unsigned int*; _Size = long unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_uninitialized.h:660:9, inlined from '_ForwardIterator std::__uninitialized_default_n(_ForwardIterator, _Size) [with _ForwardIterator = unsigned int*; _Size = long unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_uninitialized.h:712:20, inlined from '_ForwardIterator std::__uninitialized_default_n_a(_ForwardIterator, _Size, allocator<_Tp>&) [with _ForwardIterator = unsigned int*; _Size = long unsigned int; _Tp = unsigned int]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_uninitialized.h:779:44, inlined from 'void std::vector<_Tp, _Alloc>::_M_default_initialize(size_type) [with _Tp = unsigned int; _Alloc = std::allocator]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_vector.h:1720:36, inlined from 'std::vector<_Tp, _Alloc>::vector(size_type, const allocator_type&) [with _Tp = unsigned int; _Alloc = std::allocator]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_vector.h:558:30, inlined from 'arb::tree::iarray arb::tree::select_new_root(int_type)' at /Users/hater/src/arbor/arbor/tree.cpp:219:34: /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algobase.h:952:18: warning: 'void* __builtin_memset(void*, int, long unsigned int)' offset 4 is out of the bounds [0, 0] [-Warray-bounds=] 952 | *__first = __tmp; ``` Also use `iota` to generate contiguous indices. # Tests ## `test_simd.cpp` GCC doesn't acknowledge that `simd v[i] = x` for all `i = 0 ... len(v)` initializes `v`. Force initialization to zero. ## `test_network_generation.cpp` Compare across signs ## `test_distributed_for_each.cpp` Compare across signs; resolve by casting to correct type.    ## `test_range.cpp` GCC really turns the dial up to 11 here, complaining about oob access in ``` char str[] = "howdy"; auto rg = range_n(str, strlen(str)); // NOTE: Putting 5 here is OK, so is sizeof(str) - 1 util::sort(rg); ``` Same happens with `std::sort(str, str + strlen(str))`, so I am pretty sure this is not a real thing. For reference: ``` /Users/hater/src/arbor/test/unit/test_range.cpp: In member function 'virtual void range_sort_Test::TestBody()': /Users/hater/src/arbor/test/unit/test_range.cpp:446:14: note: at offset [8, 4611686018427387903] into object 'cstr' of size 6 446 | char cstr[] = "howdy"; | ^~~~ In function 'std::_Require >, std::is_move_constructible<_Tp>, std::is_move_assignable<_Tp> > std::swap(_Tp&, _Tp&) [with _Tp = char]', inlined from 'void std::iter_swap(_ForwardIterator1, _ForwardIterator2) [with _ForwardIterator1 = char*; _ForwardIterator2 = char*]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algobase.h:185:11, inlined from 'void std::__move_median_to_first(_Iterator, _Iterator, _Iterator, _Iterator, _Compare) [with _Iterator = char*; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algo.h:91:20, inlined from '_RandomAccessIterator std::__unguarded_partition_pivot(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = char*; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algo.h:1855:34, inlined from 'void std::__introsort_loop(_RandomAccessIterator, _RandomAccessIterator, _Size, _Compare) [with _RandomAccessIterator = char*; _Size = long int; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algo.h:1889:38, inlined from 'void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = char*; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algo.h:1905:25, inlined from 'void std::__sort(_RandomAccessIterator, _RandomAccessIterator, _Compare) [with _RandomAccessIterator = char*; _Compare = __gnu_cxx::__ops::_Iter_less_iter]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algo.h:1900:5, inlined from 'void std::sort(_RAIter, _RAIter) [with _RAIter = char*]' at /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/stl_algo.h:4771:18, inlined from 'std::enable_if_t<((bool)(! std::is_const::reference>::value))> arb::util::sort(Seq&&) [with Seq = range]' at /Users/hater/src/arbor/arbor/util/rangeutil.hpp:154:14, inlined from 'virtual void range_sort_Test::TestBody()' at /Users/hater/src/arbor/test/unit/test_range.cpp:448:19: /opt/homebrew/Cellar/gcc/14.2.0/include/c++/14/bits/move.h:223:11: warning: array subscript [8, 4611686018427387903] is outside array bounds of 'char [6]' [-Warray-bounds=] 223 | __b = _GLIBCXX_MOVE(__tmp); | ^ /Users/hater/src/arbor/test/unit/test_range.cpp: In member function 'virtual void range_sort_Test::TestBody()': /Users/hater/src/arbor/test/unit/test_range.cpp:446:14: note: at offset [8, 4611686018427387903] into object 'cstr' of size 6 446 | char cstr[] = "howdy"; ``` I am still going to 'fix' this, since I am for warning free code and don't want to annotate with a pragma. # General Remove spurious includes. --------- Co-authored-by: boeschf <48126478+boeschf@users.noreply.github.com> --- arbor/tree.cpp | 28 ++++---- arbor/tree.hpp | 4 -- arbor/util/range.hpp | 8 +-- .../test_distributed_for_each.cpp | 13 ++-- .../test_network_generation.cpp | 8 +-- test/unit/common.hpp | 1 + test/unit/test_range.cpp | 64 ++++++++----------- test/unit/test_simd.cpp | 4 +- 8 files changed, 55 insertions(+), 75 deletions(-) diff --git a/arbor/tree.cpp b/arbor/tree.cpp index 3ae342dff..140730357 100644 --- a/arbor/tree.cpp +++ b/arbor/tree.cpp @@ -29,7 +29,7 @@ tree::tree(std::vector parent_index) { parents_[0] = no_parent; // compute offsets into children_ array - arb::util::make_partition(child_index_, child_count(parents_)); + util::make_partition(child_index_, child_count(parents_)); std::vector pos(parents_.size(), 0); for (auto i = 1u; i < parents_.size(); ++i) { @@ -199,11 +199,9 @@ tree::iarray tree::select_new_root(int_type root) { } // maps new indices to old indices - iarray indices (num_nodes); - // fill array with indices - for (auto i: make_span(num_nodes)) { - indices[i] = i; - } + iarray indices(num_nodes); + std::iota(indices.begin(), indices.end(), 0); + // perform sort by depth index to get the permutation std::sort(indices.begin(), indices.end(), [&](auto i, auto j){ if (reduced_depth[i] != reduced_depth[j]) { @@ -214,16 +212,12 @@ tree::iarray tree::select_new_root(int_type root) { } return depth[i] < depth[j]; }); - // maps old indices to new indices - iarray indices_inv (num_nodes); - // fill array with indices - for (auto i: make_span(num_nodes)) { - indices_inv[i] = i; - } - // perform sort - std::sort(indices_inv.begin(), indices_inv.end(), [&](auto i, auto j){ - return indices[i] < indices[j]; - }); + + // inverse permutation + iarray indices_inv(num_nodes, 0); + std::iota(indices_inv.begin(), indices_inv.end(), 0); + std::sort(indices_inv.begin(), indices_inv.end(), + [&](auto i, auto j){ return indices[i] < indices[j]; }); // translate the parent vetor to new indices for (auto i: make_span(num_nodes)) { @@ -241,7 +235,7 @@ tree::iarray tree::select_new_root(int_type root) { // recompute the children array memory::copy(new_parents, parents_); - arb::util::make_partition(child_index_, child_count(parents_)); + util::make_partition(child_index_, child_count(parents_)); std::vector pos(parents_.size(), 0); for (auto i = 1u; i < parents_.size(); ++i) { diff --git a/arbor/tree.hpp b/arbor/tree.hpp index 137598318..75b6fb2f8 100644 --- a/arbor/tree.hpp +++ b/arbor/tree.hpp @@ -2,16 +2,12 @@ #include #include -#include -#include #include #include #include -#include "memory/memory.hpp" #include "util/rangeutil.hpp" -#include "util/span.hpp" namespace arb { diff --git a/arbor/util/range.hpp b/arbor/util/range.hpp index 3a7257b7c..d01ce3585 100644 --- a/arbor/util/range.hpp +++ b/arbor/util/range.hpp @@ -158,10 +158,9 @@ template auto canonical_view(Seq&& s) { using std::begin; using std::end; - - return make_range( - make_sentinel_iterator(begin(s), end(s)), - make_sentinel_end(begin(s), end(s))); + auto b = begin(s); + auto e = end(s); + return make_range(make_sentinel_iterator(b, e), make_sentinel_end(b, e)); } // Strictly evaluate end point in sentinel-terminated range and present as a range over @@ -171,7 +170,6 @@ template auto strict_view(Seq&& s) { using std::begin; using std::end; - auto b = begin(s); auto e = end(s); return make_range(b, b==e? b: std::next(util::upto(b, e))); diff --git a/test/unit-distributed/test_distributed_for_each.cpp b/test/unit-distributed/test_distributed_for_each.cpp index 5c545e8c5..b9ca83b22 100644 --- a/test/unit-distributed/test_distributed_for_each.cpp +++ b/test/unit-distributed/test_distributed_for_each.cpp @@ -40,10 +40,9 @@ TEST(distributed_for_each, one_zero) { for (int i = 0; i < rank; ++i) { data.push_back(rank); } auto sample = [&](const util::range& range) { - const auto origin_rank = range.empty() ? 0 : range.front(); - + std::size_t origin_rank = range.empty() ? 0 : range.front(); EXPECT_EQ(origin_rank, range.size()); - for (const auto& value: range) { EXPECT_EQ(value, origin_rank); } + for (std::size_t value: range) { EXPECT_EQ(value, origin_rank); } ++call_count; }; @@ -71,14 +70,14 @@ TEST(distributed_for_each, multiple) { auto sample = [&](const util::range& range_1, const util::range& range_2, const util::range*>& range_3) { - const auto origin_rank = range_1.empty() ? 0 : range_1.front(); + std::size_t origin_rank = range_1.empty() ? 0 : range_1.front(); EXPECT_EQ(origin_rank + 1, range_1.size()); EXPECT_EQ(range_2.size(), 2 * range_1.size()); EXPECT_EQ(range_3.size(), 3 * range_1.size()); - for (const auto& value: range_1) { EXPECT_EQ(value, origin_rank); } - for (const auto& value: range_2) { EXPECT_EQ(value, double(origin_rank)); } - for (const auto& value: range_3) { EXPECT_EQ(value, std::complex(origin_rank)); } + for (std::size_t value: range_1) { EXPECT_EQ(value, origin_rank); } + for (auto value: range_2) { EXPECT_EQ(value, double(origin_rank)); } + for (auto value: range_3) { EXPECT_EQ(value, std::complex(origin_rank)); } ++call_count; }; diff --git a/test/unit-distributed/test_network_generation.cpp b/test/unit-distributed/test_network_generation.cpp index f7e3b55fe..1a6e09474 100644 --- a/test/unit-distributed/test_network_generation.cpp +++ b/test/unit-distributed/test_network_generation.cpp @@ -125,8 +125,8 @@ TEST(network_generation, all) { } for (const auto& group: decomp.groups()) { - const auto num_dest = group.kind == cell_kind::spike_source ? 0 : 1; - for (const auto gid: group.gids) { + std::size_t num_dest = group.kind == cell_kind::spike_source ? 0 : 1; + for (std::size_t gid: group.gids) { EXPECT_EQ(connections_by_dest[gid].size(), num_cells * num_dest); } } @@ -141,7 +141,7 @@ TEST(network_generation, cable_only) { const auto weight = 2.0; const auto delay = 3.0; - const auto num_cells = 3 * num_ranks; + const std::size_t num_cells = 3 * num_ranks; auto rec = network_test_recipe(num_cells, selection, weight, delay); @@ -161,7 +161,7 @@ TEST(network_generation, cable_only) { for (const auto gid: group.gids) { // Only one third is a cable cell EXPECT_EQ(connections_by_dest[gid].size(), - group.kind == cell_kind::cable ? num_cells / 3 : 0); + group.kind == cell_kind::cable ? num_cells / 3 : 0); } } } diff --git a/test/unit/common.hpp b/test/unit/common.hpp index e02b9df20..23a08da97 100644 --- a/test/unit/common.hpp +++ b/test/unit/common.hpp @@ -12,6 +12,7 @@ #include #include #include +#include #include diff --git a/test/unit/test_range.cpp b/test/unit/test_range.cpp index 02e889fc0..de86ef7ec 100644 --- a/test/unit/test_range.cpp +++ b/test/unit/test_range.cpp @@ -14,7 +14,6 @@ #include "util/meta.hpp" #include "util/range.hpp" #include "util/rangeutil.hpp" -#include "util/sentinel.hpp" #include "util/transform.hpp" #include "common.hpp" @@ -442,41 +441,34 @@ struct foo { }; TEST(range, sort) { - char cstr[] = "howdy"; - - auto cstr_range = util::make_range(std::begin(cstr), null_terminated); - - // Alas, no forward_iterator sort yet, so make a strict (non-sentinel) - // range to sort on below - - // simple sort - util::sort(util::strict_view(cstr_range)); - EXPECT_EQ("dhowy"s, cstr); - - // reverse sort by transform c to -c - util::sort_by(util::strict_view(cstr_range), [](char c) { return -c; }); - EXPECT_EQ("ywohd"s, cstr); - - // stable sort: move capitals to front, numbers to back - auto rank = [](char c) { - return std::isupper(c)? 0: std::isdigit(c)? 2: 1; - }; - - char mixed[] = "t5hH4E3erLL2e1O"; - auto mixed_range = util::make_range(std::begin(mixed), null_terminated); - - util::stable_sort_by(util::strict_view(mixed_range), rank); - EXPECT_EQ("HELLOthere54321"s, mixed); - - - // sort with user-provided less comparison function - - std::vector X = {{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}}; - - util::sort(X, [](const foo& l, const foo& r) {return l.y{{5, 0}, {4, 1}, {3, 2}, {2, 3}, {1, 4}, {0, 5}})); - util::sort(X, [](const foo& l, const foo& r) {return l.x{{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}})); + { + // simple sort + char cstr[] = "howdy"; + util::sort(util::range_n(cstr, 5)); + EXPECT_EQ("dhowy"s, cstr); + } + { + // reverse sort by transform c to -c + char cstr[] = "howdy"; + util::sort_by(util::range_n(cstr, 5), + [](char c) { return -c; }); + EXPECT_EQ("ywohd"s, cstr); + } + { + // stable sort: move capitals to front, numbers to back + char mixed[] = "t5hH4E3erLL2e1O"; + util::stable_sort_by(util::strict_view(util::make_range(std::begin(mixed), null_terminated)), + [](char c) { return std::isupper(c)? 0: std::isdigit(c)? 2: 1; }); + EXPECT_EQ("HELLOthere54321"s, mixed); + } + { + // sort with user-provided less comparison function + std::vector X = {{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}}; + util::sort(X, [](const foo& l, const foo& r) {return l.y{{5, 0}, {4, 1}, {3, 2}, {2, 3}, {1, 4}, {0, 5}})); + util::sort(X, [](const foo& l, const foo& r) {return l.x{{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}})); + } } TEST(range, sum) { diff --git a/test/unit/test_simd.cpp b/test/unit/test_simd.cpp index 87ea359cb..0a6e116c0 100644 --- a/test/unit/test_simd.cpp +++ b/test/unit/test_simd.cpp @@ -360,13 +360,13 @@ TYPED_TEST_P(simd_value, comparison) { for (unsigned i = 0; ib; From 625237a4872d0a4ceeac09641b709bd021e94214 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Thu, 24 Oct 2024 14:41:29 +0200 Subject: [PATCH 19/30] Add iostream to forest (#2423) Tiny oversight. --- arbor/backends/gpu/forest.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/arbor/backends/gpu/forest.cpp b/arbor/backends/gpu/forest.cpp index cc146af02..ec3c30e68 100644 --- a/arbor/backends/gpu/forest.cpp +++ b/arbor/backends/gpu/forest.cpp @@ -1,5 +1,6 @@ #include "backends/gpu/forest.hpp" #include "util/span.hpp" +#include namespace arb { namespace gpu { From 6de5e299b7e6b38eeae378a99d92ebec86390a5c Mon Sep 17 00:00:00 2001 From: Han Lu <11597940+ErbB4@users.noreply.github.com> Date: Mon, 11 Nov 2024 10:53:15 +0100 Subject: [PATCH 20/30] Update plasticity.rst --- doc/tutorial/plasticity.rst | 100 ++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index ea6aa58bc..303006e53 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -32,20 +32,20 @@ The Python file ``01-setup.py`` is the scaffolding we will build our simulation around and thus contains some passages that might seem redundant now, but will be helpful in later steps. -We begin by defining the global settings +We begin by defining the global settings: .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python :lines: 7-15 -- ``N`` is the cell count of the simulation -- ``T`` is the total runtime of the simulation in ``ms`` -- ``t_interval`` defines the _interval_ such that the simulation is advance in +- ``N`` is the cell count of the simulation. +- ``T`` is the total runtime of the simulation in ``ms``. +- ``t_interval`` defines the _interval_ such that the simulation advances in discrete steps ``[0, 1, 2, ...] t_interval``. Later, this will be the timescale of plasticity. -- ``dt`` is the numerical timestep on which cells evolve +- ``dt`` is the numerical timestep on which cells evolve. -These parameters are used here +These parameters are used here: .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python @@ -53,26 +53,26 @@ These parameters are used here where we run the simulation in increments of ``t_interval``. -Back to the recipe; we set a prototypical cell +Back to the recipe, we set a prototypical cell: .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python :lines: 23 -and deliver it for all ``gid`` s +and deliver it for all ``gid``: .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python :lines: 42-43 -Also, each cell has an event generator attached +Also, each cell has an event generator attached, using a Poisson point process seeded with the cell's ``gid``. .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python :lines: 33-40 -using a Poisson point process seeded with the cell's ``gid``. All other -parameters are set in the constructor + +All other parameters are set in the constructor: .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python @@ -80,7 +80,7 @@ parameters are set in the constructor We also proceed to add spike recording and generate plots using a helper function ``plot_spikes`` from ``util.py``. You can skip the following details -for now and come back later if you are interested how it works. Rates are +for now and come back later if you are interested in how it works. Rates are computed by binning spikes into ``t_interval`` and the neuron id; the mean rate is the average across the neurons smoothed using a Savitzky-Golay filter (``scipy.signal.savgol_filter``). @@ -91,7 +91,7 @@ We plot per-neuron and mean rates: :width: 400 :align: center -We also generate raster plots via ``scatter``. +We also generate raster plots via ``scatter``: .. figure:: ../../python/example/plasticity/01-raster.svg :width: 400 @@ -103,7 +103,7 @@ A Randomly Wired Network We use inheritance to derive a new recipe that contains all the functionality of the ```unconnected`` recipe. We then add a random connectivity matrix during -construction, fixed connection weights, and deliver the resulting connections +construction, fix connection weights, and deliver the resulting connections via the ``connections_on`` callback, with the only extra consideration of allowing multiple connections between two neurons. @@ -114,13 +114,13 @@ incoming/outgoing connections per neuron, and the maximum for both directions :language: python :lines: 26-31 -The connection matrix is used to construct connections +The connection matrix is used to construct connections, .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python :lines: 33-38 -together with the fixed connection parameters +together with the fixed connection parameters: .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python @@ -129,31 +129,31 @@ together with the fixed connection parameters We define helper functions ``add|del_connections`` to manipulate the connection table while upholding these invariants: -- no self-connections, i.e. ``connection[i, i] == 0`` +- no self-connections, i.e., ``connection[i, i] == 0`` - ``inc[i]`` the sum of ``connections[:, i]`` -- no more incoming connections than allowed by ``max_inc``, i.e. ``inc[i] <= max_inc`` +- no more incoming connections than allowed by ``max_inc``, i.e., ``inc[i] <= max_inc`` - ``out[i]`` the sum of ``connections[i, :]`` -- no more outgoing connections than allowed by ``max_out``, i.e. ``out[i] <= max_out`` +- no more outgoing connections than allowed by ``max_out``, i.e., ``out[i] <= max_out`` -These methods return ``True`` on success and ``False`` otherwise +These methods return ``True`` on success and ``False`` otherwise. .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python :lines: 40-54 -Both are used in ``rewire`` to produce a random connection matrix +Both are used in ``rewire`` to produce a random connection matrix. .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python :lines: 56-65 -We then proceed to run the simulation +We then proceed to run the simulation: .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python :lines: 68-79 - and plot the results as before + and plot the results as before: .. figure:: ../../python/example/plasticity/02-rates.svg :width: 400 @@ -181,34 +181,34 @@ which is used to determine the creation or destruction of synaptic connections v .. math:: - \frac{dC}{dt} = \alpha(\nu - \nu^*) + \frac{dC}{dt} = \alpha(\nu - \nu^*). -Thus we need to add some extra information to our simulation; namely the +Thus we need to add some extra information to our simulation, namely the setpoint :math:`\nu^*_i` for each neuron :math:`i` and the sensitivity parameter :math:`\alpha`. We will also use a simplified version of the differential equation above, namely adding/deleting exactly one connection if the difference of observed to desired spiking frequency exceeds :math:`\pm\alpha`. This is both for simplicity and to avoid sudden changes in the network structure. -As before, we set up global parameters +As before, we set up global parameters: .. literalinclude:: ../../python/example/plasticity/homeostasis.py :language: python :lines: 10-24 -and prepare our simulation +and prepare our simulation: .. literalinclude:: ../../python/example/plasticity/homeostasis.py :language: python :lines: 37-39 -Note that our new recipe is almost unaltered from the random network +Note that our new recipe is almost unaltered from the random network. .. literalinclude:: ../../python/example/plasticity/homeostasis.py :language: python :lines: 27-33 -all changes are contained to the way we run the simulation. To add a further +All changes are contained in the way we run the simulation. To add a further interesting feature, we skip the rewiring for the first half of the simulation. The initial network is unconnected, but could be populated randomly (or any other way) if desired by calling ``self.rewire()`` in the constructor of @@ -233,57 +233,57 @@ recipe: Important caveats: - - without ``update``, changes to the recipe have no effect - - vice versa ``update`` has no effect if the recipe doesn't return different - data than before + - without ``update``, changes to the recipe have no effect. + - vice versa ``update`` has no effect if the recipe doesn't return a different + data than before. - ``update`` will delete all existing connections and their parameters, so - all connections to be kept must be explicitly re-instantiated - - ``update`` will **not** delete synapses or their state, e.g. ODEs will - still be integrated even if not connected and currents might be produced + all connections to be kept must be explicitly re-instantiated. + - ``update`` will **not** delete synapses or their state, e.g., ODEs will + still be integrated even if not connected and currents might be produced; - neither synapses/targets nor detectors/sources can be altered. Create all endpoints up front. - - only the network is updated (this might change in future versions!) + - only the network is updated (this might change in future versions!). - be very aware that ``connections_on`` might be called in arbitrary order - and by multiples (potentially different) threads and processes! This + and by multiple (potentially different) threads and processes! This requires some thought and synchronization when dealing with random numbers and updating data *inside* ``connections_on``. -Changes are based on the difference of current rate we compute from the spikes -during the last interval +Changes are based on the difference from the current rate we compute from the spikes +during the last interval, .. literalinclude:: ../../python/example/plasticity/homeostasis.py :language: python :lines: 49-54 -and the setpoint times the sensitivity +and the setpoint times the sensitivity. .. literalinclude:: ../../python/example/plasticity/homeostasis.py :language: python :lines: 55 Then, each potential pairing of target and source is checked in random -order for whether adding or removing a connection is required +order for whether adding or removing a connection is required: .. literalinclude:: ../../python/example/plasticity/homeostasis.py :language: python :lines: 59-68 If we find an option to fulfill that requirement, we do so and proceed to the -next target. The randomization is important here, espcially for adding -connections as to avoid biases, in particular when there are too few eglible +next target. The randomization is important here, especially for adding +connections to avoid biases, in particular when there are too few eligible connection partners. The ``randrange`` function produces a shuffled range ``[0, N)``. We leverage the helper functions from the random network recipe to manipulate the connection table, see the discussion above. Finally, we plot spiking rates as before; the jump at the half-way point is the effect of the plasticity activating after which each neuron moves to the -setpoint +setpoint. .. figure:: ../../python/example/plasticity/03-rates.svg :width: 400 :align: center -and the resulting network +And the resulting network: .. figure:: ../../python/example/plasticity/03-final-graph.svg :width: 400 @@ -294,14 +294,14 @@ Conclusion This concludes our foray into structural plasticity. While the building blocks -- an explicit representation of the connections, -- running the simulation in batches (and calling ``simulation.update``!) -- a rule to derive the change +- an explicit representation of the connections; +- running the simulation in batches (and calling ``simulation.update``!); +- a rule to derive the change; will likely be the same in all approaches, the concrete implementation of the rules is the centerpiece here. For example, although spike rate homeostasis was -used here, mechanism states and ion concentrations --- extracted via the normal -probe and sample interface --- can be leveraged to build rules. Due to the way +used here, mechanism states and ion concentrations---extracted via the normal +probe and sample interface---can be leveraged to build rules. Due to the way the Python interface is required to link to measurements, using the C++ API for access to streaming spike and measurement data could help to address performance issues. Plasticity as shown also meshes with the high-level connection builder. From c6725f9eb1b5b56509fd41b744523f4c646141d8 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Thu, 3 Oct 2024 11:31:44 +0200 Subject: [PATCH 21/30] Add tutorial --- doc/tutorial/index.rst | 1 + doc/tutorial/plasticity.rst | 76 + python/example/plasticity/03-rates.svg | 2011 +++++++++++++++++++ python/example/plasticity/homeostasis.py | 115 ++ python/example/plasticity/random_network.py | 96 + python/example/plasticity/unconnected.py | 82 + 6 files changed, 2381 insertions(+) create mode 100644 doc/tutorial/plasticity.rst create mode 100644 python/example/plasticity/03-rates.svg create mode 100644 python/example/plasticity/homeostasis.py create mode 100644 python/example/plasticity/random_network.py create mode 100644 python/example/plasticity/unconnected.py diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst index 8ef283e2d..3adb1567c 100644 --- a/doc/tutorial/index.rst +++ b/doc/tutorial/index.rst @@ -78,6 +78,7 @@ Advanced :maxdepth: 1 nmodl + plasticity Demonstrations -------------- diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst new file mode 100644 index 000000000..bc9a060d0 --- /dev/null +++ b/doc/tutorial/plasticity.rst @@ -0,0 +1,76 @@ +.. _tutorial_plasticity: + +In this tutorial, we are going to demonstrate how a network can be built using +plasticity and homeostatic connection rules. Despite not playing towards Arbor's +strengths, we choose a LIF (Leaky Integrate and Fire) neuron model, as we are +primarily interested in examining the required scaffolding. + +We will build up the simulation in stages, starting with an unconnected network +and finishing with a dynamically built connectome. + +An Unconnected Network +---------------------- + +Consider a collection of ``N`` LIF cells. This will be the starting point for +our exploration. For now, we set up each cell with a Poissonian input such that +it will produce spikes periodically at a low frequency. + +The Python file ``01-setup.py`` is the scaffolding we will build our simulation +around and thus contains some passages that might seem redundant now, but will +be helpful in later steps. + +We begin by defining the global settings + +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 9-17 + +- ``T`` is the total runtime of the simulation in ``ms`` +- ``dT`` defines the _interval_ such that the simulation is advance in discrete + steps ``[0, dT, 2 dT, ..., T]``. Later, this will be the timescale of + plasticity. +- ``dt`` is the numerical timestep on which cells evolve + +These parameters are used here + +.. literalinclude:: ../../python/example/plasticity/step-01.py + :language: python + +Next, we define the ``recipe`` used to describe the 'network' which is currently +unconnected. + +We also proceed to add spike recording and generating raster/rate plots. + +A Randomly Wired Network +------------------------ + +We use inheritance to derive a new recipe that contains all the functionality of +the ``unconnected`` recipe. We add a random connectivity matrix during +construction, fixed connection weights, and deliver the resulting connections +via the callback, with the only extra consideration of allowing multiple +connections between two neurons. + +Adding Homeostasis +------------------ + +Under the homeostatic model, each cell was a setpoint for the firing rate :math:`\nu^*` +which is used to determine the creation or destruction of synaptic connections via + +.. math:: + + \frac{dC}{dt} = \alpha(\nu - \nu^*) + +Thus we need to add some extra information to our simulation; namely the +setpoint :math:`\nu^*_i` for each neuron :math:`i` and the sensitivity parameter +:math:`\alpha`. We will also use a simplified version of the differential +equation above, namely adding/deleting exactly one connection if the difference +of observed to desired spiking frequency exceeds :math:`\pm\alpha`. This is both +for simplicity and to avoid sudden changes in the network structure. + +We do this by tweaking the connection table in between calls to ``run``. In +particular, we walk the potential pairings of targets and sources in random +order and check whether the targets requires adding or removing connections. If +we find an option to fulfill that requirement, we do so and proceed to the next +target. The randomization is important here, espcially for adding connections as +to avoid biases, in particular when there are too few eglible connection +partners. diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg new file mode 100644 index 000000000..c0cbdde28 --- /dev/null +++ b/python/example/plasticity/03-rates.svg @@ -0,0 +1,2011 @@ + + + + + + + + 2024-10-02T20:06:06.006052 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py new file mode 100644 index 000000000..b296c862d --- /dev/null +++ b/python/example/plasticity/homeostasis.py @@ -0,0 +1,115 @@ +#!/usr/bin/env python3 + +import arbor as A +from arbor import units as U +from typing import Any +import matplotlib.pyplot as plt +from scipy.signal import savgol_filter +import numpy as np + +from random_network import random_network + +# global parameters +# total runtime [ms] +T = 10000 +# one interval [ms] +dT = 100 +# number of intervals +nT = int((T + dT - 1)//dT) +# numerical time step [ms] +dt = 0.1 + +def randrange(n: int): + res = np.arange(n, dtype=int) + np.random.shuffle(res) + return res + +np.random.seed = 23 + +class homeostatic_network(random_network): + + def __init__(self, N) -> None: + super().__init__(N) + self.max_inc = 8 + self.max_out = 8 + # setpoint rate in kHz + self.setpoint = 0.1 + # sensitivty towards deviations from setpoint + self.alpha = 200 + + +if __name__ == "__main__": + rec = homeostatic_network(10) + sim = A.simulation(rec) + sim.record(A.spike_recording.all) + + print("Initial network:") + print(rec.inc) + print(rec.out) + print(rec.connections) + + t = 0 + while t < T: + sim.run((t + dT) * U.ms, dt * U.ms) + if t < T/2: + t += dT + continue + n = rec.num_cells() + rates = np.zeros(n) + for (gid, _), time in sim.spikes(): + if time < t: + continue + rates[gid] += 1 + rates /= dT # kHz + dC = ((rec.setpoint - rates)*rec.alpha).astype(int) + unchangeable = set() + added = [] + deled = [] + for tgt in randrange(n): + if dC[tgt] == 0: + continue + for src in randrange(n): + if dC[tgt] > 0 and rec.add_connection(src, tgt): + added.append((src, tgt)) + break + elif dC[tgt] < 0 and rec.del_connection(src, tgt): + deled.append((src, tgt)) + break + unchangeable.add(tgt) + sim.update(rec) + print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") + t += dT + + print("Final network:") + print(rec.inc) + print(rec.out) + print(rec.connections) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(nT, rec.num_cells())) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // dT) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel('Time $(t/ms)$') + ax.set_ylabel('GID') + ax.set_xlim(0, T) + fg.savefig('03-raster.pdf') + + fg, ax = plt.subplots() + ax.plot(np.arange(nT), rates/dT) + ax.plot(np.arange(nT), savgol_filter(rates.mean(axis=1)/dT, window_length=5, polyorder=2), color='0.8', lw=4, label='Mean rate') + ax.axhline(0.1, label='Setpoint', lw=2, c='0.4') + ax.legend() + ax.set_xlabel('Interval') + ax.set_ylabel('Rate $(kHz)$') + ax.set_xlim(0, nT) + fg.savefig('03-rates.pdf') + fg.savefig('03-rates.png') + fg.savefig('03-rates.svg') diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py new file mode 100644 index 000000000..921a1598a --- /dev/null +++ b/python/example/plasticity/random_network.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python3 + +import arbor as A +from arbor import units as U +from typing import Any +import matplotlib.pyplot as plt +import numpy as np + +from unconnected import unconnected + +# global parameters +# total runtime [ms] +T = 100 +# one interval [ms] +dT = 10 +# number of intervals +nT = int((T + dT - 1)//dT) +# numerical time step [ms] +dt = 0.1 + +class random_network(unconnected): + + def __init__(self, N) -> None: + super().__init__(N) + self.syn_weight = 80 + self.syn_delay = 0.5 * U.ms + self.max_inc = 4 + self.max_out = 4 + # format [to, from] + self.connections = np.zeros(shape=(N, N), dtype=np.uint8) + self.inc = np.zeros(N, np.uint8) + self.out = np.zeros(N, np.uint8) + + def connections_on(self, gid: int): + return [A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) + for source in range(self.N) + for _ in range(self.connections[gid, source]) + ] + + def add_connection(self, src: int, tgt: int) -> bool: + if tgt == src or self.inc[tgt] >= self.max_inc or self.out[src] >= self.max_out: + return False + self.inc[tgt] += 1 + self.out[src] += 1 + self.connections[tgt, src] += 1 + return True + + def del_connection(self, src: int, tgt: int) -> bool: + if tgt == src or self.connections[tgt, src] <= 0: + return False + self.inc[tgt] -= 1 + self.out[src] -= 1 + self.connections[tgt, src] -= 1 + return True + + def rewire(self): + tries = self.N*self.N*self.max_inc*self.max_out + while tries > 0 and self.inc.sum() < self.N*self.max_inc and self.out.sum() < self.N*self.max_out: + src, tgt = np.random.randint(self.N, size=2, dtype=int) + self.add_connection(src, tgt) + tries -= 1 + + +if __name__ == "__main__": + rec = random(10) + rec.rewire() + sim = A.simulation(rec) + sim.record(A.spike_recording.all) + t = 0 + while t < T: + t += dT + sim.run(t * U.ms, dt * U.ms) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(nT, rec.num_cells())) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // dT) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel('Time $(t/ms)$') + ax.set_ylabel('GID') + ax.set_xlim(0, T) + fg.savefig('02-raster.pdf') + + fg, ax = plt.subplots() + ax.plot(np.arange(nT), rates) + ax.set_xlabel('Interval') + ax.set_ylabel('Rate $(kHz)$') + ax.set_xlim(0, nT) + fg.savefig('02-rates.pdf') diff --git a/python/example/plasticity/unconnected.py b/python/example/plasticity/unconnected.py new file mode 100644 index 000000000..78b848ca7 --- /dev/null +++ b/python/example/plasticity/unconnected.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 + +import arbor as A +from arbor import units as U +from typing import Any +import matplotlib.pyplot as plt +import numpy as np + +# global parameters +# total runtime [ms] +T = 100 +# one interval [ms] +dT = 10 +# number of intervals +nT = int((T + dT - 1)//dT) +# numerical time step [ms] +dt = 0.1 + +class unconnected(A.recipe): + + def __init__(self, N) -> None: + super().__init__() + self.N = N + # Cell prototype + self.cell = A.lif_cell("src", "tgt") + # random seed [0, 100] + self.seed = 42 + # event generator parameters + self.gen_weight = 20 + self.gen_freq = 1 * U.kHz + + def num_cells(self) -> int: + return self.N + + def event_generators(self, gid: int) -> list[Any]: + return [A.event_generator("tgt", + self.gen_weight, + A.poisson_schedule(freq=self.gen_freq, + seed=self.cell_seed(gid)))] + + def cell_description(self, gid: int) -> Any: + return self.cell + + def cell_kind(self, gid: int) -> A.cell_kind: + return A.cell_kind.lif + + def cell_seed(self, gid): + return self.seed + gid*100 + +if __name__ == "__main__": + rec = unconnected(10) + sim = A.simulation(rec) + sim.record(A.spike_recording.all) + + t = 0 + while t < T: + t += dT + sim.run(t * U.ms, dt * U.ms) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(nT, rec.num_cells())) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // dT) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel('Time $(t/ms)$') + ax.set_ylabel('GID') + ax.set_xlim(0, T) + fg.savefig('01-raster.pdf') + + fg, ax = plt.subplots() + ax.plot(np.arange(nT), rates) + ax.set_xlabel('Interval') + ax.set_ylabel('Rate $(kHz)$') + ax.set_xlim(0, nT) + fg.savefig('01-rates.pdf') From 90f43db1551d59b626c725f0b52c76a0cef98994 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 10:43:17 +0200 Subject: [PATCH 22/30] Begin code snippets. --- doc/tutorial/plasticity.rst | 98 +- python/example/plasticity/01-raster.svg | 1863 ++++++++++++++ python/example/plasticity/01-rates.svg | 2107 ++++++++++++++++ python/example/plasticity/03-rates.svg | 2404 ++++++++++--------- python/example/plasticity/homeostasis.py | 54 +- python/example/plasticity/random_network.py | 62 +- python/example/plasticity/unconnected.py | 62 +- python/example/plasticity/util.py | 45 + 8 files changed, 5404 insertions(+), 1291 deletions(-) create mode 100644 python/example/plasticity/01-raster.svg create mode 100644 python/example/plasticity/01-rates.svg create mode 100644 python/example/plasticity/util.py diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index bc9a060d0..d10c079c6 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -23,32 +23,110 @@ We begin by defining the global settings .. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python - :lines: 9-17 + :lines: 7-15 +- ``N`` is the cell count of the simulation - ``T`` is the total runtime of the simulation in ``ms`` -- ``dT`` defines the _interval_ such that the simulation is advance in discrete - steps ``[0, dT, 2 dT, ..., T]``. Later, this will be the timescale of +- ``t_interval`` defines the _interval_ such that the simulation is advance in + discrete steps ``[0, 1, 2, ...] t_interval``. Later, this will be the timescale of plasticity. - ``dt`` is the numerical timestep on which cells evolve These parameters are used here -.. literalinclude:: ../../python/example/plasticity/step-01.py +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 52-62 + +where we run the simulation in increments of ``t_interval``. + +Back to the recipe; we set a prototypical cell + +.. literalinclude:: ../../python/example/plasticity/unconnected.py :language: python + :lines: 23 -Next, we define the ``recipe`` used to describe the 'network' which is currently -unconnected. +and deliver it for all ``gid`` s -We also proceed to add spike recording and generating raster/rate plots. +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 42-43 + +Also, each cell has an event generator attached + +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 33-40 + +using a Poisson point process seeded with the cell's ``gid``. All other +parameters are set in the constructor + +.. literalinclude:: ../../python/example/plasticity/unconnected.py + :language: python + :lines: 19-28 + +We also proceed to add spike recording and generate plots using a helper +function ``plot_spikes`` from ``util.py``. You can skip the following details +for now and come back later if you are interested how it works. We generate +raster plots via ``scatter``. Rates are computed by binning spikes into +``t_interval`` and the neuron id; the mean rate is the average across the +neurons smoothed using a Savitzky-Golay filter (``scipy.signal.savgol_filter``). +We plot per-neuron and mean rates. A Randomly Wired Network ------------------------ We use inheritance to derive a new recipe that contains all the functionality of -the ``unconnected`` recipe. We add a random connectivity matrix during +the ```unconnected`` recipe. We then add a random connectivity matrix during construction, fixed connection weights, and deliver the resulting connections -via the callback, with the only extra consideration of allowing multiple -connections between two neurons. +via the ``connections_on`` callback, with the only extra consideration of +allowing multiple connections between two neurons. + +In detail, the recipe stores the connection matrix, the current +incoming/outgoing connections per neuron, and the maximum for both directions + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 26-31 + +The connection matrix is used to construct connections + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 33-38 + +together with the fixed connection parameters + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 24-25 + +We define helper functions ``add|del_connections`` to manipulate the connection +table while upholding these invariants: + +- no self-connections, i.e. ``connection[i, i] == 0`` +- ``inc[i]`` the sum of ``connections[:, i]`` +- no more incoming connections than allowed by ``max_inc``, i.e. ``inc[i] <= max_inc`` +- ``out[i]`` the sum of ``connections[i, :]`` +- no more outgoing connections than allowed by ``max_out``, i.e. ``out[i] <= max_out`` + +These methods return ``True`` on success and ``False`` otherwise + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 40-54 + +Both are used in ``rewire`` to produce a random connection matrix + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 56-65 + +We then proceed to run the simulation and plot the results as before + +.. literalinclude:: ../../python/example/plasticity/random_network.py + :language: python + :lines: 68-79 Adding Homeostasis ------------------ diff --git a/python/example/plasticity/01-raster.svg b/python/example/plasticity/01-raster.svg new file mode 100644 index 000000000..7b4af213b --- /dev/null +++ b/python/example/plasticity/01-raster.svg @@ -0,0 +1,1863 @@ + + + + + + + + 2024-10-08T09:16:17.177252 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/01-rates.svg b/python/example/plasticity/01-rates.svg new file mode 100644 index 000000000..67fd888a3 --- /dev/null +++ b/python/example/plasticity/01-rates.svg @@ -0,0 +1,2107 @@ + + + + + + + + 2024-10-08T09:16:17.364183 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg index c0cbdde28..c6c97fc74 100644 --- a/python/example/plasticity/03-rates.svg +++ b/python/example/plasticity/03-rates.svg @@ -6,7 +6,7 @@ - 2024-10-02T20:06:06.006052 + 2024-10-03T11:36:12.842254 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,7 +82,7 @@ z - + @@ -122,7 +122,7 @@ z - + @@ -157,7 +157,7 @@ z - + @@ -203,7 +203,7 @@ z - + @@ -258,7 +258,7 @@ z - + @@ -445,12 +445,12 @@ z - - + @@ -475,12 +475,12 @@ z - + - + - + - + @@ -534,12 +534,12 @@ z - + - + @@ -550,12 +550,12 @@ z - + - + @@ -563,7 +563,89 @@ z - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -678,1143 +760,1143 @@ z - - - - - - - - - - + - + - + - + - + - + - + - + - + + + + + + + + + + - - - + - + - + - - + - + - + + diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py index b296c862d..c15970340 100644 --- a/python/example/plasticity/homeostasis.py +++ b/python/example/plasticity/homeostasis.py @@ -4,30 +4,30 @@ from arbor import units as U from typing import Any import matplotlib.pyplot as plt -from scipy.signal import savgol_filter import numpy as np +from util import plot_spikes from random_network import random_network # global parameters # total runtime [ms] T = 10000 # one interval [ms] -dT = 100 -# number of intervals -nT = int((T + dT - 1)//dT) +t_interval = 100 # numerical time step [ms] dt = 0.1 + def randrange(n: int): res = np.arange(n, dtype=int) np.random.shuffle(res) return res + np.random.seed = 23 -class homeostatic_network(random_network): +class homeostatic_network(random_network): def __init__(self, N) -> None: super().__init__(N) self.max_inc = 8 @@ -50,9 +50,9 @@ def __init__(self, N) -> None: t = 0 while t < T: - sim.run((t + dT) * U.ms, dt * U.ms) - if t < T/2: - t += dT + sim.run((t + t_interval) * U.ms, dt * U.ms) + if t < T / 2: + t += t_interval continue n = rec.num_cells() rates = np.zeros(n) @@ -60,8 +60,8 @@ def __init__(self, N) -> None: if time < t: continue rates[gid] += 1 - rates /= dT # kHz - dC = ((rec.setpoint - rates)*rec.alpha).astype(int) + rates /= t_interval # kHz + dC = ((rec.setpoint - rates) * rec.alpha).astype(int) unchangeable = set() added = [] deled = [] @@ -78,38 +78,14 @@ def __init__(self, N) -> None: unchangeable.add(tgt) sim.update(rec) print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") - t += dT + t += t_interval print("Final network:") print(rec.inc) print(rec.out) print(rec.connections) - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(nT, rec.num_cells())) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // dT) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel('Time $(t/ms)$') - ax.set_ylabel('GID') - ax.set_xlim(0, T) - fg.savefig('03-raster.pdf') - - fg, ax = plt.subplots() - ax.plot(np.arange(nT), rates/dT) - ax.plot(np.arange(nT), savgol_filter(rates.mean(axis=1)/dT, window_length=5, polyorder=2), color='0.8', lw=4, label='Mean rate') - ax.axhline(0.1, label='Setpoint', lw=2, c='0.4') - ax.legend() - ax.set_xlabel('Interval') - ax.set_ylabel('Rate $(kHz)$') - ax.set_xlim(0, nT) - fg.savefig('03-rates.pdf') - fg.savefig('03-rates.png') - fg.savefig('03-rates.svg') + plot_spikes( + sim, + rec.num_cells(), + ) diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py index 921a1598a..84625afee 100644 --- a/python/example/plasticity/random_network.py +++ b/python/example/plasticity/random_network.py @@ -2,40 +2,40 @@ import arbor as A from arbor import units as U -from typing import Any -import matplotlib.pyplot as plt import numpy as np +from util import plot_spikes, plot_network from unconnected import unconnected # global parameters +# cell count +N = 10 # total runtime [ms] -T = 100 +T = 1000 # one interval [ms] -dT = 10 -# number of intervals -nT = int((T + dT - 1)//dT) +t_interval = 10 # numerical time step [ms] dt = 0.1 -class random_network(unconnected): +class random_network(unconnected): def __init__(self, N) -> None: super().__init__(N) self.syn_weight = 80 self.syn_delay = 0.5 * U.ms - self.max_inc = 4 - self.max_out = 4 # format [to, from] self.connections = np.zeros(shape=(N, N), dtype=np.uint8) self.inc = np.zeros(N, np.uint8) self.out = np.zeros(N, np.uint8) + self.max_inc = 4 + self.max_out = 4 def connections_on(self, gid: int): - return [A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) - for source in range(self.N) - for _ in range(self.connections[gid, source]) - ] + return [ + A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) + for source in range(self.N) + for _ in range(self.connections[gid, source]) + ] def add_connection(self, src: int, tgt: int) -> bool: if tgt == src or self.inc[tgt] >= self.max_inc or self.out[src] >= self.max_out: @@ -54,43 +54,25 @@ def del_connection(self, src: int, tgt: int) -> bool: return True def rewire(self): - tries = self.N*self.N*self.max_inc*self.max_out - while tries > 0 and self.inc.sum() < self.N*self.max_inc and self.out.sum() < self.N*self.max_out: + tries = self.N * self.N * self.max_inc * self.max_out + while ( + tries > 0 + and self.inc.sum() < self.N * self.max_inc + and self.out.sum() < self.N * self.max_out + ): src, tgt = np.random.randint(self.N, size=2, dtype=int) self.add_connection(src, tgt) tries -= 1 if __name__ == "__main__": - rec = random(10) + rec = random_network(10) rec.rewire() sim = A.simulation(rec) sim.record(A.spike_recording.all) t = 0 while t < T: - t += dT + t += t_interval sim.run(t * U.ms, dt * U.ms) - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(nT, rec.num_cells())) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // dT) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel('Time $(t/ms)$') - ax.set_ylabel('GID') - ax.set_xlim(0, T) - fg.savefig('02-raster.pdf') - - fg, ax = plt.subplots() - ax.plot(np.arange(nT), rates) - ax.set_xlabel('Interval') - ax.set_ylabel('Rate $(kHz)$') - ax.set_xlim(0, nT) - fg.savefig('02-rates.pdf') + plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="02-") diff --git a/python/example/plasticity/unconnected.py b/python/example/plasticity/unconnected.py index 78b848ca7..e9a07ec99 100644 --- a/python/example/plasticity/unconnected.py +++ b/python/example/plasticity/unconnected.py @@ -2,22 +2,20 @@ import arbor as A from arbor import units as U -from typing import Any -import matplotlib.pyplot as plt -import numpy as np +from util import plot_spikes # global parameters +# cell count +N = 10 # total runtime [ms] -T = 100 +T = 1000 # one interval [ms] -dT = 10 -# number of intervals -nT = int((T + dT - 1)//dT) +t_interval = 10 # numerical time step [ms] dt = 0.1 -class unconnected(A.recipe): +class unconnected(A.recipe): def __init__(self, N) -> None: super().__init__() self.N = N @@ -32,51 +30,33 @@ def __init__(self, N) -> None: def num_cells(self) -> int: return self.N - def event_generators(self, gid: int) -> list[Any]: - return [A.event_generator("tgt", - self.gen_weight, - A.poisson_schedule(freq=self.gen_freq, - seed=self.cell_seed(gid)))] + def event_generators(self, gid: int): + return [ + A.event_generator( + "tgt", + self.gen_weight, + A.poisson_schedule(freq=self.gen_freq, seed=self.cell_seed(gid)), + ) + ] - def cell_description(self, gid: int) -> Any: + def cell_description(self, gid: int): return self.cell def cell_kind(self, gid: int) -> A.cell_kind: return A.cell_kind.lif - def cell_seed(self, gid): - return self.seed + gid*100 + def cell_seed(self, gid: int): + return self.seed + gid * 100 + if __name__ == "__main__": - rec = unconnected(10) + rec = unconnected(N) sim = A.simulation(rec) sim.record(A.spike_recording.all) t = 0 while t < T: - t += dT + t += t_interval sim.run(t * U.ms, dt * U.ms) - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(nT, rec.num_cells())) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // dT) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel('Time $(t/ms)$') - ax.set_ylabel('GID') - ax.set_xlim(0, T) - fg.savefig('01-raster.pdf') - - fg, ax = plt.subplots() - ax.plot(np.arange(nT), rates) - ax.set_xlabel('Interval') - ax.set_ylabel('Rate $(kHz)$') - ax.set_xlim(0, nT) - fg.savefig('01-rates.pdf') + plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="01-") diff --git a/python/example/plasticity/util.py b/python/example/plasticity/util.py new file mode 100644 index 000000000..a089fc626 --- /dev/null +++ b/python/example/plasticity/util.py @@ -0,0 +1,45 @@ +#!/usr/bin/env python3 + +import matplotlib.pyplot as plt +import numpy as np +from scipy.signal import savgol_filter + + +def plot_spikes(sim, n_cells, t_interval, T, prefix=""): + # number of intervals + n_interval = int((T + t_interval - 1) // t_interval) + print(n_interval, T, t_interval) + + # Extract spikes + times = [] + gids = [] + rates = np.zeros(shape=(n_interval, n_cells)) + for (gid, _), time in sim.spikes(): + times.append(time) + gids.append(gid) + it = int(time // t_interval) + rates[it, gid] += 1 + + fg, ax = plt.subplots() + ax.scatter(times, gids, c=gids) + ax.set_xlabel("Time $(t/ms)$") + ax.set_ylabel("GID") + ax.set_xlim(0, T) + fg.savefig(f"{prefix}raster.pdf") + fg.savefig(f"{prefix}raster.png") + fg.savefig(f"{prefix}raster.svg") + + ts = np.arange(n_interval) * t_interval + mean_rate = savgol_filter( + rates.mean(axis=1) / t_interval, window_length=5, polyorder=2 + ) + fg, ax = plt.subplots() + ax.plot(ts, rates) + ax.plot(ts, mean_rate, color="0.8", lw=4, label="Mean rate") + ax.set_xlabel("Time $(t/ms)$") + ax.legend() + ax.set_ylabel("Rate $(kHz)$") + ax.set_xlim(0, T) + fg.savefig(f"{prefix}rates.pdf") + fg.savefig(f"{prefix}rates.png") + fg.savefig(f"{prefix}rates.svg") From dddac77f765216c02372b8fe6a0afbe7be1ac759 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:35:24 +0200 Subject: [PATCH 23/30] Add graph plotting. --- doc/tutorial/plasticity.rst | 85 +- python/example/plasticity/01-raster.svg | 1592 +- python/example/plasticity/01-rates.svg | 260 +- python/example/plasticity/02-graph.svg | 808 + python/example/plasticity/02-matrix.svg | 388 + python/example/plasticity/02-raster.svg | 4005 ++++ python/example/plasticity/02-rates.svg | 2090 ++ python/example/plasticity/03-final-graph.svg | 1208 + python/example/plasticity/03-final-matrix.svg | 388 + .../example/plasticity/03-initial-graph.svg | 408 + .../example/plasticity/03-initial-matrix.svg | 388 + python/example/plasticity/03-raster.svg | 19409 ++++++++++++++++ python/example/plasticity/03-rates.svg | 2982 ++- python/example/plasticity/homeostasis.py | 52 +- python/example/plasticity/random_network.py | 3 +- python/example/plasticity/util.py | 29 +- 16 files changed, 31593 insertions(+), 2502 deletions(-) create mode 100644 python/example/plasticity/02-graph.svg create mode 100644 python/example/plasticity/02-matrix.svg create mode 100644 python/example/plasticity/02-raster.svg create mode 100644 python/example/plasticity/02-rates.svg create mode 100644 python/example/plasticity/03-final-graph.svg create mode 100644 python/example/plasticity/03-final-matrix.svg create mode 100644 python/example/plasticity/03-initial-graph.svg create mode 100644 python/example/plasticity/03-initial-matrix.svg create mode 100644 python/example/plasticity/03-raster.svg diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index d10c079c6..7aa3028f1 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -8,8 +8,18 @@ primarily interested in examining the required scaffolding. We will build up the simulation in stages, starting with an unconnected network and finishing with a dynamically built connectome. -An Unconnected Network ----------------------- +.. admonition:: Concepts and Requirements + + We cover some advanced topics in this tutorial, mainly structural + plasticity. Please refer to other tutorials for the basics of network + building. The model employed here --- storing an explicit connection matrix + --- is not advisable in most scenarios. + + In addition to Arbor and its requirements, ``scipy``, ``matplotlib``, and + ``networkx`` need to be installed. + +Unconnected Network +------------------- Consider a collection of ``N`` LIF cells. This will be the starting point for our exploration. For now, we set up each cell with a Poissonian input such that @@ -128,6 +138,9 @@ We then proceed to run the simulation and plot the results as before :language: python :lines: 68-79 +Note that we added a plot of the network connectivity using ``plot_network`` +from ``util`` as well. This generates images of the graph and connection matrix. + Adding Homeostasis ------------------ @@ -145,10 +158,64 @@ equation above, namely adding/deleting exactly one connection if the difference of observed to desired spiking frequency exceeds :math:`\pm\alpha`. This is both for simplicity and to avoid sudden changes in the network structure. -We do this by tweaking the connection table in between calls to ``run``. In -particular, we walk the potential pairings of targets and sources in random -order and check whether the targets requires adding or removing connections. If -we find an option to fulfill that requirement, we do so and proceed to the next -target. The randomization is important here, espcially for adding connections as -to avoid biases, in particular when there are too few eglible connection -partners. +As before, we set up global parameters + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 10-24 + +and prepare our simulation + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 37-39 + +Note that our new recipe is almost unaltered from the random network + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 27-33 + +all changes are contained to the way we run the simulation. To add a further +interesting feature, we skip the rewiring for the first half of the simulation. + +Plasticity is implemented by tweaking the connection table inside the recipe +between calls to ``run`` and calling ``simulation.update`` with the modified +recipe: + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 70 + +Changes are based on the difference of current rate we compute from the spikes +during the last interval + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 49-54 + +and the setpoint times the sensitivity + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 55 + +Then, each potential pairing of target and source is checked in random +order for whether adding or removing a connection is required + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 59-68 + +If we find an option to fulfill that requirement, we do so and proceed to the +next target. The randomization is important here, espcially for adding +connections as to avoid biases, in particular when there are too few eglible +connection partners. The ``randrange`` function produces a shuffled range ``[0, +N)``. We leverage the helper functions from the random network recipe to +manipulate the connection table, see the discussion above. + +Finally, we plot networks and spikes as before + +.. literalinclude:: ../../python/example/plasticity/homeostasis.py + :language: python + :lines: 74-75 diff --git a/python/example/plasticity/01-raster.svg b/python/example/plasticity/01-raster.svg index 7b4af213b..11b1a2fbd 100644 --- a/python/example/plasticity/01-raster.svg +++ b/python/example/plasticity/01-raster.svg @@ -6,7 +6,7 @@ - 2024-10-08T09:16:17.177252 + 2024-10-08T11:34:50.435338 image/svg+xml @@ -39,7 +39,7 @@ z - - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + @@ -1267,7 +1267,7 @@ z - + @@ -1308,7 +1308,7 @@ z - + @@ -1344,7 +1344,7 @@ z - + @@ -1391,7 +1391,7 @@ z - + @@ -1447,7 +1447,7 @@ z - + @@ -1704,12 +1704,12 @@ z - - + @@ -1722,7 +1722,7 @@ L -3.5 0 - + @@ -1735,7 +1735,7 @@ L -3.5 0 - + @@ -1748,7 +1748,7 @@ L -3.5 0 - + @@ -1761,7 +1761,7 @@ L -3.5 0 - + @@ -1856,7 +1856,7 @@ L 414.72 41.472 - + diff --git a/python/example/plasticity/01-rates.svg b/python/example/plasticity/01-rates.svg index 67fd888a3..087291dbb 100644 --- a/python/example/plasticity/01-rates.svg +++ b/python/example/plasticity/01-rates.svg @@ -6,7 +6,7 @@ - 2024-10-08T09:16:17.364183 + 2024-10-08T11:34:50.626245 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,7 +82,7 @@ z - + @@ -123,7 +123,7 @@ z - + @@ -159,7 +159,7 @@ z - + @@ -206,7 +206,7 @@ z - + @@ -262,7 +262,7 @@ z - + @@ -519,12 +519,12 @@ z - - + @@ -549,7 +549,7 @@ z - + @@ -592,7 +592,7 @@ z - + @@ -608,7 +608,7 @@ z - + @@ -636,7 +636,7 @@ z - + @@ -652,7 +652,7 @@ z - + @@ -668,7 +668,7 @@ z - + @@ -684,7 +684,7 @@ z - + @@ -700,7 +700,7 @@ z - + @@ -956,7 +956,7 @@ L 400.4352 174.528 L 404.0064 174.528 L 407.5776 295.488 L 411.1488 295.488 -" clip-path="url(#p4dc5912be8)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #ff7f0e; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #2ca02c; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #d62728; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #9467bd; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #8c564b; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #e377c2; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #7f7f7f; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #bcbd22; stroke-width: 1.5; stroke-linecap: square"/> +" clip-path="url(#p6f50955fff)" style="fill: none; stroke: #17becf; stroke-width: 1.5; stroke-linecap: square"/> - + + diff --git a/python/example/plasticity/02-graph.svg b/python/example/plasticity/02-graph.svg new file mode 100644 index 000000000..e40f6759e --- /dev/null +++ b/python/example/plasticity/02-graph.svg @@ -0,0 +1,808 @@ + + + + + + + + 2024-10-08T11:34:44.762762 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/02-matrix.svg b/python/example/plasticity/02-matrix.svg new file mode 100644 index 000000000..ce78d1a5e --- /dev/null +++ b/python/example/plasticity/02-matrix.svg @@ -0,0 +1,388 @@ + + + + + + + + 2024-10-08T11:34:44.531402 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/02-raster.svg b/python/example/plasticity/02-raster.svg new file mode 100644 index 000000000..eba26e802 --- /dev/null +++ b/python/example/plasticity/02-raster.svg @@ -0,0 +1,4005 @@ + + + + + + + + 2024-10-08T11:34:44.998033 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/02-rates.svg b/python/example/plasticity/02-rates.svg new file mode 100644 index 000000000..c2b4f9acf --- /dev/null +++ b/python/example/plasticity/02-rates.svg @@ -0,0 +1,2090 @@ + + + + + + + + 2024-10-08T11:34:45.180583 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-final-graph.svg b/python/example/plasticity/03-final-graph.svg new file mode 100644 index 000000000..6b31fdae9 --- /dev/null +++ b/python/example/plasticity/03-final-graph.svg @@ -0,0 +1,1208 @@ + + + + + + + + 2024-10-08T11:34:38.359950 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-final-matrix.svg b/python/example/plasticity/03-final-matrix.svg new file mode 100644 index 000000000..746c6e5d7 --- /dev/null +++ b/python/example/plasticity/03-final-matrix.svg @@ -0,0 +1,388 @@ + + + + + + + + 2024-10-08T11:34:38.085467 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-initial-graph.svg b/python/example/plasticity/03-initial-graph.svg new file mode 100644 index 000000000..f01b66872 --- /dev/null +++ b/python/example/plasticity/03-initial-graph.svg @@ -0,0 +1,408 @@ + + + + + + + + 2024-10-08T11:34:37.211454 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-initial-matrix.svg b/python/example/plasticity/03-initial-matrix.svg new file mode 100644 index 000000000..42c20f7b9 --- /dev/null +++ b/python/example/plasticity/03-initial-matrix.svg @@ -0,0 +1,388 @@ + + + + + + + + 2024-10-08T11:34:37.047482 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-raster.svg b/python/example/plasticity/03-raster.svg new file mode 100644 index 000000000..e091e70cc --- /dev/null +++ b/python/example/plasticity/03-raster.svg @@ -0,0 +1,19409 @@ + + + + + + + + 2024-10-08T11:34:38.815593 + image/svg+xml + + + Matplotlib v3.8.3, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg index c6c97fc74..dbb954ad0 100644 --- a/python/example/plasticity/03-rates.svg +++ b/python/example/plasticity/03-rates.svg @@ -6,7 +6,7 @@ - 2024-10-03T11:36:12.842254 + 2024-10-08T11:34:39.088775 image/svg+xml @@ -41,12 +41,12 @@ z - - + @@ -82,12 +82,12 @@ z - + - - + + + + - + - - + + + + - + - - + + + + - + - - + + + + - + - - + + + + - - + + - - + - - + - - + - + - - - - - - - - + + + + + + + + + + + @@ -445,209 +524,104 @@ z - - + - - - - - + + - - - - + - - - - - - - - - + + + - + - - - - - - + + + - + - - - - - - + + + - + - - - - - - + + + - + - - - - - - + + + + - + - - - - - - - - - - - - - - - - - - - - - - - - - + + + + - + - + - - + - @@ -760,1143 +761,1138 @@ z + + + - + - + - + - + - + - + - + - + - - - - - - - +L 232.5888 221.051077 +L 236.16 221.051077 +L 239.7312 183.832615 +L 243.3024 146.614154 +L 246.8736 165.223385 +L 250.4448 128.004923 +L 254.016 146.614154 +L 257.5872 109.395692 +L 261.1584 128.004923 +L 264.7296 109.395692 +L 268.3008 72.177231 +L 271.872 146.614154 +L 275.4432 146.614154 +L 279.0144 128.004923 +L 282.5856 109.395692 +L 286.1568 165.223385 +L 289.728 146.614154 +L 293.2992 128.004923 +L 296.8704 165.223385 +L 300.4416 109.395692 +L 304.0128 146.614154 +L 307.584 146.614154 +L 311.1552 90.786462 +L 314.7264 128.004923 +L 318.2976 128.004923 +L 321.8688 72.177231 +L 325.44 109.395692 +L 329.0112 146.614154 +L 332.5824 165.223385 +L 336.1536 146.614154 +L 339.7248 146.614154 +L 343.296 165.223385 +L 346.8672 128.004923 +L 350.4384 72.177231 +L 354.0096 128.004923 +L 357.5808 146.614154 +L 361.152 146.614154 +L 364.7232 128.004923 +L 368.2944 146.614154 +L 371.8656 146.614154 +L 375.4368 146.614154 +L 379.008 165.223385 +L 382.5792 146.614154 +L 386.1504 128.004923 +L 389.7216 128.004923 +L 393.2928 109.395692 +L 396.864 128.004923 +L 400.4352 128.004923 +L 404.0064 165.223385 +L 407.5776 146.614154 +L 411.1488 146.614154 +" clip-path="url(#p71169c5d0f)" style="fill: none; stroke: #17becf; stroke-width: 1.5; stroke-linecap: square"/> - - + + - - - + - + - + + + @@ -1970,123 +2002,11 @@ z - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py index c15970340..4edf354ee 100644 --- a/python/example/plasticity/homeostasis.py +++ b/python/example/plasticity/homeostasis.py @@ -2,51 +2,43 @@ import arbor as A from arbor import units as U -from typing import Any -import matplotlib.pyplot as plt import numpy as np -from util import plot_spikes +from util import plot_spikes, plot_network, randrange from random_network import random_network # global parameters +# cell count +N = 10 # total runtime [ms] T = 10000 # one interval [ms] t_interval = 100 # numerical time step [ms] dt = 0.1 - - -def randrange(n: int): - res = np.arange(n, dtype=int) - np.random.shuffle(res) - return res - - +# Set seed for numpy np.random.seed = 23 +# setpoint rate in kHz +setpoint_rate = 0.1 +# sensitivty towards deviations from setpoint +sensitivity = 200 class homeostatic_network(random_network): - def __init__(self, N) -> None: + def __init__(self, N, setpoint_rate, sensitivity) -> None: super().__init__(N) self.max_inc = 8 self.max_out = 8 - # setpoint rate in kHz - self.setpoint = 0.1 - # sensitivty towards deviations from setpoint - self.alpha = 200 + self.setpoint = setpoint_rate + self.alpha = sensitivity if __name__ == "__main__": - rec = homeostatic_network(10) + rec = homeostatic_network(N, setpoint_rate, sensitivity) sim = A.simulation(rec) sim.record(A.spike_recording.all) - print("Initial network:") - print(rec.inc) - print(rec.out) - print(rec.connections) + plot_network(rec, prefix="03-initial-") t = 0 while t < T: @@ -54,8 +46,7 @@ def __init__(self, N) -> None: if t < T / 2: t += t_interval continue - n = rec.num_cells() - rates = np.zeros(n) + rates = np.zeros(N) for (gid, _), time in sim.spikes(): if time < t: continue @@ -65,10 +56,10 @@ def __init__(self, N) -> None: unchangeable = set() added = [] deled = [] - for tgt in randrange(n): + for tgt in randrange(N): if dC[tgt] == 0: continue - for src in randrange(n): + for src in randrange(N): if dC[tgt] > 0 and rec.add_connection(src, tgt): added.append((src, tgt)) break @@ -80,12 +71,5 @@ def __init__(self, N) -> None: print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") t += t_interval - print("Final network:") - print(rec.inc) - print(rec.out) - print(rec.connections) - - plot_spikes( - sim, - rec.num_cells(), - ) + plot_network(rec, prefix="03-final-") + plot_spikes(sim, N, t_interval, T, prefix="03-") diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py index 84625afee..cc58c90af 100644 --- a/python/example/plasticity/random_network.py +++ b/python/example/plasticity/random_network.py @@ -75,4 +75,5 @@ def rewire(self): t += t_interval sim.run(t * U.ms, dt * U.ms) - plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="02-") + plot_network(rec, prefix="02-") + plot_spikes(sim, N, t_interval, T, prefix="02-") diff --git a/python/example/plasticity/util.py b/python/example/plasticity/util.py index a089fc626..bb5e2878b 100644 --- a/python/example/plasticity/util.py +++ b/python/example/plasticity/util.py @@ -3,6 +3,27 @@ import matplotlib.pyplot as plt import numpy as np from scipy.signal import savgol_filter +import networkx as nx + +def plot_network(rec, prefix=""): + fg, ax = plt.subplots() + ax.matshow(rec.connections) + fg.savefig(f"{prefix}matrix.pdf") + fg.savefig(f"{prefix}matrix.png") + fg.savefig(f"{prefix}matrix.svg") + + n = rec.num_cells() + fg, ax = plt.subplots() + g = nx.MultiDiGraph() + g.add_nodes_from(np.arange(n)) + for i in range(n): + for j in range(n): + for _ in range(rec.connections[i, j]): + g.add_edge(i, j) + nx.draw(g, with_labels=True, font_weight='bold') + fg.savefig(f"{prefix}graph.pdf") + fg.savefig(f"{prefix}graph.png") + fg.savefig(f"{prefix}graph.svg") def plot_spikes(sim, n_cells, t_interval, T, prefix=""): @@ -31,7 +52,7 @@ def plot_spikes(sim, n_cells, t_interval, T, prefix=""): ts = np.arange(n_interval) * t_interval mean_rate = savgol_filter( - rates.mean(axis=1) / t_interval, window_length=5, polyorder=2 + rates.mean(axis=1), window_length=5, polyorder=2 ) fg, ax = plt.subplots() ax.plot(ts, rates) @@ -43,3 +64,9 @@ def plot_spikes(sim, n_cells, t_interval, T, prefix=""): fg.savefig(f"{prefix}rates.pdf") fg.savefig(f"{prefix}rates.png") fg.savefig(f"{prefix}rates.svg") + + +def randrange(n: int): + res = np.arange(n, dtype=int) + np.random.shuffle(res) + return res From 371f84379bcc692413f7115b32969b396d209d1c Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:54:00 +0200 Subject: [PATCH 24/30] Add title and conclusion --- doc/tutorial/plasticity.rst | 72 +++++++++++++++++++++++++++++++------ 1 file changed, 62 insertions(+), 10 deletions(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index 7aa3028f1..4f6db878d 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -1,5 +1,8 @@ .. _tutorial_plasticity: +Structural Plasticity in Arbor +============================== + In this tutorial, we are going to demonstrate how a network can be built using plasticity and homeostatic connection rules. Despite not playing towards Arbor's strengths, we choose a LIF (Leaky Integrate and Fire) neuron model, as we are @@ -77,11 +80,23 @@ parameters are set in the constructor We also proceed to add spike recording and generate plots using a helper function ``plot_spikes`` from ``util.py``. You can skip the following details -for now and come back later if you are interested how it works. We generate -raster plots via ``scatter``. Rates are computed by binning spikes into -``t_interval`` and the neuron id; the mean rate is the average across the -neurons smoothed using a Savitzky-Golay filter (``scipy.signal.savgol_filter``). -We plot per-neuron and mean rates. +for now and come back later if you are interested how it works. Rates are +computed by binning spikes into ``t_interval`` and the neuron id; the mean rate +is the average across the neurons smoothed using a Savitzky-Golay filter +(``scipy.signal.savgol_filter``). + +We plot per-neuron and mean rates: + +.. figure:: ../../python/example/plasticity/01-rates.svg + :width: 400 + :align: center + +We also generate raster plots via ``scatter``. + +.. figure:: ../../python/example/plasticity/01-raster.svg + :width: 400 + :align: center + A Randomly Wired Network ------------------------ @@ -132,15 +147,32 @@ Both are used in ``rewire`` to produce a random connection matrix :language: python :lines: 56-65 -We then proceed to run the simulation and plot the results as before +We then proceed to run the simulation .. literalinclude:: ../../python/example/plasticity/random_network.py :language: python :lines: 68-79 + and plot the results as before + +.. figure:: ../../python/example/plasticity/02-rates.svg + :width: 400 + :align: center + + Note that we added a plot of the network connectivity using ``plot_network`` from ``util`` as well. This generates images of the graph and connection matrix. +.. figure:: ../../python/example/plasticity/02-matrix.svg + :width: 400 + :align: center + +.. figure:: ../../python/example/plasticity/02-graph.svg + :width: 400 + :align: center + + + Adding Homeostasis ------------------ @@ -214,8 +246,28 @@ connection partners. The ``randrange`` function produces a shuffled range ``[0, N)``. We leverage the helper functions from the random network recipe to manipulate the connection table, see the discussion above. -Finally, we plot networks and spikes as before +Finally, we plot spiking rates as before; the jump at the half-way point is the +effect of the plasticity activating after which each neuron moves to the +setpoint -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 74-75 +.. figure:: ../../python/example/plasticity/03-rates.svg + :width: 400 + :align: center + +and the resulting network + +.. figure:: ../../python/example/plasticity/03-graph.svg + :width: 400 + :align: center + +Conclusion +---------- + +This concludes our foray into structural plasticity. While the building blocks + +- an explicit representation of the connections, +- running the simulation in batches (and calling ``simulation.update``!) +- a rule to derive the change + +will likely be the same in all approaches, the concrete implementation of the +rules is the centerpiece here. From 7c165c7cfb0cf19290d73004bd11d6905a157fef Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 11:56:09 +0200 Subject: [PATCH 25/30] Use correct file name --- doc/tutorial/plasticity.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index 4f6db878d..db9108d48 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -256,7 +256,7 @@ setpoint and the resulting network -.. figure:: ../../python/example/plasticity/03-graph.svg +.. figure:: ../../python/example/plasticity/03-final-graph.svg :width: 400 :align: center From 6f860635374a06e6a1d5c4644531e0dce9f054c7 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:00:18 +0200 Subject: [PATCH 26/30] Small comment. --- doc/tutorial/plasticity.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index db9108d48..7cdf474d6 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -210,6 +210,9 @@ Note that our new recipe is almost unaltered from the random network all changes are contained to the way we run the simulation. To add a further interesting feature, we skip the rewiring for the first half of the simulation. +The initial network is unconnected, but could be populated randomly (or any +other way) if desired by calling ``self.rewire()`` in the constructor of +``homeostatic_network`` before setting the maxima to eight. Plasticity is implemented by tweaking the connection table inside the recipe between calls to ``run`` and calling ``simulation.update`` with the modified From df94162f8cbd2c1b1adda2c61eea6b7f796fd4f2 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 8 Oct 2024 12:45:32 +0200 Subject: [PATCH 27/30] Final thoughts and warnings. --- doc/tutorial/plasticity.rst | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst index 7cdf474d6..ea6aa58bc 100644 --- a/doc/tutorial/plasticity.rst +++ b/doc/tutorial/plasticity.rst @@ -222,6 +222,32 @@ recipe: :language: python :lines: 70 +.. note:: + + As it is the central point here, it is worth emphasizing why this yields a + changed network. The call to ``sim.update(rec)`` causes Arbor to internally + re-build the connection table from scratch based on the data returned by + ``rec.connections_on``. However, here, this method just inspects the matrix + in ``rec.connections`` and converts the data into a ``arbor.connection``. + Thus, changing this matrix before ``update`` will build a different network. + + Important caveats: + + - without ``update``, changes to the recipe have no effect + - vice versa ``update`` has no effect if the recipe doesn't return different + data than before + - ``update`` will delete all existing connections and their parameters, so + all connections to be kept must be explicitly re-instantiated + - ``update`` will **not** delete synapses or their state, e.g. ODEs will + still be integrated even if not connected and currents might be produced + - neither synapses/targets nor detectors/sources can be altered. Create all + endpoints up front. + - only the network is updated (this might change in future versions!) + - be very aware that ``connections_on`` might be called in arbitrary order + and by multiples (potentially different) threads and processes! This + requires some thought and synchronization when dealing with random numbers + and updating data *inside* ``connections_on``. + Changes are based on the difference of current rate we compute from the spikes during the last interval @@ -273,4 +299,10 @@ This concludes our foray into structural plasticity. While the building blocks - a rule to derive the change will likely be the same in all approaches, the concrete implementation of the -rules is the centerpiece here. +rules is the centerpiece here. For example, although spike rate homeostasis was +used here, mechanism states and ion concentrations --- extracted via the normal +probe and sample interface --- can be leveraged to build rules. Due to the way +the Python interface is required to link to measurements, using the C++ API for +access to streaming spike and measurement data could help to address performance +issues. Plasticity as shown also meshes with the high-level connection builder. +External tools to build and update connections might be useful as well. From ea6bb4d26b7b608519fe85ea777938153cb86bf5 Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:41:11 +0100 Subject: [PATCH 28/30] Commit, merge, and lint. --- python/example/plasticity/util.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/python/example/plasticity/util.py b/python/example/plasticity/util.py index bb5e2878b..554259ad3 100644 --- a/python/example/plasticity/util.py +++ b/python/example/plasticity/util.py @@ -5,6 +5,7 @@ from scipy.signal import savgol_filter import networkx as nx + def plot_network(rec, prefix=""): fg, ax = plt.subplots() ax.matshow(rec.connections) @@ -20,7 +21,7 @@ def plot_network(rec, prefix=""): for j in range(n): for _ in range(rec.connections[i, j]): g.add_edge(i, j) - nx.draw(g, with_labels=True, font_weight='bold') + nx.draw(g, with_labels=True, font_weight="bold") fg.savefig(f"{prefix}graph.pdf") fg.savefig(f"{prefix}graph.png") fg.savefig(f"{prefix}graph.svg") @@ -51,9 +52,7 @@ def plot_spikes(sim, n_cells, t_interval, T, prefix=""): fg.savefig(f"{prefix}raster.svg") ts = np.arange(n_interval) * t_interval - mean_rate = savgol_filter( - rates.mean(axis=1), window_length=5, polyorder=2 - ) + mean_rate = savgol_filter(rates.mean(axis=1), window_length=5, polyorder=2) fg, ax = plt.subplots() ax.plot(ts, rates) ax.plot(ts, mean_rate, color="0.8", lw=4, label="Mean rate") From 11ead8ba7d88b44613fe775ed0c64d91eca40eec Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Tue, 19 Nov 2024 13:52:38 +0100 Subject: [PATCH 29/30] Skip stubs in doc test --- .github/workflows/test-docs.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml index 21a546845..d4020b509 100644 --- a/.github/workflows/test-docs.yaml +++ b/.github/workflows/test-docs.yaml @@ -37,5 +37,5 @@ jobs: run: | mkdir build cd build - cmake .. -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` + cmake .. -DARB_WITH_PYTHON=ON -DARB_BUILD_PYTHON_STUBS=OFF -DPython3_EXECUTABLE=`which python` make html From 07d48a1379a7e1584f882cdb37b575e5af5586fc Mon Sep 17 00:00:00 2001 From: Thorsten Hater <24411438+thorstenhater@users.noreply.github.com> Date: Mon, 25 Nov 2024 11:48:14 +0100 Subject: [PATCH 30/30] reset. --- .github/workflows/benchmarks.yml | 4 +- .github/workflows/codeql.yml | 2 +- .github/workflows/sanitize.yml | 10 +- .github/workflows/test-docs.yaml | 2 +- .github/workflows/test-matrix.yml | 6 +- .github/workflows/test-spack.yml | 4 +- CMakeLists.txt | 25 +- arbor/CMakeLists.txt | 4 + arbor/backends/common_types.hpp | 41 - arbor/backends/event.hpp | 9 + arbor/backends/event_stream_base.hpp | 99 +- arbor/backends/gpu/event_stream.hpp | 126 +- arbor/backends/gpu/forest.cpp | 1 - arbor/backends/gpu/fvm.hpp | 8 + arbor/backends/gpu/gpu_store_types.hpp | 3 + arbor/backends/gpu/matrix_state_fine.hpp | 47 +- arbor/backends/gpu/shared_state.cpp | 53 +- arbor/backends/gpu/shared_state.hpp | 38 +- arbor/backends/multicore/cable_solver.hpp | 7 +- arbor/backends/multicore/event_stream.hpp | 59 +- arbor/backends/multicore/fvm.hpp | 3 + arbor/backends/multicore/multicore_common.hpp | 3 + arbor/backends/multicore/shared_state.cpp | 53 +- arbor/backends/multicore/shared_state.hpp | 39 +- arbor/backends/shared_state_base.hpp | 11 +- arbor/cable_cell.cpp | 20 +- arbor/cable_cell_param.cpp | 8 + arbor/cv_policy.cpp | 288 +- {test/ubench => arbor}/event_queue.hpp | 21 +- arbor/fvm_layout.cpp | 99 +- arbor/fvm_layout.hpp | 3 - arbor/fvm_lowered_cell_impl.hpp | 102 +- arbor/hardware/power.cpp | 29 + arbor/hardware/power.hpp | 18 + arbor/include/arbor/cable_cell.hpp | 48 +- arbor/include/arbor/cable_cell_param.hpp | 5 +- arbor/include/arbor/cv_policy.hpp | 167 +- arbor/include/arbor/event_generator.hpp | 1 + arbor/include/arbor/generic_event.hpp | 91 + arbor/include/arbor/mechanism_abi.h | 4 +- arbor/include/arbor/mechinfo.hpp | 4 +- arbor/include/arbor/s_expr.hpp | 2 + arbor/include/arbor/util/expected.hpp | 2 +- arbor/io/locked_ostream.cpp | 76 + arbor/io/locked_ostream.hpp | 25 + arbor/io/save_ios.hpp | 24 + arbor/io/serialize_hex.cpp | 62 + arbor/io/serialize_hex.hpp | 37 + arbor/io/trace.hpp | 79 + arbor/matrix.hpp | 63 + arbor/mechinfo.cpp | 18 +- arbor/merge_events.cpp | 2 + arbor/morph/cv_data.cpp | 2 +- arbor/morph/embed_pwlin.cpp | 2 +- arbor/profile/meter_manager.cpp | 4 + arbor/profile/power_meter.cpp | 46 + arbor/profile/power_meter.hpp | 11 + arbor/simulation.cpp | 11 +- arbor/tree.cpp | 28 +- arbor/tree.hpp | 4 + arbor/util/cycle.hpp | 213 + arbor/util/meta.hpp | 1 + arbor/util/nop.hpp | 33 + arbor/util/piecewise.hpp | 39 +- arbor/util/range.hpp | 8 +- arbor/util/rangeutil.hpp | 13 + arborio/cableio.cpp | 114 +- arborio/cv_policy_parse.cpp | 39 +- arborio/include/arborio/cv_policy_parse.hpp | 1 + cmake/CheckCompilerXLC.cmake | 17 + cmake/CompilerOptions.cmake | 35 +- doc/concepts/cable_cell.rst | 27 +- doc/concepts/discretization.rst | 55 - doc/dependencies.csv | 2 +- doc/fileformat/cable_cell.rst | 10 +- doc/index.rst | 2 +- doc/python/cable_cell.rst | 22 +- doc/python/decor.rst | 14 + doc/python/probe_sample.rst | 172 +- doc/tutorial/index.rst | 1 - doc/tutorial/plasticity.rst | 308 - doc/tutorial/single_cell_detailed.rst | 4 +- example/busyring/ring.cpp | 8 +- example/diffusion/diffusion.cpp | 2 +- example/dryrun/branch_cell.hpp | 5 +- example/lfp/lfp.cpp | 7 +- example/network_description/branch_cell.hpp | 6 +- example/ornstein_uhlenbeck/ou.cpp | 3 +- example/plasticity/branch_cell.hpp | 4 +- example/plasticity/plasticity.cpp | 5 +- example/probe-demo/probe-demo.cpp | 89 +- example/ring/branch_cell.hpp | 6 +- modcc/blocks.hpp | 11 +- modcc/identifier.hpp | 4 + modcc/printer/infoprinter.cpp | 13 +- modcc/printer/printerutil.hpp | 21 + python/CMakeLists.txt | 26 +- python/cells.cpp | 97 +- python/example/diffusion.py | 3 +- .../network_two_cells_gap_junctions.py | 6 +- python/example/plasticity/01-raster.svg | 1863 -- python/example/plasticity/01-rates.svg | 2107 -- python/example/plasticity/02-graph.svg | 808 - python/example/plasticity/02-matrix.svg | 388 - python/example/plasticity/02-raster.svg | 4005 ---- python/example/plasticity/02-rates.svg | 2090 -- python/example/plasticity/03-final-graph.svg | 1208 - python/example/plasticity/03-final-matrix.svg | 388 - .../example/plasticity/03-initial-graph.svg | 408 - .../example/plasticity/03-initial-matrix.svg | 388 - python/example/plasticity/03-raster.svg | 19409 ---------------- python/example/plasticity/03-rates.svg | 2013 -- python/example/plasticity/homeostasis.py | 75 - python/example/plasticity/random_network.py | 79 - python/example/plasticity/unconnected.py | 62 - python/example/plasticity/util.py | 71 - python/example/probe_lfpykit.py | 7 +- python/example/single_cell_allen.py | 4 +- .../l5pc/C060114A7_axon_replacement.acc | 2 +- .../l5pc/C060114A7_modified.acc | 2 +- .../single_cell_bluepyopt/l5pc/l5pc_decor.acc | 2 +- .../l5pc/l5pc_label_dict.acc | 2 +- .../simplecell/simple_cell_decor.acc | 2 +- .../simplecell/simple_cell_label_dict.acc | 2 +- python/example/single_cell_bluepyopt_l5pc.py | 41 +- .../single_cell_bluepyopt_simplecell.py | 23 +- python/example/single_cell_cable.py | 3 +- python/example/single_cell_detailed.py | 7 +- python/example/single_cell_detailed_recipe.py | 8 +- python/example/single_cell_nml.py | 7 +- python/example/single_cell_stdp.py | 4 +- python/example/single_cell_swc.py | 9 +- python/mechanism.cpp | 16 +- python/probes.cpp | 14 +- python/strprintf.hpp | 2 +- python/test/unit/test_io.py | 6 +- python/test/unit/test_probes.py | 23 +- spack/package.py | 8 - test/common_cells.cpp | 27 +- test/common_cells.hpp | 7 +- test/ubench/CMakeLists.txt | 2 +- test/ubench/event_binning.cpp | 1 + test/ubench/event_setup.cpp | 3 +- test/ubench/fvm_discretize.cpp | 20 +- test/ubench/mech_vec.cpp | 16 +- test/unit-distributed/test_communicator.cpp | 6 +- .../test_distributed_for_each.cpp | 13 +- .../test_network_generation.cpp | 8 +- test/unit/CMakeLists.txt | 5 +- test/unit/common.hpp | 1 - test/unit/test_cv_geom.cpp | 6 +- test/unit/test_cv_policy.cpp | 18 +- test/unit/test_cycle.cpp | 225 + test/unit/test_diffusion.cpp | 2 +- test/unit/test_event_queue.cpp | 160 + test/unit/test_event_stream.cpp | 115 +- test/unit/test_event_stream.hpp | 212 - test/unit/test_event_stream_gpu.cpp | 135 +- test/unit/test_fvm_layout.cpp | 19 +- test/unit/test_fvm_lowered.cpp | 12 +- test/unit/test_math.cpp | 16 +- test/unit/test_matrix.cpp | 21 +- test/unit/test_mechanisms.cpp | 244 + test/unit/test_probe.cpp | 111 +- test/unit/test_range.cpp | 64 +- test/unit/test_s_expr.cpp | 26 +- test/unit/test_sde.cpp | 23 +- test/unit/test_simd.cpp | 4 +- test/unit/test_spikes.cpp | 3 +- 169 files changed, 3156 insertions(+), 37434 deletions(-) rename {test/ubench => arbor}/event_queue.hpp (86%) create mode 100644 arbor/hardware/power.cpp create mode 100644 arbor/hardware/power.hpp create mode 100644 arbor/include/arbor/generic_event.hpp create mode 100644 arbor/io/locked_ostream.cpp create mode 100644 arbor/io/locked_ostream.hpp create mode 100644 arbor/io/save_ios.hpp create mode 100644 arbor/io/serialize_hex.cpp create mode 100644 arbor/io/serialize_hex.hpp create mode 100644 arbor/io/trace.hpp create mode 100644 arbor/matrix.hpp create mode 100644 arbor/profile/power_meter.cpp create mode 100644 arbor/profile/power_meter.hpp create mode 100644 arbor/util/cycle.hpp create mode 100644 arbor/util/nop.hpp create mode 100644 cmake/CheckCompilerXLC.cmake delete mode 100644 doc/concepts/discretization.rst delete mode 100644 doc/tutorial/plasticity.rst delete mode 100644 python/example/plasticity/01-raster.svg delete mode 100644 python/example/plasticity/01-rates.svg delete mode 100644 python/example/plasticity/02-graph.svg delete mode 100644 python/example/plasticity/02-matrix.svg delete mode 100644 python/example/plasticity/02-raster.svg delete mode 100644 python/example/plasticity/02-rates.svg delete mode 100644 python/example/plasticity/03-final-graph.svg delete mode 100644 python/example/plasticity/03-final-matrix.svg delete mode 100644 python/example/plasticity/03-initial-graph.svg delete mode 100644 python/example/plasticity/03-initial-matrix.svg delete mode 100644 python/example/plasticity/03-raster.svg delete mode 100644 python/example/plasticity/03-rates.svg delete mode 100644 python/example/plasticity/homeostasis.py delete mode 100644 python/example/plasticity/random_network.py delete mode 100644 python/example/plasticity/unconnected.py delete mode 100644 python/example/plasticity/util.py create mode 100644 test/unit/test_cycle.cpp create mode 100644 test/unit/test_event_queue.cpp delete mode 100644 test/unit/test_event_stream.hpp create mode 100644 test/unit/test_mechanisms.cpp diff --git a/.github/workflows/benchmarks.yml b/.github/workflows/benchmarks.yml index be1014b85..d30677469 100644 --- a/.github/workflows/benchmarks.yml +++ b/.github/workflows/benchmarks.yml @@ -12,8 +12,8 @@ jobs: strategy: fail-fast: false env: - CC: gcc-12 - CXX: g++-12 + CC: gcc-11 + CXX: g++-11 steps: - name: Get build dependencies run: | diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index fdf5c37ec..4974cd45e 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -66,7 +66,7 @@ jobs: run: | mkdir build cd build - cmake .. -DCMAKE_CXX_COMPILER=g++-12 -DCMAKE_C_COMPILER=gcc-12 -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON -DARB_BUILD_PYTHON_STUBS=OFF + cmake .. -DCMAKE_CXX_COMPILER=g++ -DCMAKE_C_COMPILER=gcc -DARB_WITH_PYTHON=ON -DARB_VECTORIZE=ON -DPython3_EXECUTABLE=`which python` -DARB_WITH_MPI=OFF -DARB_WITH_ASSERTIONS=ON -DARB_WITH_PROFILING=ON make -j4 tests examples pyarb cd - diff --git a/.github/workflows/sanitize.yml b/.github/workflows/sanitize.yml index ff51846b7..b75a1ed9a 100644 --- a/.github/workflows/sanitize.yml +++ b/.github/workflows/sanitize.yml @@ -19,7 +19,7 @@ permissions: jobs: build: name: "Sanitize" - runs-on: ubuntu-24.04 + runs-on: ubuntu-22.04 strategy: fail-fast: false matrix: @@ -27,8 +27,8 @@ jobs: sanitizer: ["address", "undefined", "thread"] simd: ["ON", "OFF"] env: - CC: clang-18 - CXX: clang++-18 + CC: clang-14 + CXX: clang++-14 ASAN_OPTIONS: detect_leaks=1 steps: - name: Get build dependencies @@ -43,6 +43,8 @@ jobs: uses: actions/checkout@v4 with: submodules: recursive + - name: Update pip + run: python -m pip install --upgrade pip # figure out vector extensions for ccache key - name: Check vector extensions run: | @@ -62,7 +64,7 @@ jobs: mkdir build cd build export SAN="-fsanitize=${{ matrix.sanitizer }} -fno-omit-frame-pointer" - cmake .. -GNinja -DCMAKE_BUILD_TYPE=debug -DCMAKE_CXX_FLAGS="$SAN" -DCMAKE_C_FLAGS="$SAN" -DCMAKE_EXE_LINKER_FLAGS="$SAN" -DCMAKE_MODULE_LINKER_FLAGS="$SAN" -DARB_BUILD_PYTHON_STUBS=OFF -DARB_WITH_ASSERTIONS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_VECTORIZE=${{ matrix.simd }} -DARB_WITH_MPI=OFF -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` + cmake .. -GNinja -DCMAKE_BUILD_TYPE=debug -DCMAKE_CXX_FLAGS="$SAN" -DCMAKE_C_FLAGS="$SAN" -DCMAKE_EXE_LINKER_FLAGS="$SAN" -DCMAKE_MODULE_LINKER_FLAGS="$SAN" -DARB_WITH_ASSERTIONS=ON -DCMAKE_CXX_COMPILER=$CXX -DCMAKE_C_COMPILER=$CC -DARB_VECTORIZE=${{ matrix.simd }} -DARB_WITH_MPI=OFF -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` ninja -j4 -v tests examples pyarb cd - - name: Run unit tests diff --git a/.github/workflows/test-docs.yaml b/.github/workflows/test-docs.yaml index d4020b509..21a546845 100644 --- a/.github/workflows/test-docs.yaml +++ b/.github/workflows/test-docs.yaml @@ -37,5 +37,5 @@ jobs: run: | mkdir build cd build - cmake .. -DARB_WITH_PYTHON=ON -DARB_BUILD_PYTHON_STUBS=OFF -DPython3_EXECUTABLE=`which python` + cmake .. -DARB_WITH_PYTHON=ON -DPython3_EXECUTABLE=`which python` make html diff --git a/.github/workflows/test-matrix.yml b/.github/workflows/test-matrix.yml index 50edbf104..a886f582f 100644 --- a/.github/workflows/test-matrix.yml +++ b/.github/workflows/test-matrix.yml @@ -29,8 +29,8 @@ jobs: - { name: "Linux Min Clang", os: "ubuntu-22.04", - cc: "clang-13", - cxx: "clang++-13", + cc: "clang-12", + cxx: "clang++-12", py: "3.9", cmake: "3.19.x", mpi: "ON", @@ -116,7 +116,7 @@ jobs: - name: Update pip run: python -m pip install --upgrade pip - name: Install Python packages - run: pip install numpy sphinx svgwrite sphinx-rtd-theme mpi4py pandas seaborn pybind11-stubgen + run: pip install numpy sphinx svgwrite sphinx-rtd-theme mpi4py pandas seaborn - name: Clone w/ submodules uses: actions/checkout@v4 with: diff --git a/.github/workflows/test-spack.yml b/.github/workflows/test-spack.yml index df3856c79..d913be5b2 100644 --- a/.github/workflows/test-spack.yml +++ b/.github/workflows/test-spack.yml @@ -74,9 +74,9 @@ jobs: - name: build and install arbor through spack dev-build run: | source spack/share/spack/setup-env.sh - spack install --no-check-signature --only dependencies arbor@develop target=x86_64_v2 +python ~pystubs ^python@${{ matrix.python-version }} + spack install --no-check-signature --only dependencies arbor@develop target=x86_64_v2 +python ^python@${{ matrix.python-version }} cd arbor - spack dev-build arbor@develop target=x86_64_v2 +python ~pystubs ^python@${{ matrix.python-version }} + spack dev-build arbor@develop target=x86_64_v2 +python ^python@${{ matrix.python-version }} - name: load arbor and verify installation, python examples. run: | diff --git a/CMakeLists.txt b/CMakeLists.txt index bae8235cf..c32ad4a75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -7,6 +7,7 @@ set(CMAKE_SUPPRESS_DEVELOPER_WARNINGS ON CACHE INTERNAL "" FORCE) # Make CUDA support throw errors if architectures remain unclear cmake_policy(SET CMP0104 NEW) + # Ensure CMake is aware of the policies for modern RPATH behavior cmake_policy(SET CMP0072 NEW) @@ -45,9 +46,6 @@ if(NOT DEFINED CMAKE_INTERPROCEDURAL_OPTIMIZATION) endif() endif() -# Use pybind11-stubgen to make type stubs. -cmake_dependent_option(ARB_BUILD_PYTHON_STUBS "Use pybind11-stubgen to build type stubs." ON "ARB_WITH_PYTHON" OFF) - # Turn on this option to force the compilers to produce color output when output is # redirected from the terminal (e.g. when using ninja or a pager). @@ -203,10 +201,13 @@ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) set(CMAKE_EXPORT_COMPILE_COMMANDS "YES") +# Detect and deprecate xlC. + +include("CheckCompilerXLC") + # Compiler options common to library, examples, tests, etc. include("CompilerOptions") -check_supported_cxx() add_compile_options("$<$:${CXXOPT_WALL}>") set(CMAKE_CXX_STANDARD 20) set(CMAKE_CUDA_STANDARD 20) @@ -276,7 +277,7 @@ install(FILES mechanisms/BuildModules.cmake DESTINATION ${ARB_INSTALL_DATADIR}) # First make ourselves less chatty set(_saved_CMAKE_MESSAGE_LOG_LEVEL ${CMAKE_MESSAGE_LOG_LEVEL}) -set(CMAKE_MESSAGE_LOG_LEVEL STATUS) +set(CMAKE_MESSAGE_LOG_LEVEL WARNING) # in the event we can find hwloc, just add it find_package(hwloc QUIET) @@ -343,8 +344,6 @@ CPMAddPackage(NAME fmt VERSION 10.0.0 GIT_TAG 10.0.0) -add_library(ext-gtest INTERFACE) -add_library(ext-bench INTERFACE) if (BUILD_TESTING) CPMAddPackage(NAME benchmark GITHUB_REPOSITORY google/benchmark @@ -355,18 +354,6 @@ if (BUILD_TESTING) GIT_TAG release-1.12.1 VERSION 1.12.1 OPTIONS "INSTALL_GTEST OFF" "BUILD_GMOCK OFF") - if(benchmark_ADDED) - target_link_libraries(ext-bench INTERFACE benchmark) - else() - find_package(benchmark REQUIRED) - target_link_libraries(ext-bench INTERFACE benchmark::benchmark) - endif() - if(googletest_ADDED) - target_link_libraries(ext-gtest INTERFACE ) - else() - find_package(googletest REQUIRED) - target_link_libraries(ext-gtest INTERFACE gtest gtest_main) - endif() endif() CPMAddPackage(NAME units diff --git a/arbor/CMakeLists.txt b/arbor/CMakeLists.txt index c27295393..5ce77a147 100644 --- a/arbor/CMakeLists.txt +++ b/arbor/CMakeLists.txt @@ -20,7 +20,10 @@ set(arbor_sources fvm_layout.cpp fvm_lowered_cell_impl.cpp hardware/memory.cpp + hardware/power.cpp iexpr.cpp + io/locked_ostream.cpp + io/serialize_hex.cpp label_resolution.cpp lif_cell_group.cpp cable_cell_group.cpp @@ -47,6 +50,7 @@ set(arbor_sources partition_load_balance.cpp profile/memory_meter.cpp profile/meter_manager.cpp + profile/power_meter.cpp profile/profiler.cpp schedule.cpp spike_event_io.cpp diff --git a/arbor/backends/common_types.hpp b/arbor/backends/common_types.hpp index 18f258604..357975c64 100644 --- a/arbor/backends/common_types.hpp +++ b/arbor/backends/common_types.hpp @@ -2,7 +2,6 @@ #include -#include "fvm_layout.hpp" #include "util/range.hpp" #include "backends/threshold_crossing.hpp" #include "execution_context.hpp" @@ -22,44 +21,4 @@ struct fvm_detector_info { execution_context ctx; }; -struct ion_data_flags { - // flags for resetting ion states after W access - bool write_eX_:1 = false; // is eX written? - bool write_Xo_:1 = false; // is Xo written? - bool write_Xi_:1 = false; // is Xi written? - bool write_Xd_:1 = false; // is Xd written? - bool read_eX_:1 = false; // is eX read? - bool read_Xo_:1 = false; // is Xo read? - bool read_Xi_:1 = false; // is Xi read? - bool read_Xd_:1 = false; // is Xd read? - - ion_data_flags(const fvm_ion_config& config): - write_eX_(config.revpot_written), - write_Xo_(config.econc_written), - write_Xi_(config.iconc_written), - write_Xd_(config.is_diffusive), - read_eX_(config.revpot_read), - read_Xo_(config.econc_read), - read_Xi_(config.iconc_read), - read_Xd_(config.is_diffusive) - {} - - ion_data_flags() = default; - ion_data_flags(const ion_data_flags&) = default; - ion_data_flags& operator=(const ion_data_flags&) = default; - - bool xi() const { return read_Xi_ || write_Xi_; } - bool reset_xi() const { return write_Xi_; } - - bool xo() const { return read_Xo_ || write_Xo_; } - bool reset_xo() const { return write_Xo_; } - - bool xd() const { return read_Xd_ || write_Xd_; } - bool reset_xd() const { return write_Xd_; } - - bool ex() const { return read_eX_ || write_eX_; } - bool reset_ex() const { return write_eX_; } -}; - - } diff --git a/arbor/backends/event.hpp b/arbor/backends/event.hpp index 7fd9eb849..10249aa34 100644 --- a/arbor/backends/event.hpp +++ b/arbor/backends/event.hpp @@ -4,6 +4,7 @@ #include #include #include +#include // Structures for the representation of event delivery targets and // staged events. @@ -45,6 +46,9 @@ struct deliverable_event { ARB_SERDES_ENABLE(deliverable_event, time, weight, handle); }; +template<> +struct has_event_index : public std::true_type {}; + // Subset of event information required for mechanism delivery. struct deliverable_event_data { cell_local_size_type mech_index; // same as target_handle::mech_index @@ -57,6 +61,11 @@ struct deliverable_event_data { weight); }; +// Stream index accessor function for multi_event_stream: +inline cell_local_size_type event_index(const arb_deliverable_event_data& ed) { + return ed.mech_index; +} + // Delivery data accessor function for multi_event_stream: inline arb_deliverable_event_data event_data(const deliverable_event& ev) { return {ev.handle.mech_index, ev.weight}; diff --git a/arbor/backends/event_stream_base.hpp b/arbor/backends/event_stream_base.hpp index a81254a78..f825b503f 100644 --- a/arbor/backends/event_stream_base.hpp +++ b/arbor/backends/event_stream_base.hpp @@ -2,8 +2,10 @@ #include +#include #include + #include "backends/event.hpp" #include "backends/event_stream_state.hpp" #include "event_lane.hpp" @@ -16,13 +18,15 @@ namespace arb { template struct event_stream_base { + using size_type = std::size_t; using event_type = Event; - using event_data_type = decltype(event_data(std::declval())); + using event_time_type = ::arb::event_time_type; + using event_data_type = ::arb::event_data_type; protected: // members std::vector ev_data_; std::vector ev_spans_ = {0}; - std::size_t index_ = 0; + size_type index_ = 0; event_data_type* base_ptr_ = nullptr; public: @@ -58,32 +62,24 @@ struct event_stream_base { index_ = 0; } - protected: - // backend specific initializations - virtual void init() = 0; -}; - -struct spike_event_stream_base : event_stream_base { + // Construct a mapping of mech_id to a stream s.t. streams are partitioned into + // time step buckets by `ev_span` template - friend void initialize(const event_lane_subrange& lanes, - const std::vector& handles, - const std::vector& divs, - const timestep_range& steps, - std::unordered_map& streams) { + static std::enable_if_t> + multi_event_stream(const event_lane_subrange& lanes, + const std::vector& handles, + const std::vector& divs, + const timestep_range& steps, + std::unordered_map& streams) { arb_assert(lanes.size() < divs.size()); - // reset streams and allocate sufficient space for temporaries auto n_steps = steps.size(); + std::unordered_map> dt_sizes; for (auto& [k, v]: streams) { v.clear(); - v.spike_counter_.clear(); - v.spike_counter_.resize(steps.size(), 0); - v.spikes_.clear(); - // ev_data_ has been cleared during v.clear(), so we use its capacity - v.spikes_.reserve(v.ev_data_.capacity()); + dt_sizes[k].resize(n_steps, 0); } - // loop over lanes: group events by mechanism and sort them by time auto cell = 0; for (const auto& lane: lanes) { auto div = divs[cell]; @@ -98,71 +94,16 @@ struct spike_event_stream_base : event_stream_base { if (step >= n_steps) break; arb_assert(div + target < handles.size()); const auto& handle = handles[div + target]; - auto& stream = streams[handle.mech_id]; - stream.spikes_.push_back(spike_data{step, handle.mech_index, time, weight}); - // insertion sort with last element as pivot - // ordering: first w.r.t. step, within a step: mech_index, within a mech_index: time - auto first = stream.spikes_.begin(); - auto last = stream.spikes_.end(); - auto pivot = std::prev(last, 1); - std::rotate(std::upper_bound(first, pivot, *pivot), pivot, last); - // increment count in current time interval - stream.spike_counter_[step]++; + streams[handle.mech_id].ev_data_.push_back({handle.mech_index, weight}); + dt_sizes[handle.mech_id][step]++; } } for (auto& [id, stream]: streams) { - // copy temporary deliverable_events into stream's ev_data_ - stream.ev_data_.reserve(stream.spikes_.size()); - std::transform(stream.spikes_.begin(), stream.spikes_.end(), std::back_inserter(stream.ev_data_), - [](auto const& e) noexcept -> arb_deliverable_event_data { - return {e.mech_index, e.weight}; }); - // scan over spike_counter_ and written to ev_spans_ - util::make_partition(stream.ev_spans_, stream.spike_counter_); - // delegate to derived class init: static cast necessary to access protected init() - static_cast(stream).init(); + util::make_partition(stream.ev_spans_, dt_sizes[id]); + stream.init(); } } - - protected: // members - struct spike_data { - arb_size_type step = 0; - cell_local_size_type mech_index = 0; - time_type time = 0; - float weight = 0; - auto operator<=>(spike_data const&) const noexcept = default; - }; - std::vector spikes_; - std::vector spike_counter_; -}; - -struct sample_event_stream_base : event_stream_base { - friend void initialize(const std::vector>& staged, - sample_event_stream_base& stream) { - // clear previous data - stream.clear(); - - // return if there are no timestep bins - if (!staged.size()) return; - - // return if there are no events - auto num_events = util::sum_by(staged, [] (const auto& v) {return v.size();}); - if (!num_events) return; - - // allocate space for spans and data - stream.ev_spans_.reserve(staged.size() + 1); - stream.ev_data_.reserve(num_events); - - // add event data and spans - for (const auto& v : staged) { - for (const auto& ev: v) stream.ev_data_.push_back(ev.raw); - stream.ev_spans_.push_back(stream.ev_data_.size()); - } - - arb_assert(num_events == stream.ev_data_.size()); - arb_assert(staged.size() + 1 == stream.ev_spans_.size()); - stream.init(); - } }; } // namespace arb diff --git a/arbor/backends/gpu/event_stream.hpp b/arbor/backends/gpu/event_stream.hpp index 3260fbace..0045d9381 100644 --- a/arbor/backends/gpu/event_stream.hpp +++ b/arbor/backends/gpu/event_stream.hpp @@ -2,33 +2,113 @@ // Indexed collection of pop-only event queues --- CUDA back-end implementation. +#include + #include "backends/event_stream_base.hpp" +#include "util/transform.hpp" +#include "threading/threading.hpp" +#include "timestep_range.hpp" #include "memory/memory.hpp" namespace arb { namespace gpu { -template -struct event_stream : BaseEventStream { - public: - ARB_SERDES_ENABLE(event_stream, - ev_data_, - ev_spans_, - device_ev_data_, - index_); +template +struct event_stream: public event_stream_base { +public: + using base = event_stream_base; + using size_type = typename base::size_type; + using event_data_type = typename base::event_data_type; + using device_array = memory::device_vector; + + using base::clear; + using base::ev_data_; + using base::ev_spans_; + using base::base_ptr_; + + event_stream() = default; + event_stream(task_system_handle t): base(), thread_pool_{t} {} + + // Initialize event streams from a vector of vector of events + // Outer vector represents time step bins + void init(const std::vector>& staged) { + // clear previous data + clear(); + + // return if there are no timestep bins + if (!staged.size()) return; + + // return if there are no events + const size_type num_events = util::sum_by(staged, [] (const auto& v) {return v.size();}); + if (!num_events) return; + + // allocate space for spans and data + ev_spans_.resize(staged.size() + 1); + ev_data_.resize(num_events); + resize(device_ev_data_, num_events); + + // compute offsets by exclusive scan over staged events + util::make_partition(ev_spans_, + util::transform_view(staged, [](const auto& v) { return v.size(); }), + 0ull); + + // assign, copy to device (and potentially sort) the event data in parallel + arb_assert(thread_pool_); + arb_assert(ev_spans_.size() == staged.size() + 1); + threading::parallel_for::apply(0, ev_spans_.size() - 1, thread_pool_.get(), + [this, &staged](size_type i) { + const auto beg = ev_spans_[i]; + const auto end = ev_spans_[i + 1]; + arb_assert(end >= beg); + const auto len = end - beg; + + auto host_span = memory::make_view(ev_data_)(beg, end); - protected: - void init() override final { - resize(this->device_ev_data_, this->ev_data_.size()); - memory::copy_async(this->ev_data_, this->device_ev_data_); - this->base_ptr_ = this->device_ev_data_.data(); + // make event data and copy + std::copy_n(util::transform_view(staged[i], + [](const auto& x) { return event_data(x); }).begin(), + len, + host_span.begin()); + // sort if necessary + if constexpr (has_event_index::value) { + util::stable_sort_by(host_span, + [](const event_data_type& ed) { return event_index(ed); }); + } + // copy to device + auto device_span = memory::make_view(device_ev_data_)(beg, end); + memory::copy_async(host_span, device_span); + }); + + base_ptr_ = device_ev_data_.data(); + + arb_assert(num_events == device_ev_data_.size()); + arb_assert(num_events == ev_data_.size()); } - private: // device memory - using event_data_type = typename BaseEventStream::event_data_type; - using device_array = memory::device_vector; + // Initialize event stream assuming ev_data_ and ev_span_ has + // been set previously (e.g. by `base::multi_event_stream`) + void init() { + resize(device_ev_data_, ev_data_.size()); + base_ptr_ = device_ev_data_.data(); - device_array device_ev_data_; + threading::parallel_for::apply(0, ev_spans_.size() - 1, thread_pool_.get(), + [this](size_type i) { + const auto beg = ev_spans_[i]; + const auto end = ev_spans_[i + 1]; + arb_assert(end >= beg); + + auto host_span = memory::make_view(ev_data_)(beg, end); + auto device_span = memory::make_view(device_ev_data_)(beg, end); + + // sort if necessary + if constexpr (has_event_index::value) { + util::stable_sort_by(host_span, + [](const event_data_type& ed) { return event_index(ed); }); + } + // copy to device + memory::copy_async(host_span, device_span); + }); + } template static void resize(D& d, std::size_t size) { @@ -37,10 +117,16 @@ struct event_stream : BaseEventStream { d = D(size); } } -}; -using spike_event_stream = event_stream; -using sample_event_stream = event_stream; + ARB_SERDES_ENABLE(event_stream, + ev_data_, + ev_spans_, + device_ev_data_, + index_); + + task_system_handle thread_pool_; + device_array device_ev_data_; +}; } // namespace gpu } // namespace arb diff --git a/arbor/backends/gpu/forest.cpp b/arbor/backends/gpu/forest.cpp index ec3c30e68..cc146af02 100644 --- a/arbor/backends/gpu/forest.cpp +++ b/arbor/backends/gpu/forest.cpp @@ -1,6 +1,5 @@ #include "backends/gpu/forest.hpp" #include "util/span.hpp" -#include namespace arb { namespace gpu { diff --git a/arbor/backends/gpu/fvm.hpp b/arbor/backends/gpu/fvm.hpp index 96de41dab..eaac5ab2b 100644 --- a/arbor/backends/gpu/fvm.hpp +++ b/arbor/backends/gpu/fvm.hpp @@ -1,11 +1,16 @@ #pragma once +#include #include #include #include #include "memory/memory.hpp" +#include "util/rangeutil.hpp" + +#include "backends/event.hpp" + #include "backends/gpu/gpu_store_types.hpp" #include "backends/gpu/shared_state.hpp" @@ -15,6 +20,7 @@ namespace arb { namespace gpu { struct backend { + static bool is_supported() { return true; } static std::string name() { return "gpu"; } using value_type = arb_value_type; @@ -37,6 +43,8 @@ struct backend { using threshold_watcher = arb::gpu::threshold_watcher; using cable_solver = arb::gpu::matrix_state_fine; using diffusion_solver = arb::gpu::diffusion_state; + using deliverable_event_stream = arb::gpu::deliverable_event_stream; + using sample_event_stream = arb::gpu::sample_event_stream; using shared_state = arb::gpu::shared_state; using ion_state = arb::gpu::ion_state; diff --git a/arbor/backends/gpu/gpu_store_types.hpp b/arbor/backends/gpu/gpu_store_types.hpp index b8ce5369d..386f8bac7 100644 --- a/arbor/backends/gpu/gpu_store_types.hpp +++ b/arbor/backends/gpu/gpu_store_types.hpp @@ -18,6 +18,9 @@ using array = memory::device_vector; using iarray = memory::device_vector; using sarray = memory::device_vector; +using deliverable_event_stream = arb::gpu::event_stream; +using sample_event_stream = arb::gpu::event_stream; + } // namespace gpu } // namespace arb diff --git a/arbor/backends/gpu/matrix_state_fine.hpp b/arbor/backends/gpu/matrix_state_fine.hpp index f052db0d9..714e1f399 100644 --- a/arbor/backends/gpu/matrix_state_fine.hpp +++ b/arbor/backends/gpu/matrix_state_fine.hpp @@ -3,6 +3,7 @@ #include #include +#include #include @@ -36,11 +37,15 @@ struct matrix_state_fine { array rhs; // [nA] // Required for matrix assembly + array cv_area; // [μm^2] array cv_capacitance; // [pF] // Invariant part of the matrix diagonal array invariant_d; // [μS] + // Solution in unpacked format + array solution_; + // Maximum number of branches in each level per block unsigned max_branches_per_level; @@ -77,13 +82,16 @@ struct matrix_state_fine { // `solver_format[perm[i]] = external_format[i]` iarray perm; + matrix_state_fine() = default; // constructor for fine-grained matrix. matrix_state_fine(const std::vector& p, - const std::vector& cell_cv_divs, - const std::vector& cap, - const std::vector& face_conductance) { + const std::vector& cell_cv_divs, + const std::vector& cap, + const std::vector& face_conductance, + const std::vector& area) + { using util::make_span; constexpr unsigned npos = unsigned(-1); @@ -352,6 +360,7 @@ struct matrix_state_fine { // cv_capacitance : flat // invariant_d : flat // cv_to_cell : flat + // area : flat // the invariant part of d is stored in in flat form std::vector invariant_d_tmp(matrix_size, 0); @@ -377,6 +386,9 @@ struct matrix_state_fine { // transform u_shuffled values into packed u vector. flat_to_packed(u_shuffled, u); + // the invariant part of d and cv_area are in flat form + cv_area = memory::make_const_view(area); + // the cv_capacitance can be copied directly because it is // to be stored in flat format cv_capacitance = memory::make_const_view(cap); @@ -396,18 +408,19 @@ struct matrix_state_fine { // voltage [mV] // current density [A/m²] // conductivity [kS/m²] - void assemble(const T dt, const_view voltage, const_view current, const_view conductivity, const_view area_um2) { - assemble_matrix_fine(d.data(), - rhs.data(), - invariant_d.data(), - voltage.data(), - current.data(), - conductivity.data(), - cv_capacitance.data(), - area_um2.data(), - dt, - perm.data(), - size()); + void assemble(const T dt, const_view voltage, const_view current, const_view conductivity) { + assemble_matrix_fine( + d.data(), + rhs.data(), + invariant_d.data(), + voltage.data(), + current.data(), + conductivity.data(), + cv_capacitance.data(), + cv_area.data(), + dt, + perm.data(), + size()); } void solve(array& to) { @@ -428,8 +441,8 @@ struct matrix_state_fine { void solve(array& voltage, - const T dt, const_view current, const_view conductivity, const_view area_um2) { - assemble(dt, voltage, current, conductivity, area_um2); + const T dt, const_view current, const_view conductivity) { + assemble(dt, voltage, current, conductivity); solve(voltage); } diff --git a/arbor/backends/gpu/shared_state.cpp b/arbor/backends/gpu/shared_state.cpp index b7f427710..928c938ba 100644 --- a/arbor/backends/gpu/shared_state.cpp +++ b/arbor/backends/gpu/shared_state.cpp @@ -46,31 +46,32 @@ std::pair minmax_value_impl(arb_size_type n, con ion_state::ion_state(const fvm_ion_config& ion_data, unsigned, // alignment/padding ignored. solver_ptr ptr): - flags_(ion_data), + write_eX_(ion_data.revpot_written), + write_Xo_(ion_data.econc_written), + write_Xi_(ion_data.iconc_written), node_index_(make_const_view(ion_data.cv)), iX_(ion_data.cv.size(), NAN), + eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end()), + Xi_(ion_data.init_iconc.begin(), ion_data.init_iconc.end()), + Xd_(ion_data.cv.size(), NAN), + Xo_(ion_data.init_econc.begin(), ion_data.init_econc.end()), gX_(ion_data.cv.size(), NAN), + init_Xi_(make_const_view(ion_data.init_iconc)), + init_Xo_(make_const_view(ion_data.init_econc)), + reset_Xi_(make_const_view(ion_data.reset_iconc)), + reset_Xo_(make_const_view(ion_data.reset_econc)), + init_eX_(make_const_view(ion_data.init_revpot)), charge(1u, static_cast(ion_data.charge)), solver(std::move(ptr)) { - if (flags_.reset_xi() - ||flags_.reset_xd()) reset_Xi_ = make_const_view(ion_data.reset_iconc); - if (flags_.reset_xi()) init_Xi_ = make_const_view(ion_data.init_iconc); - if (flags_.xi()) Xi_ = make_const_view(ion_data.init_iconc); - - if (flags_.reset_xo()) reset_Xo_ = make_const_view(ion_data.reset_econc); - if (flags_.reset_xo()) init_Xo_ = make_const_view(ion_data.init_econc); - if (flags_.xo()) Xo_ = make_const_view(ion_data.init_econc); - - if (flags_.reset_ex()) init_eX_ = make_const_view(ion_data.init_revpot); - if (flags_.ex()) eX_ = make_const_view(ion_data.init_revpot); - - if (flags_.xd()) Xd_ = make_const_view(ion_data.reset_iconc); + arb_assert(node_index_.size()==init_Xi_.size()); + arb_assert(node_index_.size()==init_Xo_.size()); + arb_assert(node_index_.size()==init_eX_.size()); } void ion_state::init_concentration() { // NB. not resetting Xd here, it's controlled via the solver. - if (flags_.reset_xi()) memory::copy(init_Xi_, Xi_); - if (flags_.reset_xo()) memory::copy(init_Xo_, Xo_); + if (write_Xi_) memory::copy(init_Xi_, Xi_); + if (write_Xo_) memory::copy(init_Xo_, Xo_); } void ion_state::zero_current() { @@ -80,10 +81,10 @@ void ion_state::zero_current() { void ion_state::reset() { zero_current(); - if (flags_.reset_xi()) memory::copy(reset_Xi_, Xi_); - if (flags_.reset_xo()) memory::copy(reset_Xo_, Xo_); - if (flags_.reset_ex()) memory::copy(init_eX_, eX_); - if (flags_.reset_xd()) memory::copy(reset_Xi_, Xd_); + memory::copy(reset_Xi_, Xd_); + if (write_Xi_) memory::copy(reset_Xi_, Xi_); + if (write_Xo_) memory::copy(reset_Xo_, Xo_); + if (write_eX_) memory::copy(init_eX_, eX_); } // istim_state methods: @@ -191,7 +192,7 @@ shared_state::shared_state(task_system_handle tp, time_since_spike(n_cell*n_detector), src_to_spike(make_const_view(src_to_spike_)), cbprng_seed(cbprng_seed_), - sample_events(), + sample_events(thread_pool), watcher{n_cv_, src_to_spike.data(), detector_info} { memory::fill(time_since_spike, -1.0); @@ -239,7 +240,7 @@ void shared_state::instantiate(mechanism& m, if (storage.count(id)) throw arb::arbor_internal_error("Duplicate mech id in shared state"); auto& store = storage.emplace(id, mech_storage{}).first->second; - streams[id] = spike_event_stream{}; + streams[id] = deliverable_event_stream{thread_pool}; // Allocate view pointers store.state_vars_ = std::vector(m.mech_.n_state_vars); @@ -387,6 +388,14 @@ void shared_state::take_samples() { } } +void shared_state::init_events(const event_lane_subrange& lanes, + const std::vector& handles, + const std::vector& divs, + const timestep_range& dts) { + arb::gpu::event_stream::multi_event_stream(lanes, handles, divs, dts, streams); +} + + // Debug interface ARB_ARBOR_API std::ostream& operator<<(std::ostream& o, shared_state& s) { using io::csv; diff --git a/arbor/backends/gpu/shared_state.hpp b/arbor/backends/gpu/shared_state.hpp index 39f330202..030cbcdab 100644 --- a/arbor/backends/gpu/shared_state.hpp +++ b/arbor/backends/gpu/shared_state.hpp @@ -24,6 +24,7 @@ namespace arb { namespace gpu { + /* * Ion state fields correspond to NMODL ion variables, where X * is replaced with the name of the ion. E.g. for calcium 'ca': @@ -39,23 +40,25 @@ struct ARB_ARBOR_API ion_state { using solver_type = arb::gpu::diffusion_state; using solver_ptr = std::unique_ptr; - ion_data_flags flags_; // Track what and when to reset / allocate + bool write_eX_; // is eX written? + bool write_Xo_; // is Xo written? + bool write_Xi_; // is Xi written? - iarray node_index_; // Instance to CV map. - array iX_; // (A/m²) current density - array eX_; // (mV) reversal potential - array Xi_; // (mM) internal concentration - array Xd_; // (mM) diffusive concentration - array Xo_; // (mM) external concentration - array gX_; // (kS/m²) per-species conductivity + iarray node_index_; // Instance to CV map. + array iX_; // (A/m²) current density + array eX_; // (mV) reversal potential + array Xi_; // (mM) internal concentration + array Xd_; // (mM) diffusive concentration + array Xo_; // (mM) external concentration + array gX_; // (kS/m²) per-species conductivity - array init_Xi_; // (mM) area-weighted initial internal concentration - array init_Xo_; // (mM) area-weighted initial external concentration - array reset_Xi_; // (mM) area-weighted user-set internal concentration - array reset_Xo_; // (mM) area-weighted user-set internal concentration - array init_eX_; // (mM) initial reversal potential + array init_Xi_; // (mM) area-weighted initial internal concentration + array init_Xo_; // (mM) area-weighted initial external concentration + array reset_Xi_; // (mM) area-weighted user-set internal concentration + array reset_Xo_; // (mM) area-weighted user-set internal concentration + array init_eX_; // (mM) initial reversal potential - array charge; // charge of ionic species (global, length 1) + array charge; // charge of ionic species (global, length 1) solver_ptr solver = nullptr; @@ -166,7 +169,7 @@ struct ARB_ARBOR_API shared_state: shared_state_base ion_data; std::unordered_map storage; - std::unordered_map streams; + std::unordered_map streams; shared_state() = default; @@ -237,6 +240,11 @@ struct ARB_ARBOR_API shared_state: shared_state_base& handles, + const std::vector& divs, + const timestep_range& dts); }; // For debugging only diff --git a/arbor/backends/multicore/cable_solver.hpp b/arbor/backends/multicore/cable_solver.hpp index bc748486a..3622ee688 100644 --- a/arbor/backends/multicore/cable_solver.hpp +++ b/arbor/backends/multicore/cable_solver.hpp @@ -23,6 +23,7 @@ struct cable_solver { array d; // [μS] array u; // [μS] array cv_capacitance; // [pF] + array cv_area; // [μm^2] array invariant_d; // [μS] invariant part of matrix diagonal cable_solver() = default; @@ -35,11 +36,13 @@ struct cable_solver { cable_solver(const std::vector& p, const std::vector& cell_cv_divs, const std::vector& cap, - const std::vector& cond): + const std::vector& cond, + const std::vector& area): parent_index(p.begin(), p.end()), cell_cv_divs(cell_cv_divs.begin(), cell_cv_divs.end()), d(size(), 0), u(size(), 0), cv_capacitance(cap.begin(), cap.end()), + cv_area(area.begin(), area.end()), invariant_d(size(), 0) { // Sanity check @@ -64,7 +67,7 @@ struct cable_solver { // * expects the voltage from its first argument // * will likewise overwrite the first argument with the solction template - void solve(T& rhs, const value_type dt, const_view current, const_view conductivity, const_view cv_area) { + void solve(T& rhs, const value_type dt, const_view current, const_view conductivity) { value_type * const ARB_NO_ALIAS d_ = d.data(); value_type * const ARB_NO_ALIAS r_ = rhs.data(); diff --git a/arbor/backends/multicore/event_stream.hpp b/arbor/backends/multicore/event_stream.hpp index a72d4a518..f280777db 100644 --- a/arbor/backends/multicore/event_stream.hpp +++ b/arbor/backends/multicore/event_stream.hpp @@ -2,26 +2,61 @@ // Indexed collection of pop-only event queues --- multicore back-end implementation. +#include "arbor/spike_event.hpp" #include "backends/event_stream_base.hpp" +#include "timestep_range.hpp" namespace arb { namespace multicore { -template -struct event_stream : BaseEventStream { - public: - ARB_SERDES_ENABLE(event_stream, +template +struct event_stream: public event_stream_base { + using base = event_stream_base; + using size_type = typename base::size_type; + + using base::clear; + using base::ev_spans_; + using base::ev_data_; + using base::base_ptr_; + + event_stream() = default; + + // Initialize event stream from a vector of vector of events + // Outer vector represents time step bins + void init(const std::vector>& staged) { + // clear previous data + clear(); + + // return if there are no timestep bins + if (!staged.size()) return; + + // return if there are no events + const size_type num_events = util::sum_by(staged, [] (const auto& v) {return v.size();}); + if (!num_events) return; + + // allocate space for spans and data + ev_spans_.reserve(staged.size() + 1); + ev_data_.reserve(num_events); + + // add event data and spans + for (const auto& v : staged) { + for (const auto& ev: v) ev_data_.push_back(event_data(ev)); + ev_spans_.push_back(ev_data_.size()); + } + + arb_assert(num_events == ev_data_.size()); + arb_assert(staged.size() + 1 == ev_spans_.size()); + base_ptr_ = ev_data_.data(); + } + + // Initialize event stream assuming ev_data_ and ev_span_ has + // been set previously (e.g. by `base::multi_event_stream`) + void init() { base_ptr_ = ev_data_.data(); } + + ARB_SERDES_ENABLE(event_stream, ev_data_, ev_spans_, index_); - protected: - void init() override final { - this->base_ptr_ = this->ev_data_.data(); - } }; - -using spike_event_stream = event_stream; -using sample_event_stream = event_stream; - } // namespace multicore } // namespace arb diff --git a/arbor/backends/multicore/fvm.hpp b/arbor/backends/multicore/fvm.hpp index 89e724211..be787cbcc 100644 --- a/arbor/backends/multicore/fvm.hpp +++ b/arbor/backends/multicore/fvm.hpp @@ -16,6 +16,7 @@ namespace arb { namespace multicore { struct backend { + static bool is_supported() { return true; } static std::string name() { return "cpu"; } using value_type = arb_value_type; @@ -38,6 +39,8 @@ struct backend { using cable_solver = arb::multicore::cable_solver; using diffusion_solver = arb::multicore::diffusion_solver; using threshold_watcher = arb::multicore::threshold_watcher; + using deliverable_event_stream = arb::multicore::deliverable_event_stream; + using sample_event_stream = arb::multicore::sample_event_stream; using shared_state = arb::multicore::shared_state; using ion_state = arb::multicore::ion_state; diff --git a/arbor/backends/multicore/multicore_common.hpp b/arbor/backends/multicore/multicore_common.hpp index fca714c45..9186628eb 100644 --- a/arbor/backends/multicore/multicore_common.hpp +++ b/arbor/backends/multicore/multicore_common.hpp @@ -23,6 +23,9 @@ using padded_vector = std::vector>; using array = padded_vector; using iarray = padded_vector; +using deliverable_event_stream = arb::multicore::event_stream; +using sample_event_stream = arb::multicore::event_stream; + } // namespace multicore } // namespace arb diff --git a/arbor/backends/multicore/shared_state.cpp b/arbor/backends/multicore/shared_state.cpp index c55c762a1..17a57789f 100644 --- a/arbor/backends/multicore/shared_state.cpp +++ b/arbor/backends/multicore/shared_state.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include #include @@ -54,31 +55,33 @@ ion_state::ion_state(const fvm_ion_config& ion_data, unsigned align, solver_ptr ptr): alignment(min_alignment(align)), - flags_{ion_data}, + write_eX_(ion_data.revpot_written), + write_Xo_(ion_data.econc_written), + write_Xi_(ion_data.iconc_written), node_index_(ion_data.cv.begin(), ion_data.cv.end(), pad(alignment)), iX_(ion_data.cv.size(), NAN, pad(alignment)), + eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)), + Xi_(ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)), + Xd_(ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)), + Xo_(ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)), gX_(ion_data.cv.size(), NAN, pad(alignment)), + init_Xi_(ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)), + init_Xo_(ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)), + reset_Xi_(ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)), + reset_Xo_(ion_data.reset_econc.begin(), ion_data.reset_econc.end(), pad(alignment)), + init_eX_(ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)), charge(1u, ion_data.charge, pad(alignment)), solver(std::move(ptr)) { - if (flags_.reset_xi() - ||flags_.reset_xd()) reset_Xi_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; - if (flags_.reset_xi()) init_Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; - if (flags_.xi()) Xi_ = {ion_data.init_iconc.begin(), ion_data.init_iconc.end(), pad(alignment)}; - - if (flags_.reset_xo()) reset_Xo_ = {ion_data.reset_econc.begin(), ion_data.reset_econc.end(), pad(alignment)}; - if (flags_.reset_xo()) init_Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; - if (flags_.xo()) Xo_ = {ion_data.init_econc.begin(), ion_data.init_econc.end(), pad(alignment)}; - - if (flags_.reset_ex()) init_eX_ = {ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)}; - if (flags_.ex()) eX_ = {ion_data.init_revpot.begin(), ion_data.init_revpot.end(), pad(alignment)}; - - if (flags_.xd()) Xd_ = {ion_data.reset_iconc.begin(), ion_data.reset_iconc.end(), pad(alignment)}; + arb_assert(node_index_.size()==init_Xi_.size()); + arb_assert(node_index_.size()==init_Xo_.size()); + arb_assert(node_index_.size()==eX_.size()); + arb_assert(node_index_.size()==init_eX_.size()); } void ion_state::init_concentration() { // NB. not resetting Xd here, it's controlled via the solver. - if (flags_.reset_xi()) std::copy(init_Xi_.begin(), init_Xi_.end(), Xi_.begin()); - if (flags_.reset_xo()) std::copy(init_Xo_.begin(), init_Xo_.end(), Xo_.begin()); + if (write_Xi_) std::copy(init_Xi_.begin(), init_Xi_.end(), Xi_.begin()); + if (write_Xo_) std::copy(init_Xo_.begin(), init_Xo_.end(), Xo_.begin()); } void ion_state::zero_current() { @@ -88,10 +91,10 @@ void ion_state::zero_current() { void ion_state::reset() { zero_current(); - if (flags_.reset_xi()) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xi_.begin()); - if (flags_.reset_xo()) std::copy(reset_Xo_.begin(), reset_Xo_.end(), Xo_.begin()); - if (flags_.reset_ex()) std::copy(init_eX_.begin(), init_eX_.end(), eX_.begin()); - if (flags_.reset_xd()) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xd_.begin()); + std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xd_.begin()); + if (write_Xi_) std::copy(reset_Xi_.begin(), reset_Xi_.end(), Xi_.begin()); + if (write_Xo_) std::copy(reset_Xo_.begin(), reset_Xo_.end(), Xo_.begin()); + if (write_eX_) std::copy(init_eX_.begin(), init_eX_.end(), eX_.begin()); } // istim_state methods: @@ -379,7 +382,7 @@ void shared_state::instantiate(arb::mechanism& m, util::padded_allocator<> pad(m.data_alignment()); if (storage.count(id)) throw arbor_internal_error("Duplicate mechanism id in MC shared state."); - streams[id] = spike_event_stream{}; + streams[id] = deliverable_event_stream{}; auto& store = storage[id]; auto width = pos_data.cv.size(); // Assign non-owning views onto shared state: @@ -532,5 +535,13 @@ void shared_state::instantiate(arb::mechanism& m, } } +void shared_state::init_events(const event_lane_subrange& lanes, + const std::vector& handles, + const std::vector& divs, + const timestep_range& dts) { + arb::multicore::event_stream::multi_event_stream(lanes, handles, divs, dts, streams); +} + + } // namespace multicore } // namespace arb diff --git a/arbor/backends/multicore/shared_state.hpp b/arbor/backends/multicore/shared_state.hpp index 3674d635d..99bbe37b5 100644 --- a/arbor/backends/multicore/shared_state.hpp +++ b/arbor/backends/multicore/shared_state.hpp @@ -48,25 +48,27 @@ struct ARB_ARBOR_API ion_state { using solver_type = diffusion_solver; using solver_ptr = std::unique_ptr; - unsigned alignment = 1; // Alignment and padding multiple. + unsigned alignment = 1; // Alignment and padding multiple. - ion_data_flags flags_; // Track what and when to reset / allocate + bool write_eX_; // is eX written? + bool write_Xo_; // is Xo written? + bool write_Xi_; // is Xi written? - iarray node_index_; // Instance to CV map. - array iX_; // (A/m²) current density - array eX_; // (mV) reversal potential - array Xi_; // (mM) internal concentration - array Xd_; // (mM) diffusive internal concentration - array Xo_; // (mM) external concentration - array gX_; // (kS/m²) per-species conductivity + iarray node_index_; // Instance to CV map. + array iX_; // (A/m²) current density + array eX_; // (mV) reversal potential + array Xi_; // (mM) internal concentration + array Xd_; // (mM) diffusive internal concentration + array Xo_; // (mM) external concentration + array gX_; // (kS/m²) per-species conductivity - array init_Xi_; // (mM) area-weighted initial internal concentration - array init_Xo_; // (mM) area-weighted initial external concentration - array reset_Xi_; // (mM) area-weighted user-set internal concentration - array reset_Xo_; // (mM) area-weighted user-set internal concentration - array init_eX_; // (mV) initial reversal potential + array init_Xi_; // (mM) area-weighted initial internal concentration + array init_Xo_; // (mM) area-weighted initial external concentration + array reset_Xi_; // (mM) area-weighted user-set internal concentration + array reset_Xo_; // (mM) area-weighted user-set internal concentration + array init_eX_; // (mV) initial reversal potential - array charge; // charge of ionic species (global value, length 1) + array charge; // charge of ionic species (global value, length 1) solver_ptr solver = nullptr; @@ -173,7 +175,7 @@ struct ARB_ARBOR_API shared_state: istim_state stim_data; std::unordered_map ion_data; std::unordered_map storage; - std::unordered_map streams; + std::unordered_map streams; shared_state() = default; @@ -243,6 +245,11 @@ struct ARB_ARBOR_API shared_state: sample_time_host = util::range_pointer_view(sample_time); sample_value_host = util::range_pointer_view(sample_value); } + + void init_events(const event_lane_subrange& lanes, + const std::vector& handles, + const std::vector& divs, + const timestep_range& dts); }; // For debugging only: diff --git a/arbor/backends/shared_state_base.hpp b/arbor/backends/shared_state_base.hpp index 0c1742a0d..c20247d36 100644 --- a/arbor/backends/shared_state_base.hpp +++ b/arbor/backends/shared_state_base.hpp @@ -7,9 +7,8 @@ #include "backends/common_types.hpp" #include "fvm_layout.hpp" -#include "util/rangeutil.hpp" -#include "timestep_range.hpp" #include "event_lane.hpp" +#include "timestep_range.hpp" namespace arb { @@ -35,21 +34,21 @@ struct shared_state_base { const std::vector& divs) { auto d = static_cast(this); // events - initialize(lanes, handles, divs, dts, d->streams); + d->init_events(lanes, handles, divs, dts); // samples auto n_samples = util::sum_by(samples, [] (const auto& s) {return s.size();}); if (d->sample_time.size() < n_samples) { d->sample_time = array(n_samples); d->sample_value = array(n_samples); } - initialize(samples, d->sample_events); + d->sample_events.init(samples); // thresholds d->watcher.clear_crossings(); } void configure_solver(const fvm_cv_discretization& disc) { auto d = static_cast(this); - d->solver = {disc.geometry.cv_parent, disc.geometry.cell_cv_divs, disc.cv_capacitance, disc.face_conductance}; + d->solver = {disc.geometry.cv_parent, disc.geometry.cell_cv_divs, disc.cv_capacitance, disc.face_conductance, disc.cv_area}; } void add_ion(const std::string& ion_name, @@ -135,7 +134,7 @@ struct shared_state_base { void integrate_cable_state() { auto d = static_cast(this); - d->solver.solve(d->voltage, d->dt, d->current_density, d->conductivity, d->area_um2); + d->solver.solve(d->voltage, d->dt, d->current_density, d->conductivity); for (auto& [ion, data]: d->ion_data) { if (data.solver) { data.solver->solve(data.Xd_, diff --git a/arbor/cable_cell.cpp b/arbor/cable_cell.cpp index 41e9c892a..b956b2db9 100644 --- a/arbor/cable_cell.cpp +++ b/arbor/cable_cell.cpp @@ -83,22 +83,18 @@ struct cable_cell_impl { // The decorations on the cell. decor decorations; - // Discretization - std::optional discretization_; - // The placeable label to lid_range map dynamic_typed_map>::type> labeled_lid_ranges; - cable_cell_impl(const arb::morphology& m, const label_dict& labels, const decor& decorations, const std::optional& cvp): + cable_cell_impl(const arb::morphology& m, const label_dict& labels, const decor& decorations): provider(m, labels), dictionary(labels), - decorations(decorations), - discretization_{cvp} + decorations(decorations) { init(); } - cable_cell_impl(): cable_cell_impl({}, {}, {}, {}) {} + cable_cell_impl(): cable_cell_impl({},{},{}) {} cable_cell_impl(const cable_cell_impl& other) = default; @@ -126,7 +122,7 @@ struct cable_cell_impl { cell_lid_type first = lid; for (const auto& loc: locs) { - placed p{loc, lid++, item, label}; + placed p{loc, lid++, item}; mm.push_back(p); } auto range = lid_range(first, lid); @@ -207,10 +203,6 @@ struct cable_cell_impl { } }; -const std::optional& cable_cell::discretization() const { return impl_->discretization_; } -void cable_cell::discretization(cv_policy cvp) { impl_->discretization_ = std::move(cvp); } - - using impl_ptr = std::unique_ptr; impl_ptr make_impl(cable_cell_impl* c) { return impl_ptr(c, [](cable_cell_impl* p){delete p;}); @@ -240,8 +232,8 @@ void cable_cell_impl::init() { } } -cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary, const std::optional& cvp): - impl_(make_impl(new cable_cell_impl(m, dictionary, decorations, cvp))) +cable_cell::cable_cell(const arb::morphology& m, const decor& decorations, const label_dict& dictionary): + impl_(make_impl(new cable_cell_impl(m, dictionary, decorations))) {} cable_cell::cable_cell(): impl_(make_impl(new cable_cell_impl())) {} diff --git a/arbor/cable_cell_param.cpp b/arbor/cable_cell_param.cpp index 34fcd7e6e..a5c4e2079 100644 --- a/arbor/cable_cell_param.cpp +++ b/arbor/cable_cell_param.cpp @@ -105,6 +105,11 @@ std::vector cable_cell_parameter_set::serialize() const { for (const auto& [name, mech]: reversal_potential_method) { D.push_back(ion_reversal_potential_method{name, mech}); } + + if (discretization) { + D.push_back(*discretization); + } + return D; } @@ -158,6 +163,9 @@ decor& decor::set_default(defaultable what) { else if constexpr (std::is_same_v) { defaults_.reversal_potential_method[p.ion] = p.method; } + else if constexpr (std::is_same_v) { + defaults_.discretization = std::forward(p); + } else if constexpr (std::is_same_v) { if (p.scale.type() != iexpr_type::scalar) throw cable_cell_error{"Default values cannot have a scale."}; auto s = p.scale.get_scalar(); diff --git a/arbor/cv_policy.cpp b/arbor/cv_policy.cpp index 039d9d9d9..fcd817054 100644 --- a/arbor/cv_policy.cpp +++ b/arbor/cv_policy.cpp @@ -8,244 +8,194 @@ #include #include "util/rangeutil.hpp" +#include "util/span.hpp" -namespace arb { - -static std::string print_flag(cv_policy_flag flag) { - switch (flag) { - case arb::cv_policy_flag::none: return "(flag-none)"; - case arb::cv_policy_flag::interior_forks: return "(flag-interior-forks)"; - } - throw std::runtime_error("UNREACHABLE"); -} - -static bool has_flag(cv_policy_flag lhs, cv_policy_flag rhs) { - return static_cast(static_cast(lhs) & static_cast(rhs)); -} +// Discretization policy implementations: +namespace arb { static auto unique_sum = [](auto&&... lss) { return ls::support(sum(std::forward(lss)...)); }; -struct cvp_cv_policy_plus { - locset cv_boundary_points(const cable_cell& c) const { +// Combinators: +// cv_policy_plus_ represents the result of operator+, +// cv_policy_bar_ represents the result of operator|. + +struct cv_policy_plus_: cv_policy_base { + cv_policy_plus_(const cv_policy& lhs, const cv_policy& rhs): + lhs_(lhs), rhs_(rhs) {} + + cv_policy_base_ptr clone() const override { + return cv_policy_base_ptr(new cv_policy_plus_(*this)); + } + + locset cv_boundary_points(const cable_cell& c) const override { return unique_sum(lhs_.cv_boundary_points(c), rhs_.cv_boundary_points(c)); } - region domain() const { return join(lhs_.domain(), rhs_.domain()); } + region domain() const override { return join(lhs_.domain(), rhs_.domain()); } - std::ostream& format(std::ostream& os) const { + std::ostream& print(std::ostream& os) override { os << "(join " << lhs_ << ' ' << rhs_ << ')'; return os; } - // TODO This is needed seemingly only for older compilers - cvp_cv_policy_plus(cv_policy lhs, cv_policy rhs): lhs_{std::move(lhs)}, rhs_(std::move(rhs)) {} - cv_policy lhs_, rhs_; }; ARB_ARBOR_API cv_policy operator+(const cv_policy& lhs, const cv_policy& rhs) { - return cv_policy{cvp_cv_policy_plus(lhs, rhs)}; + return cv_policy_plus_(lhs, rhs); } -struct cvp_cv_policy_bar { - locset cv_boundary_points(const cable_cell& c) const { - return unique_sum(ls::restrict_to(lhs_.cv_boundary_points(c), - complement(rhs_.domain())), - rhs_.cv_boundary_points(c)); +struct cv_policy_bar_: cv_policy_base { + cv_policy_bar_(const cv_policy& lhs, const cv_policy& rhs): + lhs_(lhs), rhs_(rhs) {} + + cv_policy_base_ptr clone() const override { + return cv_policy_base_ptr(new cv_policy_bar_(*this)); } - region domain() const { return join(lhs_.domain(), rhs_.domain()); } + locset cv_boundary_points(const cable_cell& c) const override { + return unique_sum(ls::restrict_to(lhs_.cv_boundary_points(c), complement(rhs_.domain())), rhs_.cv_boundary_points(c)); + } + + region domain() const override { return join(lhs_.domain(), rhs_.domain()); } - std::ostream& format(std::ostream& os) const { + std::ostream& print(std::ostream& os) override { os << "(replace " << lhs_ << ' ' << rhs_ << ')'; return os; } - // TODO This is needed seemingly only for older compilers - cvp_cv_policy_bar(cv_policy lhs, cv_policy rhs): lhs_{std::move(lhs)}, rhs_(std::move(rhs)) {} - cv_policy lhs_, rhs_; }; ARB_ARBOR_API cv_policy operator|(const cv_policy& lhs, const cv_policy& rhs) { - return cv_policy{cvp_cv_policy_bar(lhs, rhs)}; -} - -struct cvp_cv_policy_max_extent { - double max_extent_; - region domain_; - cv_policy_flag flags_; - - std::ostream& format(std::ostream& os) const { - os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << print_flag(flags_) << ')'; - return os; - } - - locset cv_boundary_points(const cable_cell& cell) const { - const unsigned nbranch = cell.morphology().num_branches(); - const auto& embed = cell.embedding(); - if (!nbranch || max_extent_<=0) return ls::nil(); - - std::vector points; - double oomax_extent = 1./max_extent_; - auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); - - for (auto& comp: comps) { - for (mcable c: comp) { - double cable_length = embed.integrate_length(c); - unsigned ncv = std::ceil(cable_length*oomax_extent); - double scale = (c.dist_pos-c.prox_pos)/ncv; - - if (has_flag(flags_, cv_policy_flag::interior_forks)) { - for (unsigned i = 0; i points; + double oomax_extent = 1./max_extent_; + auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); + + for (auto& comp: comps) { + for (mcable c: comp) { + double cable_length = embed.integrate_length(c); + unsigned ncv = std::ceil(cable_length*oomax_extent); + double scale = (c.dist_pos-c.prox_pos)/ncv; - locset cv_boundary_points(const cable_cell&) const { - return ls::cboundary(domain_); + if (flags_&cv_policy_flag::interior_forks) { + for (unsigned i = 0; i points; - double ooncv = 1./cv_per_branch_; - auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); + std::vector points; + double ooncv = 1./cv_per_branch_; + auto comps = components(cell.morphology(), thingify(domain_, cell.provider())); - for (auto& comp: comps) { - for (mcable c: comp) { - double scale = (c.dist_pos-c.prox_pos)*ooncv; + for (auto& comp: comps) { + for (mcable c: comp) { + double scale = (c.dist_pos-c.prox_pos)*ooncv; - if (has_flag(flags_, cv_policy_flag::interior_forks)) { - for (unsigned i = 0; i #include +#include namespace arb { /* Event classes `Event` used with `event_queue` must be move and copy constructible, - * and either have a public field `time` that returns the time value, or provide a - * projection to a time value through the EventTime functor. + * and either have a public field `time` that returns the time value, or provide an + * overload of `event_time(const Event&)` which returns this value (see generic_event.hpp). * * Time values must be well ordered with respect to `operator>`. */ -struct default_event_time { - template - auto operator()(Event const& e) const noexcept { return e.time; } -}; - -template +template class event_queue { public: - static constexpr EventTime event_time = {}; using value_type = Event; - using event_time_type = decltype(event_time(std::declval())); + using event_time_type = ::arb::event_time_type; event_queue() = default; @@ -55,6 +50,8 @@ class event_queue { if (queue_.empty()) { return std::nullopt; } + + using ::arb::event_time; auto t = event_time(queue_.top()); return t_until > t? std::optional(t): std::nullopt; } @@ -63,6 +60,7 @@ class event_queue { // queue non-empty and the head satisfies predicate. template std::optional pop_if(Pred&& pred) { + using ::arb::event_time; if (!queue_.empty() && pred(queue_.top())) { auto ev = queue_.top(); queue_.pop(); @@ -75,6 +73,7 @@ class event_queue { // Pop and return top event `ev` of queue if `t_until` > `event_time(ev)`. std::optional pop_if_before(const event_time_type& t_until) { + using ::arb::event_time; return pop_if( [&t_until](const value_type& ev) { return t_until > event_time(ev); } ); @@ -82,6 +81,7 @@ class event_queue { // Pop and return top event `ev` of queue unless `event_time(ev)` > `t_until` std::optional pop_if_not_after(const event_time_type& t_until) { + using ::arb::event_time; return pop_if( [&t_until](const value_type& ev) { return !(event_time(ev) > t_until); } ); @@ -95,6 +95,7 @@ class event_queue { private: struct event_greater { bool operator()(const Event& a, const Event& b) { + using ::arb::event_time; return event_time(a) > event_time(b); } }; diff --git a/arbor/fvm_layout.cpp b/arbor/fvm_layout.cpp index 158edc4fb..04b1ac9ce 100644 --- a/arbor/fvm_layout.cpp +++ b/arbor/fvm_layout.cpp @@ -244,13 +244,17 @@ ARB_ARBOR_API fvm_cv_discretization& append(fvm_cv_discretization& dczn, const f } // FVM discretization +// ------------------ + ARB_ARBOR_API fvm_cv_discretization -fvm_cv_discretize(const cable_cell& cell, - const cable_cell_parameter_set& global_dflt) { +fvm_cv_discretize(const cable_cell& cell, const cable_cell_parameter_set& global_dflt) { const auto& dflt = cell.default_parameters(); fvm_cv_discretization D; - const auto& cvp = cell.discretization().value_or(global_dflt.discretization.value_or(default_cv_policy())); - D.geometry = cv_geometry(cell, cvp.cv_boundary_points(cell)); + + D.geometry = cv_geometry(cell, + dflt.discretization? dflt.discretization->cv_boundary_points(cell): + global_dflt.discretization? global_dflt.discretization->cv_boundary_points(cell): + default_cv_policy().cv_boundary_points(cell)); if (D.geometry.empty()) return D; @@ -707,8 +711,8 @@ fvm_mechanism_data& append(fvm_mechanism_data& left, const fvm_mechanism_data& r arb_size_type target_offset = left.n_target; - for (const auto& [ion, R]: right.ions) { - fvm_ion_config& L = left.ions[ion]; + for (const auto& [k, R]: right.ions) { + fvm_ion_config& L = left.ions[k]; append(L.cv, R.cv); append(L.init_iconc, R.init_iconc); @@ -717,13 +721,10 @@ fvm_mechanism_data& append(fvm_mechanism_data& left, const fvm_mechanism_data& r append(L.reset_econc, R.reset_econc); append(L.init_revpot, R.init_revpot); append(L.face_diffusivity, R.face_diffusivity); - L.is_diffusive |= R.is_diffusive; + L.is_diffusive |= R.is_diffusive; L.econc_written |= R.econc_written; L.iconc_written |= R.iconc_written; - L.econc_read |= R.econc_read; - L.iconc_read |= R.iconc_read; L.revpot_written |= R.revpot_written; - L.revpot_read |= R.revpot_read; } for (const auto& kv: right.mechanisms) { @@ -825,28 +826,14 @@ struct fvm_ion_build_data { mcable_map init_econc_mask; bool write_xi = false; bool write_xo = false; - bool read_xi = false; - bool read_xo = false; - bool read_ex = false; std::vector support; - auto& add_to_support(const std::vector& cvs) { + void add_to_support(const std::vector& cvs) { arb_assert(util::is_sorted(cvs)); support = unique_union(support, cvs); - return *this; - } - - auto& add_ion_dep(const ion_dependency& dep) { - write_xi |= dep.write_concentration_int; - write_xo |= dep.write_concentration_ext; - read_xi |= dep.read_concentration_int; - read_xo |= dep.read_concentration_ext; - read_ex |= dep.read_reversal_potential; - return *this; } }; - using fvm_mechanism_config_map = std::map; using fvm_ion_config_map = std::unordered_map; using fvm_ion_map = std::unordered_map; @@ -923,14 +910,8 @@ make_gj_mechanism_config(const std::unordered_map +// Build reversal potential configs. Returns { X | X ion && eX is written } +std::unordered_set make_revpot_mechanism_config(const std::unordered_map& method, const std::unordered_map& ions, const cell_build_data& data, @@ -1092,12 +1073,8 @@ fvm_mechanism_data fvm_build_mechanism_data(const cable_cell_global_properties& auto method = dflt.reversal_potential_method; method.insert(global_dflt.reversal_potential_method.begin(), global_dflt.reversal_potential_method.end()); - auto confs = make_revpot_mechanism_config(method, M.ions, data, M.mechanisms); - for (const auto& [ion, conf]: confs) { - M.ions[ion].revpot_written |= conf.write_eX; - M.ions[ion].iconc_read |= conf.read_Xi; - M.ions[ion].econc_read |= conf.read_Xo; - } + auto written = make_revpot_mechanism_config(method, M.ions, data, M.mechanisms); + for (const auto& ion: written) M.ions[ion].revpot_written = true; } M.target_divs = {0u, M.n_target}; @@ -1251,9 +1228,10 @@ make_density_mechanism_config(const region_assignment& assignments, apply_parameters_on_cv(config, data, param_maps, support); for (const auto& [ion, dep]: info.ions) { - auto& build_data = ion_build_data[ion] - .add_ion_dep(dep) - .add_to_support(config.cv); + auto& build_data = ion_build_data[ion]; + build_data.write_xi |= dep.write_concentration_int; + build_data.write_xo |= dep.write_concentration_ext; + build_data.add_to_support(config.cv); auto ok = true; if (dep.write_concentration_int) { @@ -1365,9 +1343,6 @@ make_ion_config(fvm_ion_map build_data, config.econc_written = build_data.write_xo; config.iconc_written = build_data.write_xi; - config.econc_read = build_data.read_xo; - config.iconc_read = build_data.read_xi; - config.revpot_read = build_data.read_ex; if (!config.cv.empty()) result[ion] = std::move(config); } } @@ -1553,9 +1528,10 @@ make_point_mechanism_config(const std::unordered_map +std::unordered_set make_revpot_mechanism_config(const std::unordered_map& method, const std::unordered_map& ions, const cell_build_data& data, fvm_mechanism_config_map& result) { std::unordered_map revpot_tbl; - std::unordered_map ex_config; + std::unordered_set written; for (const auto& ion: util::keys(data.ion_species)) { if (!method.count(ion)) continue; @@ -1686,17 +1663,14 @@ make_revpot_mechanism_config(const std::unordered_map(n_cv, val)); } + if (!config.cv.empty()) result[name] = std::move(config); } - ex_config[ion].read_Xi |= dep.read_concentration_int; - ex_config[ion].read_Xo |= dep.read_concentration_ext; } } // Confirm that all ions written to by a revpot have a corresponding entry in a reversal_potential_method table. for (auto& [k, v]: revpot_tbl) { - if (!ex_config.count(k) || !ex_config.at(k).write_eX) { + if (!written.count(k)) { throw make_cc_error("Revpot mechanism {} also writes to ion {}.", v.name(), k); } } - return ex_config; + return written; } } // namespace arb diff --git a/arbor/fvm_layout.hpp b/arbor/fvm_layout.hpp index 9ba9a9d61..6c2684ca7 100644 --- a/arbor/fvm_layout.hpp +++ b/arbor/fvm_layout.hpp @@ -242,9 +242,6 @@ struct fvm_ion_config { bool revpot_written = false; bool iconc_written = false; bool econc_written = false; - bool revpot_read = false; - bool iconc_read = false; - bool econc_read = false; // Ordered CV indices where ion must be present. std::vector cv; diff --git a/arbor/fvm_lowered_cell_impl.hpp b/arbor/fvm_lowered_cell_impl.hpp index adcbab1a9..730471d10 100644 --- a/arbor/fvm_lowered_cell_impl.hpp +++ b/arbor/fvm_lowered_cell_impl.hpp @@ -14,7 +14,6 @@ #include #include -#include #include #include #include @@ -289,12 +288,14 @@ void fvm_lowered_cell_impl::update_ion_state() { template void fvm_lowered_cell_impl::assert_voltage_bounded(arb_value_type bound) { - const auto& [vmin, vmax] = state_->voltage_bounds(); - if (vmin >= -bound && vmax <= bound) return; + auto v_minmax = state_->voltage_bounds(); + if (v_minmax.first>=-bound && v_minmax.second<=bound) { + return; + } throw range_check_failure( util::pprintf("voltage solution out of bounds for at t = {}", state_->time), - vmin < -bound ? vmin : vmax); + v_minmax.first<-bound? v_minmax.first: v_minmax.second); } inline @@ -881,15 +882,15 @@ void resolve_probe(const cable_probe_density_state_cell& p, probe_resolution_dat } inline -auto point_info_of(cell_tag_type target, - cell_lid_type lid, +auto point_info_of(cell_lid_type target, int mech_index, const mlocation_map& instances, const std::vector& multiplicity) { - auto opt_i = util::binary_search_index(instances, lid, [](auto& item) { return item.lid; }); + + auto opt_i = util::binary_search_index(instances, target, [](auto& item) { return item.lid; }); if (!opt_i) throw arbor_internal_error("inconsistent mechanism state"); - return cable_probe_point_info {std::move(target), - lid, + + return cable_probe_point_info {target, multiplicity.empty() ? 1u: multiplicity.at(mech_index), instances[*opt_i].loc}; } @@ -902,7 +903,6 @@ void resolve_probe(const cable_probe_point_state& p, probe_resolution_data& R const auto& mech = p.mechanism; const auto& state = p.state; const auto& target = p.target; - const auto& t_hash = hash_value(target); const auto& data = R.mechanism_state(mech, state); if (!R.mech_instance_by_name.count(mech)) return; const auto mech_id = R.mech_instance_by_name.at(mech)->mechanism_id(); @@ -913,27 +913,17 @@ void resolve_probe(const cable_probe_point_state& p, probe_resolution_data& R // Convert cell-local target number to cellgroup target number. const auto& divs = R.M.target_divs; auto cell = R.cell_idx; - auto cg_lo = divs.at(cell); - auto cg_hi = divs.at(cell + 1); - const auto& [lr_beg, lr_end] = R.cell - .synapse_ranges() - .equal_range(t_hash); - for (auto lr = lr_beg; lr != lr_end; ++lr) { - const auto& [lid_beg, lid_end] = lr->second; - for (auto lid = lid_beg; lid != lid_end; ++lid) { - auto cg = lid + cg_lo; - if (cg >= cg_hi) continue; - const auto& handle = R.handles.at(cg); - if (handle.mech_id != mech_id) return; - auto mech_index = handle.mech_index; - R.result.push_back(fvm_probe_scalar{{data + mech_index}, - point_info_of(target, - lid, - mech_index, - synapses.at(mech), - R.M.mechanisms.at(mech).multiplicity)}); - } - } + auto cg = target + divs.at(cell); + if (cg >= divs.at(cell + 1)) return; + + const auto& handle = R.handles.at(cg); + if (handle.mech_id != mech_id) return; + auto mech_index = handle.mech_index; + R.result.push_back(fvm_probe_scalar{{data + mech_index}, + point_info_of(target, + mech_index, + synapses.at(mech), + R.M.mechanisms.at(mech).multiplicity)}); } template @@ -954,35 +944,25 @@ void resolve_probe(const cable_probe_point_state_cell& p, probe_resolution_data< auto cell_targets_beg = R.M.target_divs.at(R.cell_idx); auto cell_targets_end = R.M.target_divs.at(R.cell_idx + 1); - const auto& decor = R.cell.decorations(); - - fvm_probe_multi result; + fvm_probe_multi r; std::vector metadata; - cell_lid_type lid = 0; + for (auto target: util::make_span(cell_targets_beg, cell_targets_end)) { const auto& handle = R.handles.at(target); if (handle.mech_id != mech_id) continue; auto mech_index = handle.mech_index; - result.raw_handles.push_back(data + mech_index); + r.raw_handles.push_back(data + mech_index); - // Convert to cell-local target index. - const auto& ins = placed_instances.at(lid); - auto lid = target - cell_targets_beg; - auto tag = decor.tag_of(ins.tag); - - metadata.push_back(point_info_of(tag, - lid, + metadata.push_back(point_info_of(target - cell_targets_beg, // Convert to cell-local target index. mech_index, placed_instances, multiplicity)); - ++lid; } - - result.metadata = std::move(metadata); - result.shrink_to_fit(); - R.result.push_back(std::move(result)); + r.metadata = std::move(metadata); + r.shrink_to_fit(); + R.result.push_back(std::move(r)); } template @@ -1022,35 +1002,31 @@ void resolve_probe(const cable_probe_ion_current_cell& p, probe_resolution_data< template void resolve_probe(const cable_probe_ion_int_concentration& p, probe_resolution_data& R) { - const auto& ion = p.ion; - const auto& xi = R.state->ion_data.at(ion).Xi_; - if (xi.empty()) return; for (mlocation loc: thingify(p.locations, R.cell.provider())) { - auto opt_i = R.ion_location_index(ion, loc); + auto opt_i = R.ion_location_index(p.ion, loc); if (!opt_i) continue; - R.result.push_back(fvm_probe_scalar{{xi.data() + *opt_i}, loc}); + + R.result.push_back(fvm_probe_scalar{{R.state->ion_data.at(p.ion).Xi_.data()+*opt_i}, loc}); } } template void resolve_probe(const cable_probe_ion_ext_concentration& p, probe_resolution_data& R) { - const auto& ion = p.ion; - const auto& xo = R.state->ion_data.at(ion).Xo_; for (mlocation loc: thingify(p.locations, R.cell.provider())) { - auto opt_i = R.ion_location_index(ion, loc); + auto opt_i = R.ion_location_index(p.ion, loc); if (!opt_i) continue; - R.result.push_back(fvm_probe_scalar{{xo.data() + *opt_i}, loc}); + + R.result.push_back(fvm_probe_scalar{{R.state->ion_data.at(p.ion).Xo_.data()+*opt_i}, loc}); } } template void resolve_probe(const cable_probe_ion_diff_concentration& p, probe_resolution_data& R) { - const auto& ion = p.ion; - const auto& xd = R.state->ion_data.at(ion).Xd_; for (mlocation loc: thingify(p.locations, R.cell.provider())) { - auto opt_i = R.ion_location_index(ion, loc); + auto opt_i = R.ion_location_index(p.ion, loc); if (!opt_i) continue; - R.result.push_back(fvm_probe_scalar{{xd.data() + *opt_i}, loc}); + + R.result.push_back(fvm_probe_scalar{{R.state->ion_data.at(p.ion).Xd_.data()+*opt_i}, loc}); } } @@ -1059,6 +1035,7 @@ template void resolve_ion_conc_common(const std::vector& ion_cvs, const arb_value_type* src, probe_resolution_data& R) { fvm_probe_multi r; mcable_list cables; + for (auto i: util::count_along(ion_cvs)) { for (auto cable: R.D.geometry.cables(ion_cvs[i])) { if (cable.prox_pos!=cable.dist_pos) { @@ -1075,21 +1052,18 @@ void resolve_ion_conc_common(const std::vector& ion_cvs, const a template void resolve_probe(const cable_probe_ion_int_concentration_cell& p, probe_resolution_data& R) { if (!R.state->ion_data.count(p.ion)) return; - if (R.state->ion_data.at(p.ion).Xi_.empty()) return; resolve_ion_conc_common(R.M.ions.at(p.ion).cv, R.state->ion_data.at(p.ion).Xi_.data(), R); } template void resolve_probe(const cable_probe_ion_ext_concentration_cell& p, probe_resolution_data& R) { if (!R.state->ion_data.count(p.ion)) return; - if (R.state->ion_data.at(p.ion).Xo_.empty()) return; resolve_ion_conc_common(R.M.ions.at(p.ion).cv, R.state->ion_data.at(p.ion).Xo_.data(), R); } template void resolve_probe(const cable_probe_ion_diff_concentration_cell& p, probe_resolution_data& R) { if (!R.state->ion_data.count(p.ion)) return; - if (R.state->ion_data.at(p.ion).Xd_.empty()) return; resolve_ion_conc_common(R.M.ions.at(p.ion).cv, R.state->ion_data.at(p.ion).Xd_.data(), R); } diff --git a/arbor/hardware/power.cpp b/arbor/hardware/power.cpp new file mode 100644 index 000000000..7ca8df68f --- /dev/null +++ b/arbor/hardware/power.cpp @@ -0,0 +1,29 @@ +#include + +#include "power.hpp" + +// Currently only supporting Cray PM counters. + +#define CRAY_PM_COUNTER_ENERGY "/sys/cray/pm_counters/energy" + +namespace arb { +namespace hw { + +bool has_energy_measurement() { + return static_cast(std::ifstream(CRAY_PM_COUNTER_ENERGY)); +} + +energy_size_type energy() { + energy_size_type result = energy_size_type(-1); + + std::ifstream fid(CRAY_PM_COUNTER_ENERGY); + if (fid) { + fid >> result; + } + + return result; +} + +} // namespace hw +} // namespace arb + diff --git a/arbor/hardware/power.hpp b/arbor/hardware/power.hpp new file mode 100644 index 000000000..003a30798 --- /dev/null +++ b/arbor/hardware/power.hpp @@ -0,0 +1,18 @@ +#pragma once + +#include + +namespace arb { +namespace hw { + +// Test for support on configured architecture: +bool has_energy_measurement(); + +// Energy in Joules (J) +using energy_size_type = std::uint64_t; + +// Returns energy_size_type(-1) if unable to read energy +energy_size_type energy(); + +} // namespace hw +} // namespace arb diff --git a/arbor/include/arbor/cable_cell.hpp b/arbor/include/arbor/cable_cell.hpp index 3ef19cbcf..829582316 100644 --- a/arbor/include/arbor/cable_cell.hpp +++ b/arbor/include/arbor/cable_cell.hpp @@ -9,7 +9,6 @@ #include #include #include -#include #include #include #include @@ -53,8 +52,7 @@ using cable_sample_range = std::pair; // // Metadata for point process probes. struct ARB_SYMBOL_VISIBLE cable_probe_point_info { - cell_tag_type target; // Target tag of point process instance on cell. - cell_lid_type lid; // Target lid of point process instance on cell. + cell_lid_type target; // Target number of point process instance on cell. unsigned multiplicity; // Number of combined instances at this site. mlocation loc; // Point on cell morphology where instance is placed. }; @@ -110,7 +108,6 @@ struct ARB_SYMBOL_VISIBLE cable_probe_density_state { }; // Value of state variable `state` in density mechanism `mechanism` across components of the cell. -// // Sample value type: `cable_sample_range` // Sample metadata type: `mcable_list` struct ARB_SYMBOL_VISIBLE cable_probe_density_state_cell { @@ -119,30 +116,16 @@ struct ARB_SYMBOL_VISIBLE cable_probe_density_state_cell { }; // Value of state variable `key` in point mechanism `source` at target `target`. -// // Sample value type: `double` // Sample metadata type: `cable_probe_point_info` struct ARB_SYMBOL_VISIBLE cable_probe_point_state { - cell_tag_type target; + cell_lid_type target; std::string mechanism; std::string state; - - // Engage in minimal hygeine. Ideally, we'd disable all nullptr constructors. - cable_probe_point_state(std::nullptr_t, std::string, std::string) = delete; - cable_probe_point_state() = delete; - - constexpr cable_probe_point_state(cell_tag_type t, std::string m, std::string s): - target(std::move(t)), mechanism(std::move(m)), state(std::move(s)) {} - constexpr cable_probe_point_state(const cable_probe_point_state&) = default; - constexpr cable_probe_point_state(cable_probe_point_state&&) = default; - constexpr cable_probe_point_state& operator=(const cable_probe_point_state&) = default; - constexpr cable_probe_point_state& operator=(cable_probe_point_state&&) = default; }; -// Value of state variable `key` in point mechanism `source` at every target -// with this mechanism. Metadata has one entry of type cable_probe_point_info -// for each matched (possibly coalesced) instance. -// +// Value of state variable `key` in point mechanism `source` at every target with this mechanism. +// Metadata has one entry of type cable_probe_point_info for each matched (possibly coalesced) instance. // Sample value type: `cable_sample_range` // Sample metadata type: `std::vector` struct ARB_SYMBOL_VISIBLE cable_probe_point_state_cell { @@ -240,7 +223,6 @@ struct placed { mlocation loc; cell_lid_type lid; T item; - hash_type tag; }; // Note: lid fields of elements of mlocation_map used in cable_cell are strictly increasing. @@ -255,16 +237,9 @@ using location_assignment = mlocation_map>; using cable_cell_region_map = static_typed_map; + density, voltage_process, init_membrane_potential, axial_resistivity, + temperature, membrane_capacitance, init_int_concentration, + ion_diffusivity, init_ext_concentration, init_reversal_potential>; using cable_cell_location_map = static_typed_map; @@ -290,10 +265,7 @@ struct ARB_SYMBOL_VISIBLE cable_cell { } /// Construct from morphology, label and decoration descriptions. - cable_cell(const class morphology& m, - const decor& d, - const label_dict& l={}, - const std::optional& = {}); + cable_cell(const class morphology& m, const decor& d, const label_dict& l={}); /// Access to labels const label_dict& labels() const; @@ -334,10 +306,6 @@ struct ARB_SYMBOL_VISIBLE cable_cell { // The decorations on the cell. const decor& decorations() const; - // The current cv_policy of this cell - const std::optional& discretization() const; - void discretization(cv_policy); - // The default parameter and ion settings on the cell. const cable_cell_parameter_set& default_parameters() const; diff --git a/arbor/include/arbor/cable_cell_param.hpp b/arbor/include/arbor/cable_cell_param.hpp index 621d36b6f..d68c0c73e 100644 --- a/arbor/include/arbor/cable_cell_param.hpp +++ b/arbor/include/arbor/cable_cell_param.hpp @@ -8,8 +8,8 @@ #include #include -#include #include +#include #include #include #include @@ -380,7 +380,8 @@ using defaultable = init_int_concentration, init_ext_concentration, init_reversal_potential, - ion_reversal_potential_method>; + ion_reversal_potential_method, + cv_policy>; // Cable cell ion and electrical defaults. diff --git a/arbor/include/arbor/cv_policy.hpp b/arbor/include/arbor/cv_policy.hpp index e06cd0c73..a56a5c51d 100644 --- a/arbor/include/arbor/cv_policy.hpp +++ b/arbor/include/arbor/cv_policy.hpp @@ -69,77 +69,146 @@ struct cv_policy_base { virtual std::ostream& print(std::ostream&) = 0; }; +using cv_policy_base_ptr = std::unique_ptr; + struct ARB_SYMBOL_VISIBLE cv_policy { - // construct from anything except other policies - template , cv_policy>>> - explicit cv_policy(const Impl& impl): impl_(std::make_unique>(impl)) {} - template , cv_policy>>> - explicit cv_policy(Impl&& impl): impl_(std::make_unique>(std::move(impl))) {} - // move - cv_policy(cv_policy&&) = default; - cv_policy& operator=(cv_policy&&) = default; - // copy - cv_policy(const cv_policy& other): impl_(other.impl_->clone()) {} + cv_policy(const cv_policy_base& ref) { // implicit + policy_ptr = ref.clone(); + } + + cv_policy(const cv_policy& other): + policy_ptr(other.policy_ptr->clone()) {} + cv_policy& operator=(const cv_policy& other) { - impl_ = other.impl_->clone(); + policy_ptr = other.policy_ptr->clone(); return *this; } - // interface - locset cv_boundary_points(const cable_cell& cell) const { return impl_->cv_boundary_points(cell); } - region domain() const { return impl_->domain(); } - std::ostream& format(std::ostream& os) const { return impl_->format(os); } + cv_policy(cv_policy&&) = default; + cv_policy& operator=(cv_policy&&) = default; - friend ARB_ARBOR_API std::ostream& operator<<(std::ostream& os, const cv_policy& cvp) { return cvp.format(os); } + locset cv_boundary_points(const cable_cell& cell) const { + return policy_ptr->cv_boundary_points(cell); + } -private: - struct iface { - virtual locset cv_boundary_points(const cable_cell& cell) const = 0; - virtual region domain() const = 0; - virtual std::unique_ptr clone() const = 0; - virtual ~iface() {} - virtual std::ostream& format(std::ostream&) const = 0; - }; + region domain() const { + return policy_ptr->domain(); + } + + friend std::ostream& operator<<(std::ostream& o, const cv_policy& p) { + return p.policy_ptr->print(o); + } - using iface_ptr = std::unique_ptr; +private: + cv_policy_base_ptr policy_ptr; +}; - template - struct wrap: iface { - explicit wrap(const Impl& impl): inner_(impl) {} - explicit wrap(Impl&& impl): inner_(std::move(impl)) {} +ARB_ARBOR_API cv_policy operator+(const cv_policy&, const cv_policy&); +ARB_ARBOR_API cv_policy operator|(const cv_policy&, const cv_policy&); - locset cv_boundary_points(const cable_cell& cell) const override { return inner_.cv_boundary_points(cell); } - region domain() const override { return inner_.domain(); }; - iface_ptr clone() const override { return std::make_unique>(inner_); } - std::ostream& format(std::ostream& os) const override { return inner_.format(os); }; - Impl inner_; +// Common flags for CV policies; bitwise composable. +namespace cv_policy_flag { + using value = unsigned; + enum : unsigned { + none = 0, + interior_forks = 1<<0 }; +} + +struct ARB_ARBOR_API cv_policy_explicit: cv_policy_base { + explicit cv_policy_explicit(locset locs, region domain = reg::all()): + locs_(std::move(locs)), domain_(std::move(domain)) {} + + cv_policy_base_ptr clone() const override; + locset cv_boundary_points(const cable_cell&) const override; + region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(explicit " << locs_ << ' ' << domain_ << ')'; + return os; + } + +private: + locset locs_; + region domain_; +}; + +struct ARB_ARBOR_API cv_policy_single: cv_policy_base { + explicit cv_policy_single(region domain = reg::all()): + domain_(domain) {} - iface_ptr impl_; + cv_policy_base_ptr clone() const override; + locset cv_boundary_points(const cable_cell&) const override; + region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(single " << domain_ << ')'; + return os; + } + +private: + region domain_; }; -// Common flags for CV policies; bitwise composable. -enum class cv_policy_flag: unsigned { - none = 0, - interior_forks = 1<<0 +struct ARB_ARBOR_API cv_policy_max_extent: cv_policy_base { + cv_policy_max_extent(double max_extent, region domain, cv_policy_flag::value flags = cv_policy_flag::none): + max_extent_(max_extent), domain_(std::move(domain)), flags_(flags) {} + + explicit cv_policy_max_extent(double max_extent, cv_policy_flag::value flags = cv_policy_flag::none): + max_extent_(max_extent), domain_(reg::all()), flags_(flags) {} + + cv_policy_base_ptr clone() const override; + locset cv_boundary_points(const cable_cell&) const override; + region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(max-extent " << max_extent_ << ' ' << domain_ << ' ' << flags_ << ')'; + return os; + } + +private: + double max_extent_; + region domain_; + cv_policy_flag::value flags_; }; -ARB_ARBOR_API cv_policy operator+(const cv_policy&, const cv_policy&); -ARB_ARBOR_API cv_policy operator|(const cv_policy&, const cv_policy&); +struct ARB_ARBOR_API cv_policy_fixed_per_branch: cv_policy_base { + cv_policy_fixed_per_branch(unsigned cv_per_branch, region domain, cv_policy_flag::value flags = cv_policy_flag::none): + cv_per_branch_(cv_per_branch), domain_(std::move(domain)), flags_(flags) {} -ARB_ARBOR_API cv_policy cv_policy_explicit(locset, region = reg::all()); + explicit cv_policy_fixed_per_branch(unsigned cv_per_branch, cv_policy_flag::value flags = cv_policy_flag::none): + cv_per_branch_(cv_per_branch), domain_(reg::all()), flags_(flags) {} + + cv_policy_base_ptr clone() const override; + locset cv_boundary_points(const cable_cell&) const override; + region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(fixed-per-branch " << cv_per_branch_ << ' ' << domain_ << ' ' << flags_ << ')'; + return os; + } -ARB_ARBOR_API cv_policy cv_policy_max_extent(double, region, cv_policy_flag = cv_policy_flag::none); -ARB_ARBOR_API cv_policy cv_policy_max_extent(double, cv_policy_flag = cv_policy_flag::none); +private: + unsigned cv_per_branch_; + region domain_; + cv_policy_flag::value flags_; +}; -ARB_ARBOR_API cv_policy cv_policy_fixed_per_branch(unsigned, region, cv_policy_flag = cv_policy_flag::none); -ARB_ARBOR_API cv_policy cv_policy_fixed_per_branch(unsigned, cv_policy_flag = cv_policy_flag::none); +struct ARB_ARBOR_API cv_policy_every_segment: cv_policy_base { + explicit cv_policy_every_segment(region domain = reg::all()): + domain_(std::move(domain)) {} -ARB_ARBOR_API cv_policy cv_policy_single(region domain = reg::all()); + cv_policy_base_ptr clone() const override; + locset cv_boundary_points(const cable_cell&) const override; + region domain() const override; + std::ostream& print(std::ostream& os) override { + os << "(every-segment " << domain_ << ')'; + return os; + } -ARB_ARBOR_API cv_policy cv_policy_every_segment(region domain = reg::all()); +private: + region domain_; +}; -inline cv_policy default_cv_policy() { return cv_policy_fixed_per_branch(1); } +inline cv_policy default_cv_policy() { + return cv_policy_fixed_per_branch(1); +} } // namespace arb diff --git a/arbor/include/arbor/event_generator.hpp b/arbor/include/arbor/event_generator.hpp index d809fbb2d..cab7e5fe2 100644 --- a/arbor/include/arbor/event_generator.hpp +++ b/arbor/include/arbor/event_generator.hpp @@ -4,6 +4,7 @@ #include #include +#include #include #include #include diff --git a/arbor/include/arbor/generic_event.hpp b/arbor/include/arbor/generic_event.hpp new file mode 100644 index 000000000..9a139a670 --- /dev/null +++ b/arbor/include/arbor/generic_event.hpp @@ -0,0 +1,91 @@ +#pragma once + +#include +#include + +// Generic accessors for event types used in `event_queue` and +// `event_stream`. +// +// 1. event_time(const Event&): +// +// Returns ordered type (typically `time_type`) representing +// the event time. Default implementation returns `e.time` +// for an event `e`. +// +// 2. event_index(const Event&): +// +// Returns the stream index associated with the event (an +// unsigned index type), for use with `event_stream`. +// Default implementation returns `e.index` for an event `e`. +// +// 3. event_data(const Event&): +// +// Returns the event _payload_, viz. the event data that +// does not include (necessarily) the time or index. This +// is used with `event_stream`. +// Default implementation returns `e.data` for an event `e`. +// +// The type aliases event_time_type, event_index_type and event_data_type +// give the corresponding return types. +// +// The accessors act as customization points, in that they can be +// specialized for a particular event class. In order for ADL +// to work correctly across namespaces, the accessor functions +// should be brought into scope with a `using` declaration. +// +// Example use: +// +// template +// bool is_before(const Event& a, const Event& b) { +// using ::arb::event_time; +// return event_time(a) +auto event_time(const Event& ev) { + return ev.time; +} + +struct event_time_less { + template ::value>> + bool operator() (T l, const Event& r) { + return l::value>> + bool operator() (const Event& l, T r) { + return event_time(l) + using event_time_type = decltype(event_time(std::declval())); + + template + using event_data_type = decltype(event_data(std::declval())); + + template + using event_index_type = decltype(event_index(std::declval>())); +} + +template +using event_time_type = impl::event_time_type; + +template +using event_index_type = impl::event_index_type; + +template +using event_data_type = impl::event_data_type; + +template +struct has_event_index : public std::false_type {}; + +} // namespace arb + diff --git a/arbor/include/arbor/mechanism_abi.h b/arbor/include/arbor/mechanism_abi.h index b5bca6f24..bf0a9b30b 100644 --- a/arbor/include/arbor/mechanism_abi.h +++ b/arbor/include/arbor/mechanism_abi.h @@ -20,7 +20,7 @@ extern "C" { // Version #define ARB_MECH_ABI_VERSION_MAJOR 0 -#define ARB_MECH_ABI_VERSION_MINOR 7 +#define ARB_MECH_ABI_VERSION_MINOR 6 #define ARB_MECH_ABI_VERSION_PATCH 0 #define ARB_MECH_ABI_VERSION ((ARB_MECH_ABI_VERSION_MAJOR * 10000L * 10000L) + (ARB_MECH_ABI_VERSION_MAJOR * 10000L) + ARB_MECH_ABI_VERSION_PATCH) @@ -193,8 +193,6 @@ typedef struct arb_ion_info { const char* name; bool write_int_concentration; bool write_ext_concentration; - bool read_int_concentration; - bool read_ext_concentration; bool use_diff_concentration; bool write_rev_potential; bool read_rev_potential; diff --git a/arbor/include/arbor/mechinfo.hpp b/arbor/include/arbor/mechinfo.hpp index a4527ed99..6d717a65f 100644 --- a/arbor/include/arbor/mechinfo.hpp +++ b/arbor/include/arbor/mechinfo.hpp @@ -7,6 +7,8 @@ #include #include #include +#include +#include #include #include @@ -33,8 +35,6 @@ struct mechanism_field_spec { struct ion_dependency { bool write_concentration_int = false; bool write_concentration_ext = false; - bool read_concentration_int = false; - bool read_concentration_ext = false; bool access_concentration_diff = false; diff --git a/arbor/include/arbor/s_expr.hpp b/arbor/include/arbor/s_expr.hpp index ce23a18ce..c5c4f51fa 100644 --- a/arbor/include/arbor/s_expr.hpp +++ b/arbor/include/arbor/s_expr.hpp @@ -7,7 +7,9 @@ #include #include #include +#include #include +#include #include diff --git a/arbor/include/arbor/util/expected.hpp b/arbor/include/arbor/util/expected.hpp index 1af88eb42..9d860f653 100644 --- a/arbor/include/arbor/util/expected.hpp +++ b/arbor/include/arbor/util/expected.hpp @@ -486,7 +486,7 @@ struct expected { // Swap ops. void swap(expected& other) { - data_.swap(other.data_); + data_.swap(other.data); } // Accessors. diff --git a/arbor/io/locked_ostream.cpp b/arbor/io/locked_ostream.cpp new file mode 100644 index 000000000..1aa855217 --- /dev/null +++ b/arbor/io/locked_ostream.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include +#include + +#include "locked_ostream.hpp" + +namespace arb { +namespace io { + +using tbl_type = std::unordered_map>; + +static tbl_type& g_mex_tbl() { + static tbl_type tbl; + return tbl; +} + +static std::mutex& g_mex_tbl_mex() { + static std::mutex mex; + return mex; +} + +static std::shared_ptr register_sbuf(std::streambuf* b) { + if (b) { + std::lock_guard lock(g_mex_tbl_mex()); + + auto& wptr = g_mex_tbl()[b]; + auto mex = wptr.lock(); + if (!mex) { + mex = std::shared_ptr(new std::mutex); + wptr = mex; + } + return mex; + } + else { + return std::shared_ptr(); + } +} + +static void deregister_sbuf(std::streambuf* b) { + if (b) { + std::lock_guard lock(g_mex_tbl_mex()); + + auto i = g_mex_tbl().find(b); + if (i!=g_mex_tbl().end() && !(i->second.use_count())) { + g_mex_tbl().erase(i); + } + } +} + +locked_ostream::locked_ostream(std::streambuf *b): + std::ostream(b), + mex(register_sbuf(b)) +{} + + +locked_ostream::locked_ostream(locked_ostream&& other): + std::ostream(std::move(other)), + mex(std::move(other.mex)) +{ + set_rdbuf(other.rdbuf()); + other.set_rdbuf(nullptr); +} + +locked_ostream::~locked_ostream() { + mex.reset(); + deregister_sbuf(rdbuf()); +} + +std::unique_lock locked_ostream::guard() { + return std::unique_lock(*mex); +} + +} // namespace io +} // namespace arb diff --git a/arbor/io/locked_ostream.hpp b/arbor/io/locked_ostream.hpp new file mode 100644 index 000000000..20ecfc74a --- /dev/null +++ b/arbor/io/locked_ostream.hpp @@ -0,0 +1,25 @@ +#pragma once + +// Lockable ostream over a provided streambuf. + +#include +#include +#include + +namespace arb { +namespace io { + +struct locked_ostream: std::ostream { + locked_ostream(std::streambuf *b); + locked_ostream(locked_ostream&& other); + + ~locked_ostream(); + + std::unique_lock guard(); + +private: + std::shared_ptr mex; +}; + +} // namespace io +} // namespace arb diff --git a/arbor/io/save_ios.hpp b/arbor/io/save_ios.hpp new file mode 100644 index 000000000..adfcda247 --- /dev/null +++ b/arbor/io/save_ios.hpp @@ -0,0 +1,24 @@ +#pragma once + +// RAII save-and-restore ios formatting flags. + +#include + +namespace arb { +namespace io { + +struct save_ios_flags { + std::ios_base& ios; + std::ios_base::fmtflags flags; + + save_ios_flags(std::ios_base& ios): + ios(ios), flags(ios.flags()) {} + + save_ios_flags(const save_ios_flags&) = delete; + + ~save_ios_flags() { ios.flags(flags); } +}; + + +} // namespace io +} // namespace arb diff --git a/arbor/io/serialize_hex.cpp b/arbor/io/serialize_hex.cpp new file mode 100644 index 000000000..522f8401a --- /dev/null +++ b/arbor/io/serialize_hex.cpp @@ -0,0 +1,62 @@ +// Adaptor for hexadecimal output to a std::ostream. + +#include +#include + +// Required for endianness macros: +#include + +#include "io/serialize_hex.hpp" + +namespace arb { +namespace io { + +namespace impl { + + enum class endian { + little = __ORDER_LITTLE_ENDIAN__, + big = __ORDER_BIG_ENDIAN__, + native = __BYTE_ORDER__ + }; + + std::ostream& operator<<(std::ostream& out, const hex_inline_wrap& h) { + using std::ptrdiff_t; + + constexpr bool little = endian::native==endian::little; + ptrdiff_t width = h.width; + const unsigned char* from = h.from; + const unsigned char* end = h.from+h.size; + std::string buf; + + auto emit = [&buf](unsigned char c) { + const char* digit = "0123456789abcdef"; + buf += digit[(c>>4)&0xf]; + buf += digit[c&0xf]; + }; + + constexpr unsigned bufsz = 512; + unsigned bufmargin = 4*width+1; + + buf.reserve(bufsz); + while (end-from>width) { + if (buf.size()+bufmargin>=bufsz) { + out << buf; + buf.clear(); + } + for (ptrdiff_t i = 0; i + +namespace arb { +namespace io { + +namespace impl { + // Wrapper for emitting values on an ostream as a sequence of hex digits. + struct hex_inline_wrap { + const unsigned char* from; + std::size_t size; + unsigned width; + }; + + std::ostream& operator<<(std::ostream&, const hex_inline_wrap&); +} // namespace impl + +// Inline hexadecimal adaptor: group output in `width` bytes. + +template +impl::hex_inline_wrap hex_inline(const T& obj, unsigned width = 4) { + return impl::hex_inline_wrap{reinterpret_cast(&obj), sizeof obj, width}; +} + +// Inline hexadecimal adaptor: print `n` bytes of data from `ptr`, grouping output in `width` bytes. + +template +impl::hex_inline_wrap hex_inline_n(const T* ptr, std::size_t n, unsigned width = 4) { + return impl::hex_inline_wrap{reinterpret_cast(ptr), n, width}; +} + +} // namespace io +} // namespace arb + diff --git a/arbor/io/trace.hpp b/arbor/io/trace.hpp new file mode 100644 index 000000000..5b50b7d60 --- /dev/null +++ b/arbor/io/trace.hpp @@ -0,0 +1,79 @@ +#pragma once + +// Internal TRACE macros and formatters for debugging during +// development. + +#include +#include + +// Required for endianness macros: +#include + +#include "io/locked_ostream.hpp" +#include "io/sepval.hpp" +#include "io/serialize_hex.hpp" + + +// TRACE(expr1 [, expr2 ...]) +// +// Emit current source location to std::cerr, followed by the +// literal expressions expr1, ..., and then the values of those expressions. +// +// TRACE output is to std::cerr is serialized. + +#define TRACE(...) arb::impl::debug_emit_trace(__FILE__, __LINE__, #__VA_ARGS__, ##__VA_ARGS__) + + +// DEBUG << ...; +// +// Emit arguments to std::cerr followed by a newline. +// DEBUG output to std::cerr is serialized. + +#define DEBUG arb::impl::emit_nl_locked(std::cerr.rdbuf()) + + +namespace arb { + +namespace impl { + inline void debug_emit_csv(std::ostream&) {} + + template + void debug_emit_csv(std::ostream& out, const Head& head, const Tail&... tail) { + out << head; + if (sizeof...(tail)) { + out << ", "; + } + debug_emit_csv(out, tail...); + } + + inline void debug_emit_trace_leader(std::ostream& out, const char* file, int line, const char* vars) { + out << file << ':' << line << ": " << vars << ": "; + } + + struct emit_nl_locked: public io::locked_ostream { + emit_nl_locked(std::streambuf* buf): + io::locked_ostream(buf), + lock_(this->guard()) + {} + + ~emit_nl_locked() { + if (rdbuf()) { + (*this) << std::endl; + } + } + + private: + std::unique_lock lock_; + }; + + template + void debug_emit_trace(const char* file, int line, const char* varlist, const Args&... args) { + impl::emit_nl_locked out(std::cerr.rdbuf()); + + out.precision(17); + impl::debug_emit_trace_leader(out, file, line, varlist); + impl::debug_emit_csv(out, args...); + } +} // namespace impl + +} // namespace arb diff --git a/arbor/matrix.hpp b/arbor/matrix.hpp new file mode 100644 index 000000000..3b69db504 --- /dev/null +++ b/arbor/matrix.hpp @@ -0,0 +1,63 @@ +#pragma once + +#include + +#include + +#include +#include + +#include + +namespace arb { + +/// Hines matrix +/// Make the back end state implementation optional to allow for +/// testing different implementations in the same code. +template +class matrix { +public: + using backend = Backend; + using array = typename backend::array; + using iarray = typename backend::iarray; + using const_view = const array&; + using state = State; // backend specific storage for matrix state + + matrix() = default; + + matrix(const std::vector& pi, + const std::vector& ci, + const std::vector& cv_capacitance, + const std::vector& face_conductance, + const std::vector& cv_area, + const std::vector& cell_to_intdom): + num_cells_{ci.size() - 1}, + state_(pi, ci, cv_capacitance, face_conductance, cv_area, cell_to_intdom) + { + arb_assert(cell_index()[num_cells()] == arb_index_type(parent_index().size())); + } + + /// the dimension of the matrix (i.e. the number of rows or colums) + std::size_t size() const { return state_.size(); } + /// the number of cell matrices that have been packed together + std::size_t num_cells() const { return num_cells_; } + /// the vector holding the parent index + const iarray& parent_index() const { return state_.parent_index; } + /// the partition of the parent index over the cells + const iarray& cell_index() const { return state_.cell_cv_divs; } + /// Solve the linear system into a given solution storage. + void solve(array& to) { state_.solve(to); } + /// Assemble the matrix for given dt + void assemble(const_view& dt, const_view& U, const_view& I, const_view& g) { state_.assemble(dt, U, I, g); } + +private: + std::size_t num_cells_ = 0; + +public: + // Provide via public interface to make testing much easier. If you modify + // this directly without knowing what you are doing, you get what you + // deserve. + state state_; +}; + +} // namespace arb diff --git a/arbor/mechinfo.cpp b/arbor/mechinfo.cpp index b96cca7bd..1d5a15205 100644 --- a/arbor/mechinfo.cpp +++ b/arbor/mechinfo.cpp @@ -22,16 +22,14 @@ mechanism_info::mechanism_info(const arb_mechanism_type& m) { } for (auto idx: util::make_span(m.n_ions)) { const auto& v = m.ions[idx]; - ions[v.name] = {v.write_int_concentration, - v.write_ext_concentration, - v.read_int_concentration, - v.read_ext_concentration, - v.use_diff_concentration, - v.read_rev_potential, - v.write_rev_potential, - v.read_valence, - v.verify_valence, - v.expected_valence }; + ions[v.name] = { v.write_int_concentration, + v.write_ext_concentration, + v.use_diff_concentration, + v.read_rev_potential, + v.write_rev_potential, + v.read_valence, + v.verify_valence, + v.expected_valence }; } for (auto idx: util::make_span(m.n_random_variables)) { const auto& rv = m.random_variables[idx]; diff --git a/arbor/merge_events.cpp b/arbor/merge_events.cpp index ccfcd7cd3..9dd71ef48 100644 --- a/arbor/merge_events.cpp +++ b/arbor/merge_events.cpp @@ -1,9 +1,11 @@ +#include #include #include #include #include +#include "io/trace.hpp" #include "merge_events.hpp" #include "util/tourney_tree.hpp" diff --git a/arbor/morph/cv_data.cpp b/arbor/morph/cv_data.cpp index 6fdd996bb..ec4d68e76 100644 --- a/arbor/morph/cv_data.cpp +++ b/arbor/morph/cv_data.cpp @@ -138,7 +138,7 @@ arb_size_type cell_cv_data::size() const { } ARB_ARBOR_API std::optional cv_data(const cable_cell& cell) { - if (const auto& policy = cell.discretization()) { + if (auto policy = cell.decorations().defaults().discretization) { return cell_cv_data(cell, policy->cv_boundary_points(cell)); } return {}; diff --git a/arbor/morph/embed_pwlin.cpp b/arbor/morph/embed_pwlin.cpp index 02a4da607..945dffdf6 100644 --- a/arbor/morph/embed_pwlin.cpp +++ b/arbor/morph/embed_pwlin.cpp @@ -103,7 +103,7 @@ struct embed_pwlin_data { template double interpolate(double pos, const pw_ratpoly& f) { - const auto& [extent, poly] = f(pos); + auto [extent, poly] = f(pos); auto [left, right] = extent; return left==right? poly[0]: poly((pos-left)/(right-left)); diff --git a/arbor/profile/meter_manager.cpp b/arbor/profile/meter_manager.cpp index f8066270f..99710caeb 100644 --- a/arbor/profile/meter_manager.cpp +++ b/arbor/profile/meter_manager.cpp @@ -4,6 +4,7 @@ #include #include "memory_meter.hpp" +#include "power_meter.hpp" #include "execution_context.hpp" #include "util/hostname.hpp" @@ -47,6 +48,9 @@ meter_manager::meter_manager() { if (auto m = make_gpu_memory_meter()) { meters_.push_back(std::move(m)); } + if (auto m = make_power_meter()) { + meters_.push_back(std::move(m)); + } }; void meter_manager::start(context ctx) { diff --git a/arbor/profile/power_meter.cpp b/arbor/profile/power_meter.cpp new file mode 100644 index 000000000..aa5adf02d --- /dev/null +++ b/arbor/profile/power_meter.cpp @@ -0,0 +1,46 @@ +#include +#include + +#include + +#include "hardware/power.hpp" + +namespace arb { +namespace profile { + +class power_meter: public meter { + std::vector readings_; + +public: + std::string name() override { + return "energy"; + } + + std::string units() override { + return "J"; + } + + std::vector measurements() override { + std::vector diffs; + + for (auto i=1ul; i + +namespace arb { +namespace profile { + +meter_ptr make_power_meter(); + +} // namespace profile +} // namespace arb diff --git a/arbor/simulation.cpp b/arbor/simulation.cpp index 9945e2508..998963790 100644 --- a/arbor/simulation.cpp +++ b/arbor/simulation.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -42,11 +43,7 @@ ARB_ARBOR_API void merge_cell_events(time_type t_from, pse_vector& new_events) { PE(communication:enqueue:setup); new_events.clear(); - constexpr auto event_time_less = [](auto const& l, auto const& r) noexcept { - if constexpr (std::is_floating_point_v>) { return l < r.time; } - else { return l.time < r; } - }; - old_events = split_sorted_range(old_events, t_from, event_time_less).second; + old_events = split_sorted_range(old_events, t_from, event_time_less()).second; PL(); if (!generators.empty()) { @@ -56,8 +53,8 @@ ARB_ARBOR_API void merge_cell_events(time_type t_from, std::vector spanbuf; spanbuf.reserve(2+generators.size()); - auto old_split = split_sorted_range(old_events, t_to, event_time_less); - auto pending_split = split_sorted_range(pending, t_to, event_time_less); + auto old_split = split_sorted_range(old_events, t_to, event_time_less()); + auto pending_split = split_sorted_range(pending, t_to, event_time_less()); spanbuf.push_back(old_split.first); spanbuf.push_back(pending_split.first); diff --git a/arbor/tree.cpp b/arbor/tree.cpp index 140730357..3ae342dff 100644 --- a/arbor/tree.cpp +++ b/arbor/tree.cpp @@ -29,7 +29,7 @@ tree::tree(std::vector parent_index) { parents_[0] = no_parent; // compute offsets into children_ array - util::make_partition(child_index_, child_count(parents_)); + arb::util::make_partition(child_index_, child_count(parents_)); std::vector pos(parents_.size(), 0); for (auto i = 1u; i < parents_.size(); ++i) { @@ -199,9 +199,11 @@ tree::iarray tree::select_new_root(int_type root) { } // maps new indices to old indices - iarray indices(num_nodes); - std::iota(indices.begin(), indices.end(), 0); - + iarray indices (num_nodes); + // fill array with indices + for (auto i: make_span(num_nodes)) { + indices[i] = i; + } // perform sort by depth index to get the permutation std::sort(indices.begin(), indices.end(), [&](auto i, auto j){ if (reduced_depth[i] != reduced_depth[j]) { @@ -212,12 +214,16 @@ tree::iarray tree::select_new_root(int_type root) { } return depth[i] < depth[j]; }); - - // inverse permutation - iarray indices_inv(num_nodes, 0); - std::iota(indices_inv.begin(), indices_inv.end(), 0); - std::sort(indices_inv.begin(), indices_inv.end(), - [&](auto i, auto j){ return indices[i] < indices[j]; }); + // maps old indices to new indices + iarray indices_inv (num_nodes); + // fill array with indices + for (auto i: make_span(num_nodes)) { + indices_inv[i] = i; + } + // perform sort + std::sort(indices_inv.begin(), indices_inv.end(), [&](auto i, auto j){ + return indices[i] < indices[j]; + }); // translate the parent vetor to new indices for (auto i: make_span(num_nodes)) { @@ -235,7 +241,7 @@ tree::iarray tree::select_new_root(int_type root) { // recompute the children array memory::copy(new_parents, parents_); - util::make_partition(child_index_, child_count(parents_)); + arb::util::make_partition(child_index_, child_count(parents_)); std::vector pos(parents_.size(), 0); for (auto i = 1u; i < parents_.size(); ++i) { diff --git a/arbor/tree.hpp b/arbor/tree.hpp index 75b6fb2f8..137598318 100644 --- a/arbor/tree.hpp +++ b/arbor/tree.hpp @@ -2,12 +2,16 @@ #include #include +#include +#include #include #include #include +#include "memory/memory.hpp" #include "util/rangeutil.hpp" +#include "util/span.hpp" namespace arb { diff --git a/arbor/util/cycle.hpp b/arbor/util/cycle.hpp new file mode 100644 index 000000000..905c59e0d --- /dev/null +++ b/arbor/util/cycle.hpp @@ -0,0 +1,213 @@ +#pragma once + +#include +#include +#include + +#include "util/iterutil.hpp" +#include "util/range.hpp" + +namespace arb { +namespace util { + +template +class cyclic_iterator : public iterator_adaptor, I> { + using base = iterator_adaptor, I>; + friend class iterator_adaptor, I>; + + I begin_; + I inner_; + S end_; + typename base::difference_type off_; // offset from begin + + const I& inner() const { + return inner_; + } + + I& inner() { + return inner_; + } + +public: + using value_type = typename base::value_type; + using difference_type = typename base::difference_type; + + cyclic_iterator() = default; + + template + cyclic_iterator(Iter&& iter, Sentinel&& sentinel) + : begin_(std::forward(iter)), + inner_(std::forward(iter)), + end_(std::forward(sentinel)), + off_(0) + { } + + cyclic_iterator(const cyclic_iterator& other) + : begin_(other.begin_), + inner_(other.inner_), + end_(other.end_), + off_(other.off_) + { } + + cyclic_iterator(cyclic_iterator&& other) + : begin_(std::move(other.begin_)), + inner_(std::move(other.inner_)), + end_(std::move(other.end_)), + off_(other.off_) + { } + + + cyclic_iterator& operator=(const cyclic_iterator& other) { + if (this != &other) { + inner_ = other.inner_; + begin_ = other.begin_; + end_ = other.end_; + off_ = other.off_; + } + + return *this; + } + + cyclic_iterator& operator=(cyclic_iterator&& other) { + if (this != &other) { + inner_ = std::move(other.inner_); + begin_ = std::move(other.begin_); + end_ = std::move(other.end_); + off_ = other.off_; + } + + return *this; + } + + // forward and input iterator requirements + value_type operator*() const { + return *inner_; + } + + value_type operator[](difference_type n) const { + return *(*this + n); + } + + cyclic_iterator& operator++() { + if (++inner_ == end_) { + // wrap around + inner_ = begin_; + } + + ++off_; + return *this; + } + + cyclic_iterator operator++(int) { + cyclic_iterator iter(*this); + ++(*this); + return iter; + } + + cyclic_iterator& operator--() { + if (inner_ == begin_) { + // wrap around; use upto() to handle efficiently the move to the end + // in case inner_ is a bidirectional iterator + inner_ = upto(inner_, end_); + } + else { + --inner_; + } + + --off_; + return *this; + } + + cyclic_iterator operator--(int) { + cyclic_iterator iter(*this); + --(*this); + return iter; + } + + cyclic_iterator& operator+=(difference_type n) { + // wrap distance + auto size = util::distance(begin_, end_); + + // calculate distance from begin + auto pos = (off_ += n); + if (pos < 0) { + auto mod = -pos % size; + pos = mod ? size - mod : 0; + } + else { + pos = pos % size; + } + + inner_ = std::next(begin_, pos); + return *this; + } + + cyclic_iterator& operator-=(difference_type n) { + return this->operator+=(-n); + } + + bool operator==(const cyclic_iterator& other) const { + return begin_ == other.begin_ && off_ == other.off_; + } + + bool operator!=(const cyclic_iterator& other) const { + return !(*this == other); + } + + cyclic_iterator operator-(difference_type n) const { + cyclic_iterator c(*this); + return c -= n; + } + + difference_type operator-(const cyclic_iterator& other) const { + return off_ - other.off_; + } + + bool operator<(const cyclic_iterator& other) const { + return off_ < other.off_; + } + + // expose inner iterator for testing against a sentinel + template + bool operator==(const Sentinel& s) const { + return inner_ == s; + } + + template + bool operator!=(const Sentinel& s) const { + return !(inner_ == s); + } +}; + +template +cyclic_iterator make_cyclic_iterator(const I& iter, const S& sentinel) { + return cyclic_iterator(iter, sentinel); +} + + +template +auto cyclic_view(Seq&& s) { + using std::begin; + using std::end; + + auto b = begin(s); + auto e = end(s); + + if constexpr (is_regular_sequence_v) { + return make_range(make_cyclic_iterator(b, e), make_cyclic_iterator(e, e)); + } + else { + return make_range(make_cyclic_iterator(b, e), e); + } +} + +// Handle initializer lists +template +auto cyclic_view(const std::initializer_list& list) { + return make_range( + make_cyclic_iterator(list.begin(), list.end()), + make_cyclic_iterator(list.end(), list.end())); +} + +} // namespace util +} // namespace arb diff --git a/arbor/util/meta.hpp b/arbor/util/meta.hpp index ed0055f08..d9f30584f 100644 --- a/arbor/util/meta.hpp +++ b/arbor/util/meta.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include diff --git a/arbor/util/nop.hpp b/arbor/util/nop.hpp new file mode 100644 index 000000000..4f3fdd3dd --- /dev/null +++ b/arbor/util/nop.hpp @@ -0,0 +1,33 @@ +#pragma once + +/* + * Provide object that implicitly converts to + * a std::function object that does nothing but return a + * default-constructed type or void. + */ + +#include + +namespace arb { +namespace util { + +struct nop_function_t { + template + operator std::function() const { + return [](Args...) { return R{}; }; + } + + template + operator std::function() const { + return [](Args...) { }; + } + + // keep clang happy: see CWG issue #253, + // http://open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#253 + constexpr nop_function_t() {} +}; + +static constexpr nop_function_t nop_function; + +} // namespace util +} // namespace arb diff --git a/arbor/util/piecewise.hpp b/arbor/util/piecewise.hpp index 9fbd936ef..f047b2bbd 100644 --- a/arbor/util/piecewise.hpp +++ b/arbor/util/piecewise.hpp @@ -1,5 +1,6 @@ #pragma once + // Create/manipulate 1-d piecewise defined objects. // // A `pw_element` describes a _value_ of type `A` and an _extent_ of @@ -397,6 +398,7 @@ struct pw_elements { void push_back(double left, double right, U&& v) { if (!empty() && left != vertex_.back()) throw std::runtime_error("noncontiguous element"); if (right(v)); if (vertex_.empty()) vertex_.push_back(left); @@ -405,7 +407,10 @@ struct pw_elements { template void push_back(double right, U&& v) { - if (empty()) throw std::runtime_error("require initial left vertex for element"); + if (empty()) { + throw std::runtime_error("require initial left vertex for element"); + } + push_back(vertex_.back(), right, std::forward(v)); } @@ -419,41 +424,37 @@ struct pw_elements { using std::begin; using std::end; - // Invariant, see below - // empty() || value_.size() + 1 = vertex_.size() - auto vs = std::size(vertices); - auto es = std::size(values); - // check invariant - if (!((es == 0 && es == vs) - || (es != 0 && es + 1 == vs))) { - // TODO(fmt): Make a better error w/ format. - throw std::runtime_error{"Vertices and values need to have matching lengths"}; - } - - // clean-up - clear(); - - // We know that invariant holds from here on. - if (es == 0) return; - auto vi = begin(vertices); + auto ve = end(vertices); auto ei = begin(values); auto ee = end(values); - reserve(vs); + if (ei == ee) { // empty case + if (vi != ve) throw std::runtime_error{"Vertices and values need to have same length; values too long."}; + clear(); + return; + } + clear(); + if (vi == ve) throw std::runtime_error{"Vertices and values need to have same length; values too short."}; + + reserve(vertices.size()); double left = *vi++; double right = *vi++; push_back(left, right, *ei++); + while (ei != ee) { + if (vi == ve) throw std::runtime_error{"Vertices and values need to have same length; values too short."}; double right = *vi++; push_back(right, *ei++); } + if (vi != ve) throw std::runtime_error{"Vertices and values need to have same length; values too long."}; } private: // Consistency requirements: // 1. empty() || value_.size()+1 = vertex_.size() // 2. vertex_[i]<=vertex_[j] for i<=j. + std::vector vertex_; std::vector value_; }; diff --git a/arbor/util/range.hpp b/arbor/util/range.hpp index d01ce3585..3a7257b7c 100644 --- a/arbor/util/range.hpp +++ b/arbor/util/range.hpp @@ -158,9 +158,10 @@ template auto canonical_view(Seq&& s) { using std::begin; using std::end; - auto b = begin(s); - auto e = end(s); - return make_range(make_sentinel_iterator(b, e), make_sentinel_end(b, e)); + + return make_range( + make_sentinel_iterator(begin(s), end(s)), + make_sentinel_end(begin(s), end(s))); } // Strictly evaluate end point in sentinel-terminated range and present as a range over @@ -170,6 +171,7 @@ template auto strict_view(Seq&& s) { using std::begin; using std::end; + auto b = begin(s); auto e = end(s); return make_range(b, b==e? b: std::next(util::upto(b, e))); diff --git a/arbor/util/rangeutil.hpp b/arbor/util/rangeutil.hpp index 48bac64c0..2d08638ab 100644 --- a/arbor/util/rangeutil.hpp +++ b/arbor/util/rangeutil.hpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -18,6 +19,18 @@ namespace arb { namespace util { +// Present a single item as a range + +template +range singleton_view(T& item) { + return {&item, &item+1}; +} + +template +range singleton_view(const T& item) { + return {&item, &item+1}; +} + // Non-owning views and subviews template diff --git a/arborio/cableio.cpp b/arborio/cableio.cpp index 61dd4af5a..4ca05cc64 100644 --- a/arborio/cableio.cpp +++ b/arborio/cableio.cpp @@ -19,7 +19,7 @@ namespace arborio { using namespace arb; -ARB_ARBORIO_API std::string acc_version() {return "0.10-dev";} +ARB_ARBORIO_API std::string acc_version() {return "0.9-dev";} cableio_parse_error::cableio_parse_error(const std::string& msg, const arb::src_location& loc): arb::arbor_exception(msg+" at :"+ @@ -116,6 +116,12 @@ s_expr mksexp(const mpoint& p) { s_expr mksexp(const msegment& seg) { return slist("segment"_symbol, (int)seg.id, mksexp(seg.prox), mksexp(seg.dist), seg.tag); } +// This can be removed once cv_policy is removed from the decor. +s_expr mksexp(const cv_policy& c) { + std::stringstream s; + s << c; + return slist("cv-policy"_symbol, parse_s_expr(s.str())); +} s_expr mksexp(const decor& d) { auto round_trip = [](auto& x) { std::stringstream s; @@ -139,11 +145,6 @@ s_expr mksexp(const decor& d) { } return {"decor"_symbol, slist_range(decorations)}; } -s_expr mksexp(const cv_policy& c) { - std::stringstream s; - s << c; - return slist("cv-policy"_symbol, parse_s_expr(s.str())); -} s_expr mksexp(const label_dict& dict) { auto round_trip = [](auto& x) { std::stringstream s; @@ -201,10 +202,8 @@ ARB_ARBORIO_API std::ostream& write_component(std::ostream& o, const morphology& return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), mksexp(x))}; } ARB_ARBORIO_API std::ostream& write_component(std::ostream& o, const cable_cell& x, const meta_data& m) { - if (m.version != acc_version()) throw cableio_version_error(m.version); - if (const auto& cvp = x.discretization()) { - auto cell = s_expr{"cable-cell"_symbol, slist(mksexp(x.morphology()), mksexp(x.labels()), mksexp(x.decorations()), mksexp(*cvp))}; - return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), cell)}; + if (m.version != acc_version()) { + throw cableio_version_error(m.version); } auto cell = s_expr{"cable-cell"_symbol, slist(mksexp(x.morphology()), mksexp(x.labels()), mksexp(x.decorations()))}; return o << s_expr{"arbor-component"_symbol, slist(mksexp(m), cell)}; @@ -360,36 +359,20 @@ morphology make_morphology(const std::vector>& args) } // Define cable-cell maker -// Accepts the morphology, decor, label_dict and cv_policy arguments in any order as a vector -cable_cell make_cable_cell4(const std::vector>& args) { +// Accepts the morphology, decor and label_dict arguments in any order as a vector +cable_cell make_cable_cell(const std::vector>& args) { decor dec; label_dict dict; morphology morpho; - std::optional cvp; for(const auto& a: args) { auto cable_cell_visitor = arb::util::overload( - [&](const morphology& q) { morpho = q; }, - [&](const label_dict& q) { dict = q; }, - [&](const cv_policy& q) { cvp = q; }, - [&](const decor& q){ dec = q; }); + [&](const morphology & p) { morpho = p; }, + [&](const label_dict & p) { dict = p; }, + [&](const decor & p){ dec = p; }); std::visit(cable_cell_visitor, a); } - return cable_cell(morpho, dec, dict, cvp); + return cable_cell(morpho, dec, dict); } -cable_cell make_cable_cell3(const std::vector>& args) { - decor dec; - label_dict dict; - morphology morpho; - for(const auto& a: args) { - auto cable_cell_visitor = arb::util::overload( - [&](const morphology& q) { morpho = q; }, - [&](const label_dict& q) { dict = q; }, - [&](const decor& q){ dec = q; }); - std::visit(cable_cell_visitor, a); - } - return cable_cell(morpho, dec, dict, {}); -} - version_tuple make_version(const std::string& v) { return version_tuple{v}; } @@ -609,16 +592,6 @@ parse_hopefully eval(const s_expr& e, const eval_map& map, const eval_ auto& hd = e.head(); if (hd.is_atom()) { auto& atom = hd.atom(); - if (atom.spelling == "cv-policy") { - // NOTE tail produces a single item list? - auto res = parse_cv_policy_expression(e.tail().head()); - if (res) { - return res.value(); - } - else { - return res.error(); - } - } // If this is not a symbol, parse as a tuple if (atom.kind != tok::symbol) { auto args = eval_args(e, map, vec); @@ -668,6 +641,9 @@ parse_hopefully eval(const s_expr& e, const eval_map& map, const eval_ if (match(l->type())) return eval_cast(l.value()); } + // Or it could be a cv-policy expression + if (auto p = parse_cv_policy_expression(e)) return p.value(); + // Unable to find a match: try to return a helpful error message. const auto nc = std::distance(matches.first, matches.second); std::string msg = "No matches for found for "+name+" with "+std::to_string(args->size())+" arguments.\n" @@ -697,26 +673,27 @@ eval_map named_evals{ ARBIO_ADD_ION_EVAL("ion-diffusivity", make_ion_diffusivity), {"quantity", make_call(make_quantity, "'quantity' with a value:real and a unit:string")}, {"envelope", make_arg_vec_call(make_envelope, - "'envelope' with one or more pairs of start time and amplitude (start:real amplitude:real)")}, + "'envelope' with one or more pairs of start time and amplitude (start:real amplitude:real)")}, {"envelope-pulse", make_call(make_envelope_pulse, - "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, + "'envelope-pulse' with 3 arguments (delay:real duration:real amplitude:real)")}, {"current-clamp", make_call, double, double>(make_i_clamp, - "'current-clamp' with 3 arguments (env:envelope freq:real phase:real)")}, + "'current-clamp' with 3 arguments (env:envelope freq:real phase:real)")}, {"current-clamp", make_call(make_i_clamp_pulse, - "'current-clamp' with 3 arguments (env:envelope_pulse freq:real phase:real)")}, + "'current-clamp' with 3 arguments (env:envelope_pulse freq:real phase:real)")}, {"threshold-detector", make_call(arb::threshold_detector::from_raw_millivolts, - "'threshold-detector' with 1 argument (threshold:real)")}, + "'threshold-detector' with 1 argument (threshold:real)")}, {"mechanism", make_mech_call("'mechanism' with a name argument, and 0 or more parameter settings" - "(name:string (param:string val:real))")}, + "(name:string (param:string val:real))")}, {"ion-reversal-potential-method", make_call( make_ion_reversal_potential_method, - "'ion-reversal-potential-method' with 2 arguments (ion:string mech:mechanism)")}, - {"cv-policy", make_call(make_cv_policy, "'cv-policy' with 1 argument (p:policy)")}, + "'ion-reversal-potential-method' with 2 arguments (ion:string mech:mechanism)")}, + {"cv-policy", make_call(make_cv_policy, + "'cv-policy' with 1 argument (p:policy)")}, {"junction", make_call(make_wrapped_mechanism, "'junction' with 1 argumnet (m: mechanism)")}, {"synapse", make_call(make_wrapped_mechanism, "'synapse' with 1 argumnet (m: mechanism)")}, {"density", make_call(make_wrapped_mechanism, "'density' with 1 argumnet (m: mechanism)")}, {"voltage-process", make_call(make_wrapped_mechanism, "'voltage-process' with 1 argumnet (m: mechanism)")}, {"scaled-mechanism", make_scaled_mechanism_call("'scaled_mechanism' with a density argument, and 0 or more parameter scaling expressions" - "(d:density (param:string val:iexpr))")}, + "(d:density (param:string val:iexpr))")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset c:current-clamp name:string)")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset t:threshold-detector name:string)")}, {"place", make_call(make_place, "'place' with 3 arguments (ls:locset gj:junction name:string)")}, @@ -742,42 +719,39 @@ eval_map named_evals{ {"default", make_call(make_default, "'default' with 1 argument (v:ion-diffusivity)")}, {"default", make_call(make_default, "'default' with 1 argument (v:ion-reversal-potential)")}, {"default", make_call(make_default, "'default' with 1 argument (v:ion-reversal-potential-method)")}, + {"default", make_call(make_default, "'default' with 1 argument (v:cv-policy)")}, {"locset-def", make_call(make_locset_pair, - "'locset-def' with 2 arguments (name:string ls:locset)")}, + "'locset-def' with 2 arguments (name:string ls:locset)")}, {"region-def", make_call(make_region_pair, - "'region-def' with 2 arguments (name:string reg:region)")}, + "'region-def' with 2 arguments (name:string reg:region)")}, {"iexpr-def", make_call(make_iexpr_pair, - "'iexpr-def' with 2 arguments (name:string e:iexpr)")}, + "'iexpr-def' with 2 arguments (name:string e:iexpr)")}, {"point", make_call(make_point, - "'point' with 4 arguments (x:real y:real z:real radius:real)")}, + "'point' with 4 arguments (x:real y:real z:real radius:real)")}, {"segment", make_call(make_segment, - "'segment' with 4 arguments (parent:int prox:point dist:point tag:int)")}, + "'segment' with 4 arguments (parent:int prox:point dist:point tag:int)")}, {"branch", make_branch_call( - "'branch' with 2 integers and 1 or more segment arguments (id:int parent:int s0:segment s1:segment ..)")}, + "'branch' with 2 integers and 1 or more segment arguments (id:int parent:int s0:segment s1:segment ..)")}, {"decor", make_arg_vec_call(make_decor, - "'decor' with 1 or more `paint`, `place` or `default` arguments")}, + "'decor' with 1 or more `paint`, `place` or `default` arguments")}, {"label-dict", make_arg_vec_call(make_label_dict, - "'label-dict' with 1 or more `locset-def` or `region-def` or `iexpr-def` arguments")}, + "'label-dict' with 1 or more `locset-def` or `region-def` or `iexpr-def` arguments")}, {"morphology", make_arg_vec_call(make_morphology, - "'morphology' 1 or more `branch` arguments")}, + "'morphology' 1 or more `branch` arguments")}, - {"cable-cell", - make_unordered_call(make_cable_cell4, - "'cable-cell' with 4 arguments: `morphology`, `label-dict`, `decor`, and `cv_policy` in any order")}, - {"cable-cell", - make_unordered_call(make_cable_cell3, - "'cable-cell' with 3 arguments: `morphology`, `label-dict`, and `decor` in any order")}, + {"cable-cell", make_unordered_call(make_cable_cell, + "'cable-cell' with 3 arguments: `morphology`, `label-dict`, and `decor` in any order")}, {"version", make_call(make_version, "'version' with one argment (val:std::string)")}, {"meta-data", make_call(make_meta_data, "'meta-data' with one argument (v:version)")}, - {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:decor)")}, - {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:label_dict)")}, - {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:morphology)")}, - {"arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:cable_cell)")} + { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:decor)")}, + { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:label_dict)")}, + { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:morphology)")}, + { "arbor-component", make_call(make_component, "'arbor-component' with 2 arguments (m:meta_data p:cable_cell)")} }; #undef ARBIO_ADD_EVAL diff --git a/arborio/cv_policy_parse.cpp b/arborio/cv_policy_parse.cpp index b371806ce..7e5bc2fe4 100644 --- a/arborio/cv_policy_parse.cpp +++ b/arborio/cv_policy_parse.cpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include @@ -8,6 +10,7 @@ #include #include + #include "parse_helpers.hpp" namespace arborio { @@ -26,43 +29,43 @@ template using parse_hopefully = arb::util::expected eval_map {{"default", - make_call<>([] () { return arb::default_cv_policy(); }, + make_call<>([] () { return arb::cv_policy{arb::default_cv_policy()}; }, "'default' with no arguments")}, {"every-segment", - make_call<>([] () { return arb::cv_policy_every_segment(); }, + make_call<>([] () { return arb::cv_policy{arb::cv_policy_every_segment()}; }, "'every-segment' with no arguments")}, {"every-segment", - make_call([] (const region& r) { return arb::cv_policy_every_segment(r); }, + make_call([] (const region& r) { return arb::cv_policy{arb::cv_policy_every_segment(r) }; }, "'every-segment' with one argument (every-segment (reg:region))")}, {"fixed-per-branch", - make_call([] (int i) { return arb::cv_policy_fixed_per_branch(i); }, + make_call([] (int i) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i) }; }, "'every-segment' with one argument (fixed-per-branch (count:int))")}, {"fixed-per-branch", - make_call([] (int i, const region& r) { return arb::cv_policy_fixed_per_branch(i, r); }, + make_call([] (int i, const region& r) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r) }; }, "'every-segment' with two arguments (fixed-per-branch (count:int) (reg:region))")}, {"fixed-per-branch", - make_call([] (int i, const region& r, cv_policy_flag f) { return arb::cv_policy_fixed_per_branch(i, r, f); }, - "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:flag))")}, + make_call([] (int i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_fixed_per_branch(i, r, f) }; }, + "'fixed-per-branch' with three arguments (fixed-per-branch (count:int) (reg:region) (flags:int))")}, {"max-extent", - make_call([] (double i) { return arb::cv_policy_max_extent(i); }, + make_call([] (double i) { return arb::cv_policy{arb::cv_policy_max_extent(i) }; }, "'max-extent' with one argument (max-extent (length:double))")}, {"max-extent", - make_call([] (double i, const region& r) { return arb::cv_policy_max_extent(i, r); }, + make_call([] (double i, const region& r) { return arb::cv_policy{arb::cv_policy_max_extent(i, r) }; }, "'max-extent' with two arguments (max-extent (length:double) (reg:region))")}, {"max-extent", - make_call([] (double i, const region& r, cv_policy_flag f) { return arb::cv_policy_max_extent(i, r, f); }, - "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:flag))")}, + make_call([] (double i, const region& r, int f) { return arb::cv_policy{arb::cv_policy_max_extent(i, r, f) }; }, + "'max-extent' with three arguments (max-extent (length:double) (reg:region) (flags:int))")}, {"single", - make_call<>([] () { return arb::cv_policy_single(); }, + make_call<>([] () { return arb::cv_policy{arb::cv_policy_single()}; }, "'single' with no arguments")}, {"single", - make_call([] (const region& r) { return arb::cv_policy_single(r); }, + make_call([] (const region& r) { return arb::cv_policy{arb::cv_policy_single(r) }; }, "'single' with one argument (single (reg:region))")}, {"explicit", - make_call([] (const locset& l) { return arb::cv_policy_explicit(l); }, + make_call([] (const locset& l) { return arb::cv_policy{arb::cv_policy_explicit(l) }; }, "'explicit' with one argument (explicit (ls:locset))")}, {"explicit", - make_call([] (const locset& l, const region& r) { return arb::cv_policy_explicit(l, r); }, + make_call([] (const locset& l, const region& r) { return arb::cv_policy{arb::cv_policy_explicit(l, r) }; }, "'explicit' with two arguments (explicit (ls:locset) (reg:region))")}, {"join", make_fold([](cv_policy l, cv_policy r) { return l + r; }, @@ -70,12 +73,6 @@ eval_map {{"default", {"replace", make_fold([](cv_policy l, cv_policy r) { return l | r; }, "'replace' with at least 2 arguments: (replace cv_policy cv_policy ...)")}, - {"flag-none", - make_call<>([] () { return cv_policy_flag::none; }, - "'flag:none' with no arguments")}, - {"flag-interior-forks", - make_call<>([] () { return cv_policy_flag::interior_forks; }, - "'flag-interior-forks' with no arguments")}, }; parse_hopefully eval(const s_expr& e); diff --git a/arborio/include/arborio/cv_policy_parse.hpp b/arborio/include/arborio/cv_policy_parse.hpp index 6df585877..70836105c 100644 --- a/arborio/include/arborio/cv_policy_parse.hpp +++ b/arborio/include/arborio/cv_policy_parse.hpp @@ -1,5 +1,6 @@ #pragma once +#include #include #include diff --git a/cmake/CheckCompilerXLC.cmake b/cmake/CheckCompilerXLC.cmake new file mode 100644 index 000000000..7354dd279 --- /dev/null +++ b/cmake/CheckCompilerXLC.cmake @@ -0,0 +1,17 @@ +# CMake (at least sometimes) misidentifies XL 13 for Linux as Clang. + +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + try_compile(ignore ${CMAKE_BINARY_DIR} ${PROJECT_SOURCE_DIR}/cmake/dummy.cpp COMPILE_DEFINITIONS --version OUTPUT_VARIABLE cc_out) + string(REPLACE "\n" ";" cc_out "${cc_out}") + foreach(line ${cc_out}) + if(line MATCHES "^IBM XL C") + set(CMAKE_CXX_COMPILER_ID "XL") + endif() + endforeach(line) +endif() + +# If we _do_ find xlC, don't try and build: too many bugs! + +if(CMAKE_CXX_COMPILER_ID STREQUAL "XL") + message(FATAL_ERROR "Arbor does not support being built by the IBM xlC compiler") +endif() diff --git a/cmake/CompilerOptions.cmake b/cmake/CompilerOptions.cmake index 751f7ac68..503e04610 100644 --- a/cmake/CompilerOptions.cmake +++ b/cmake/CompilerOptions.cmake @@ -74,28 +74,24 @@ set(CXXOPT_WALL # because there is nothing to fix on our side. $,-Wno-psabi,> -) - -# Check for supported compilers / versions -function(check_supported_cxx) - set(cxx_supported_ids "AppleClang" "GNU" "Clang") - set(cxx_supported_ver 15 12 12) - foreach(id ver IN ZIP_LISTS cxx_supported_ids cxx_supported_ver) - if(CMAKE_CXX_COMPILER_ID MATCHES ${id} AND CMAKE_CXX_COMPILER_VERSION GREATER_EQUAL ${ver}) - return() - endif() - endforeach() - message(WARNING "Found an unsupported compiler ${CMAKE_CXX_COMPILER_ID} version ${CMAKE_CXX_COMPILER_VERSION}, please consider switching to a supported version of GCC or Clang. Build failure is expected. We reserve the option to close all related issues without consideration.") -endfunction() + + # Intel: + # + # Disable warning for unused template parameter + # this is raised by a templated function in the json library. + + $,-wd488,>) # Set ${optvar} in parent scope according to requested architecture. # Architectures are given by the same names that GCC uses for its # -mcpu or -march options. + function(set_arch_target optvar optvar_cuda_guarded arch) if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") # Correct compiler option unfortunately depends upon the target architecture family. # Extract this information from running the configured compiler with --verbose. + try_compile(ignore ${CMAKE_BINARY_DIR} ${PROJECT_SOURCE_DIR}/cmake/dummy.cpp COMPILE_DEFINITIONS --verbose OUTPUT_VARIABLE cc_out) string(REPLACE "\n" ";" cc_out "${cc_out}") set(target) @@ -110,22 +106,22 @@ function(set_arch_target optvar optvar_cuda_guarded arch) # architecture. # See clang / gcc manuals and: # https://maskray.me/blog/2022-08-28-march-mcpu-mtune - if(CMAKE_CXX_COMPILER_ID MATCHES "AppleClang") - set(arch_opt "-march=${arch}") + if (CMAKE_CXX_COMPILER_ID MATCHES "AppleClang" AND CMAKE_CXX_COMPILER_VERSION LESS 15) + set(arch_opt "") elseif (CMAKE_CXX_COMPILER_ID MATCHES "GNU") - if("${target}" MATCHES "(arm64|aarch64)-.*") + if ("${target}" MATCHES "(arm64|aarch64)-.*") # on AArch64, this is correct, ... set(arch_opt "-mcpu=${arch} -mtune=${arch}") else() # ... however on x86 mcpu _is_ mtune _and_ deprecated (since 2003!), but ... set(arch_opt "-march=${arch}") - endif() + endif () elseif (CMAKE_CXX_COMPILER_ID MATCHES "Clang") # ... clang likes march (and possibly mtune) # See https://discourse.llvm.org/t/when-to-use-mcpu-versus-march/47953/9 set(arch_opt "-march=${arch} -mtune=${arch}") - else() - message(STATUS "Setting fallback architecture flags for ${CMAKE_CXX_COMPILER}.") + else () + message(STATUS "Falling back to -march=${arch} for compiler ${CMAKE_CXX_COMPILER_ID}") set(arch_opt "-march=${arch}") endif() endif() @@ -144,6 +140,7 @@ function(set_arch_target optvar optvar_cuda_guarded arch) else() set("${optvar_cuda_guarded}" "${arch_opt}" PARENT_SCOPE) endif() + endfunction() # Set ${has_sve} and ${sve_length} in parent scope according to auto detection. diff --git a/doc/concepts/cable_cell.rst b/doc/concepts/cable_cell.rst index 5ed43566f..3364f20fb 100644 --- a/doc/concepts/cable_cell.rst +++ b/doc/concepts/cable_cell.rst @@ -7,27 +7,17 @@ An Arbor *cable cell* is a full :ref:`description ` of a cell with morphology and cell dynamics like ion species and their properties, ion channels, synapses, gap junction mechanisms, stimuli and threshold detectors. -Cable cells are constructed from four components: - -* :ref:`Morphology `: a description of the geometry and branching - structure of the cell shape. -* :ref:`Label dictionary `: a set of definitions and a :abbr:`DSL - (domain specific language)` that refer to regions and locations on the cell - morphology. -* :ref:`Decor `: a description of the dynamics on the - cell, placed according to the named rules in the dictionary. It can reference - :ref:`mechanisms` from mechanism catalogues. -* :ref:`Discretization `: a description of the geometry and branching structure of the cell shape. +* :ref:`Label dictionary `: a set of definitions and a :abbr:`DSL (domain specific language)` that refer to regions and locations on the cell morphology. +* :ref:`Decor `: a description of the dynamics on the cell, placed according to the named rules in the dictionary. It can reference :ref:`mechanisms` from mechanism catalogues. When a cable cell is constructed the following steps are performed using the inputs: -1. Concrete regions and locsets are generated for the morphology for each - labelled region and locset in the dictionary -2. The default values for parameters specified in the decor, such as ion species - concentration, are instantiated. -3. Dynamics (mechanisms, parameters, synapses, gap junctions etc.) are - instantiated on the regions and locsets as specified by the decor. +1. Concrete regions and locsets are generated for the morphology for each labelled region and locset in the dictionary +2. The default values for parameters specified in the decor, such as ion species concentration, are instantiated. +3. Dynamics (mechanisms, parameters, synapses, gap junctions etc.) are instantiated on the regions and locsets as specified by the decor. Once constructed, the cable cell can be queried for specific information about the cell, but it can't be modified (it is *immutable*). @@ -56,7 +46,6 @@ Once constructed, the cable cell can be queried for specific information about t labels mechanisms decor - discretization API --- diff --git a/doc/concepts/discretization.rst b/doc/concepts/discretization.rst deleted file mode 100644 index af6377c6e..000000000 --- a/doc/concepts/discretization.rst +++ /dev/null @@ -1,55 +0,0 @@ -.. _discretization: - -.. _cablecell-discretization: - -Discretization -============== - -Before Arbor can actually simulate the behavior of a cell using the interplay of -morpholgy, mechanisms, etc it needs to turn the contiguous morphology into a -collection of discrete, conical sub-volumes. These are called 'compartments' or -'control volumes' (CV). The granularity of the subdivision controls the -precision of the simulation and its computational cost. - -Arbor offers a set of composable discretization policies - -.. label:: (cv-policy-explicit locset region) - - Use the subdivision as given by ``locset`` - -.. label:: (cv-policy-max-extent ext region flags) - - Subdivision into CVs of at most ``ext`` :math:`\mu m`. In the vicinity of - fork points, smaller CVs might be used to avoid producing CVs containing - forks, unless ``flags`` is ``(flag-interior-forks)``. - -.. label:: (cv-policy-fixed-per-branch n region) - - Subdivide each branch into ``n`` equal CVs. In the vicinity of fork points, - smaller CVs might be used to avoid producing CVs containing forks, unless - ``flags`` is ``(flag-interior-forks)``. - -.. label:: (cv-policy-every-segment region) - - Each segment --- as given during morphology construction --- will produce - one CV. - -.. label:: (cv-policy-default) = (cv-policy-fixed-per-branch 1) - - Each branch will produce one CV. - -.. label:: (cv-policy-single region) - - The whole region will produce one CV. - -In all cases ``region`` is optional and defaults to ``(all)``, i.e. the whole -cell. These policies compose through - -.. label:: (join cvp1 cvp2) - - Use the union of the boundary points defined by ``cvp1`` and ``cvp2``. - -.. label:: (replace cvp1 cvp2) - - Use the boundary points defined by ``cvp1`` everywhere except where ``cvp2`` - is defined. There, use those of ``cvp2``. diff --git a/doc/dependencies.csv b/doc/dependencies.csv index 60a33441a..2eac7a26a 100644 --- a/doc/dependencies.csv +++ b/doc/dependencies.csv @@ -4,7 +4,7 @@ Build option/target,Tool name,Minimum version,Notes,Update criteria --,C++ compiler,,C++17 support. See below, --,GCC,12.0.0,,"* it is supported by the minimum version of CUDA. * it is available either by default or can be installed using standard package manager on the supported Linux versions." ---,Clang,13.0,, +--,Clang,12.0,, --,Apple Clang,15,Apple LLVM version 15.0.0 (clang-1500.x.y.z), ARB_GPU,Hip Clang,ROCm 3.9,HIP support is currently experimental., ARB_GPU,CUDA,12.0,,"* It is available on all of the target HPC systems (Piz Daint, Juwels Booster)" diff --git a/doc/fileformat/cable_cell.rst b/doc/fileformat/cable_cell.rst index 74cd3de0c..d967e0904 100644 --- a/doc/fileformat/cable_cell.rst +++ b/doc/fileformat/cable_cell.rst @@ -396,7 +396,7 @@ Parsable arbor-components and meta-data The formats described above can be used to generate a :ref:`label dictionary `, :ref:`decoration `, :ref:`morphology `, or :ref:`cable cell ` object. These are denoted as arbor-components. Arbor-components need to be accompanied by *meta-data* -specifying the version of the format being used. The only version currently supported is ``0.10-dev``. +specifying the version of the format being used. The only version currently supported is ``0.9-dev``. .. label:: (version val:string) @@ -418,7 +418,7 @@ Label-dict .. code:: lisp (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (label-dict (region-def "my_soma" (tag 1)) (locset-def "root" (root)))) @@ -429,7 +429,7 @@ Decoration .. code:: lisp (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (decor (default (membrane-potential -55.000000)) (place (locset "root") (synapse (mechanism "expsyn")) "root_synapse") @@ -441,7 +441,7 @@ Morphology .. code:: lisp (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (morphology (branch 0 -1 (segment 0 (point 0 0 0 2) (point 4 0 0 2) 1) @@ -454,7 +454,7 @@ Cable-cell .. code:: lisp (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (cable-cell (label-dict (region-def "my_soma" (tag 1)) diff --git a/doc/index.rst b/doc/index.rst index 8d35bce97..525f78a54 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -96,7 +96,7 @@ Cite (Bibtex format) :target: https://doi.org/10.1109/EMPDP.2019.8671560 .. |zlatest| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.13284789.svg - :target: https://doi.org/10.5281/zenodo.13284789 + :target: https://doi.org/10.5281/zenodo.13284789image:: .. |z0100| image:: https://zenodo.org/badge/DOI/10.5281/zenodo.13284789.svg :target: https://doi.org/10.5281/zenodo.13284789 diff --git a/doc/python/cable_cell.rst b/doc/python/cable_cell.rst index 267160bb5..b78a753eb 100644 --- a/doc/python/cable_cell.rst +++ b/doc/python/cable_cell.rst @@ -48,7 +48,7 @@ Cable cells # Construct a cable cell. cell = arbor.cable_cell(lmrf.morphology, decor, lmrf.labels) - .. method:: __init__(morphology, decorations, labels=None, discretization=None) + .. method:: __init__(morphology, decorations, labels) Constructor. @@ -58,23 +58,15 @@ Cable cells :type decorations: :py:class:`decor` :param labels: dictionary of labeled regions and locsets :type labels: :py:class:`label_dict` - :param discretization: discretization policy - :type discretization: :py:class:`cv_policy` - .. method:: discretization(policy) + .. method:: placed_lid_range(index) - Set the cv_policy used to discretise the cell into control volumes for simulation. - - :param policy: The cv_policy. - :type policy: :py:class:`cv_policy` - - .. method:: discretization(policy) - :noindex: - - Set the cv_policy used to discretise the cell into control volumes for simulation. - - :param str policy: :ref:`string representation ` of a cv_policy. + Returns the range of local indexes assigned to a placement in the decorations as a tuple of two integers, + that define the range of indexes as a half open interval. + :param index: the unique index of the placement. + :type index: int + :rtype: tuple(int, int) .. py:class:: ion diff --git a/doc/python/decor.rst b/doc/python/decor.rst index 1f8744e9a..8573fc47c 100644 --- a/doc/python/decor.rst +++ b/doc/python/decor.rst @@ -173,6 +173,20 @@ Cable cell decoration :type d: :py:class:`threshold_detector` :param str label: the label of the group of detectors on the locset. + .. method:: discretization(policy) + + Set the cv_policy used to discretise the cell into control volumes for simulation. + + :param policy: The cv_policy. + :type policy: :py:class:`cv_policy` + + .. method:: discretization(policy) + :noindex: + + Set the cv_policy used to discretise the cell into control volumes for simulation. + + :param str policy: :ref:`string representation ` of a cv_policy. + .. method:: paintings() Returns a list of tuples ``(region, painted_object)`` for inspection. diff --git a/doc/python/probe_sample.rst b/doc/python/probe_sample.rst index 202857c06..a5adf4f19 100644 --- a/doc/python/probe_sample.rst +++ b/doc/python/probe_sample.rst @@ -96,39 +96,39 @@ Example return cell def probes(self, gid): - return [A.cable_probe_membrane_voltage('(location 0 0.5)', tag="Um-soma"), - A.cable_probe_membrane_voltage_cell(tag="Um-cell"), - A.cable_probe_membrane_voltage('(join (location 0 0) (location 0 1))', tag="Um-ends"), + return [A.cable_probe_membrane_voltage('(location 0 0.5)'), + A.cable_probe_membrane_voltage_cell(), + A.cable_probe_membrane_voltage('(join (location 0 0) (location 0 1))'), ] - # Override the global_properties method + # (4.6) Override the global_properties method def global_properties(self, kind): return A.neuron_cable_properties() recipe = single_recipe() sim = A.simulation(recipe) - handles = {tag: sim.sample((0, n), A.regular_schedule(0.1*U.ms)) - for tag in ["Um-soma", "Um-cell", "Um-ends"]} + handles = [sim.sample((0, n), A.regular_schedule(0.1*U.ms)) + for n in range(3) ] sim.run(tfinal=1*U.ms) - for tag, hd in handles.items(): - print(f"Handle {hd} Tag '{}'") + for hd in handles: + print("Handle", hd) for d, m in sim.samples(hd): print(" * Meta:", m) print(" * Payload:", d.shape) -This script has a scalar probe, a vector probe, and a probeset involving two scalar probes. +This script has a single (scalar) probe, a single vector probe, and a probeset involving two scalar probes. The script is complete and can be run with Arbor installed, and will output: .. code-block:: - Handle 0 Tag 'Um-soma' + Handle 0 * Meta: (location 0 0.5) * Payload: (10, 2) - Handle 1 Tag 'Um-cell' + Handle 1 * Meta: [(cable 0 0 1), (cable 0 1 1), (cable 1 0 0), (cable 2 0 0), (cable 1 0 1), (cable 2 0 1)] * Payload: (10, 7) - Handle 2 Tag 'Um-ends' + Handle 2 * Meta: (location 0 0) * Payload: (10, 2) * Meta: (location 0 1) @@ -143,213 +143,177 @@ API An opaque object that is the Python representation of :cpp:class:`probe_info`. - See below for ways to create probes. In general, all probes are named via - the ``tag`` argument, as seen above. This tag is later used to retrieve the - data collected by the associated probes. + See below for ways to create probes. Membrane voltage -^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_membrane_voltage(where, tag) + .. py:function:: cable_probe_membrane_voltage(where) Cell membrane potential (mV) at the sites specified by the location expression string ``where``. This value is spatially interpolated. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_membrane_voltage_cell(tag) + .. py:function:: cable_probe_membrane_voltage_cell() Cell membrane potential (mV) associated with each cable in each CV of the cell discretization. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Axial current -^^^^^^^^^^^^^ - - .. py:function:: cable_probe_axial_current(where, tag) + .. py:function:: cable_probe_axial_current(where) Estimation of intracellular current (nA) in the distal direction at the sites specified by the location expression string ``where``. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. Ionic current -^^^^^^^^^^^^^ - - .. py:function:: cable_probe_ion_current_density(where, ion, tag) + .. py:function:: cable_probe_ion_current_density(where, ion) Transmembrane current density (A/m²) associated with the given ``ion`` at sites specified by the location expression string ``where``. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_current_cell(ion, tag) + .. py:function:: cable_probe_ion_current_cell(ion) Transmembrane current (nA) associated with the given ``ion`` across each cable in each CV of the cell discretization. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Total ionic current -^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_total_ion_current_density(where, tag) + .. py:function:: cable_probe_total_ion_current_density(where) Transmembrane current density (A/m²) _excluding_ capacitive currents at the sites specified by the location expression string ``where``. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_total_ion_current_cell(tag) + .. py:function:: cable_probe_total_ion_current_cell() Transmembrane current (nA) _excluding_ capacitive currents across each cable in each CV of the cell discretization. Stimulus currents are not included. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Total transmembrane current -^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_total_current_cell(tag) + .. py:function:: cable_probe_total_current_cell() Transmembrane current (nA) *including* capacitive currents across each cable in each CV of the cell discretization. Stimulus currents are not included. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Total stimulus current -^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_stimulus_current_cell(tag) + .. py:function:: cable_probe_stimulus_current_cell() Total stimulus current (nA) across each cable in each CV of the cell discretization. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Density mechanism state variable -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_density_state(where, mechanism, state, tag) + .. py:function:: cable_probe_density_state(where, mechanism, state) The value of the state variable ``state`` in the density mechanism ``mechanism`` at the sites specified by the location expression ``where``. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_density_state_cell(mechanism, state, tag) + .. py:function:: cable_probe_density_state_cell(mechanism, state) The value of the state variable ``state`` in the density mechanism ``mechanism`` on each cable in each CV of the cell discretization. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Point process state variable -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_point_state(target, mechanism, state, tag) + .. py:function:: cable_probe_point_state(target, mechanism, state) The value of the state variable ``state`` in the point process ``mechanism`` associated with the target index ``target`` on the cell. If the given mechanism is not associated with the target index, no probe will be generated. - **Metadata**: - - .. py:class:: cable_point_probe_info + Metadata: an object of type :class:`cable_point_probe_info`, comprising three fields: - .. py:attribute:: target + * ``target``: target index on the cell; - tag of target mechanism on the cell + * ``multiplicity``: number of targets sharing the same state in the discretization; - .. py:attribute:: lid + * ``location``: :class:`location` object corresponding to the target site. - local id of target; - - .. py:attribute:: multiplicity - - number of targets sharing the same state in the discretization; - - .. py:attribute:: location - - :class:`location` object corresponding to the target site. - - .. py:function:: cable_probe_point_state_cell(mechanism, state, tag) + .. py:function:: cable_probe_point_state_cell(mechanism, state) The value of the state variable ``state`` in the point process ``mechanism`` at each of the targets where that mechanism is defined. - **Metadata**: a list of :class:`cable_point_probe_info` values, one for each matching + Metadata: a list of :class:`cable_point_probe_info` values, one for each matching target. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Ionic internal concentration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_ion_int_concentration(where, ion, tag) + .. py:function:: cable_probe_ion_int_concentration(where, ion) Ionic internal concentration (mmol/L) of the given ``ion`` at the sites specified by the location expression string ``where``. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_int_concentration_cell(ion, tag) + .. py:function:: cable_probe_ion_int_concentration_cell(ion) Ionic internal concentration (mmol/L) of the given ``ion`` in each cable in each CV of the cell discretization. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Ionic external concentration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_ion_ext_concentration(where, ion, tag) + .. py:function:: cable_probe_ion_ext_concentration(where, ion) - Ionic external concentration (mM) of the given ``ion`` at the sites specified - by the location expression string ``where``. + Ionic external concentration (mmol/L) of the given ``ion`` at the + sites specified by the location expression string ``where``. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - .. py:function:: cable_probe_ion_ext_concentration_cell(ion, tag) + .. py:function:: cable_probe_ion_ext_concentration_cell(ion) Ionic external concentration (mmol/L) of the given ``ion`` in each able in each CV of the cell discretization. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. Ionic diffusion concrentration -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - - .. py:function:: cable_probe_ion_diff_concentration_cell(ion, tag) + .. py:function:: cable_probe_ion_diff_concentration_cell(ion) Diffusive ionic concentration of the given ``ion`` for each cable in each CV. - **Metadata**: the explicit :class:`location` of the sample site. + Metadata: the explicit :class:`location` of the sample site. - **Kind**: :term:`vector probe`. + Kind: :term:`vector probe`. - .. py:function:: cable_probe_ion_diff_concentration(where, ion, tag) + .. py:function:: cable_probe_ion_diff_concentration(where, ion) Diffusive ionic concentration of the given ``ion`` at the sites specified by the location expression string ``where``. - **Metadata**: the list of corresponding :class:`cable` objects. + Metadata: the list of corresponding :class:`cable` objects. .. _pycablecell-probesample-lif: @@ -357,8 +321,8 @@ LIF Cell probing ================ Membrane voltage - .. py:function:: lif_probe_voltage(tag) + .. py:function:: lif_probe_voltage() Current cell membrane potential (mV). - **Metadata**: none + Metadata: none diff --git a/doc/tutorial/index.rst b/doc/tutorial/index.rst index 3adb1567c..8ef283e2d 100644 --- a/doc/tutorial/index.rst +++ b/doc/tutorial/index.rst @@ -78,7 +78,6 @@ Advanced :maxdepth: 1 nmodl - plasticity Demonstrations -------------- diff --git a/doc/tutorial/plasticity.rst b/doc/tutorial/plasticity.rst deleted file mode 100644 index ea6aa58bc..000000000 --- a/doc/tutorial/plasticity.rst +++ /dev/null @@ -1,308 +0,0 @@ -.. _tutorial_plasticity: - -Structural Plasticity in Arbor -============================== - -In this tutorial, we are going to demonstrate how a network can be built using -plasticity and homeostatic connection rules. Despite not playing towards Arbor's -strengths, we choose a LIF (Leaky Integrate and Fire) neuron model, as we are -primarily interested in examining the required scaffolding. - -We will build up the simulation in stages, starting with an unconnected network -and finishing with a dynamically built connectome. - -.. admonition:: Concepts and Requirements - - We cover some advanced topics in this tutorial, mainly structural - plasticity. Please refer to other tutorials for the basics of network - building. The model employed here --- storing an explicit connection matrix - --- is not advisable in most scenarios. - - In addition to Arbor and its requirements, ``scipy``, ``matplotlib``, and - ``networkx`` need to be installed. - -Unconnected Network -------------------- - -Consider a collection of ``N`` LIF cells. This will be the starting point for -our exploration. For now, we set up each cell with a Poissonian input such that -it will produce spikes periodically at a low frequency. - -The Python file ``01-setup.py`` is the scaffolding we will build our simulation -around and thus contains some passages that might seem redundant now, but will -be helpful in later steps. - -We begin by defining the global settings - -.. literalinclude:: ../../python/example/plasticity/unconnected.py - :language: python - :lines: 7-15 - -- ``N`` is the cell count of the simulation -- ``T`` is the total runtime of the simulation in ``ms`` -- ``t_interval`` defines the _interval_ such that the simulation is advance in - discrete steps ``[0, 1, 2, ...] t_interval``. Later, this will be the timescale of - plasticity. -- ``dt`` is the numerical timestep on which cells evolve - -These parameters are used here - -.. literalinclude:: ../../python/example/plasticity/unconnected.py - :language: python - :lines: 52-62 - -where we run the simulation in increments of ``t_interval``. - -Back to the recipe; we set a prototypical cell - -.. literalinclude:: ../../python/example/plasticity/unconnected.py - :language: python - :lines: 23 - -and deliver it for all ``gid`` s - -.. literalinclude:: ../../python/example/plasticity/unconnected.py - :language: python - :lines: 42-43 - -Also, each cell has an event generator attached - -.. literalinclude:: ../../python/example/plasticity/unconnected.py - :language: python - :lines: 33-40 - -using a Poisson point process seeded with the cell's ``gid``. All other -parameters are set in the constructor - -.. literalinclude:: ../../python/example/plasticity/unconnected.py - :language: python - :lines: 19-28 - -We also proceed to add spike recording and generate plots using a helper -function ``plot_spikes`` from ``util.py``. You can skip the following details -for now and come back later if you are interested how it works. Rates are -computed by binning spikes into ``t_interval`` and the neuron id; the mean rate -is the average across the neurons smoothed using a Savitzky-Golay filter -(``scipy.signal.savgol_filter``). - -We plot per-neuron and mean rates: - -.. figure:: ../../python/example/plasticity/01-rates.svg - :width: 400 - :align: center - -We also generate raster plots via ``scatter``. - -.. figure:: ../../python/example/plasticity/01-raster.svg - :width: 400 - :align: center - - -A Randomly Wired Network ------------------------- - -We use inheritance to derive a new recipe that contains all the functionality of -the ```unconnected`` recipe. We then add a random connectivity matrix during -construction, fixed connection weights, and deliver the resulting connections -via the ``connections_on`` callback, with the only extra consideration of -allowing multiple connections between two neurons. - -In detail, the recipe stores the connection matrix, the current -incoming/outgoing connections per neuron, and the maximum for both directions - -.. literalinclude:: ../../python/example/plasticity/random_network.py - :language: python - :lines: 26-31 - -The connection matrix is used to construct connections - -.. literalinclude:: ../../python/example/plasticity/random_network.py - :language: python - :lines: 33-38 - -together with the fixed connection parameters - -.. literalinclude:: ../../python/example/plasticity/random_network.py - :language: python - :lines: 24-25 - -We define helper functions ``add|del_connections`` to manipulate the connection -table while upholding these invariants: - -- no self-connections, i.e. ``connection[i, i] == 0`` -- ``inc[i]`` the sum of ``connections[:, i]`` -- no more incoming connections than allowed by ``max_inc``, i.e. ``inc[i] <= max_inc`` -- ``out[i]`` the sum of ``connections[i, :]`` -- no more outgoing connections than allowed by ``max_out``, i.e. ``out[i] <= max_out`` - -These methods return ``True`` on success and ``False`` otherwise - -.. literalinclude:: ../../python/example/plasticity/random_network.py - :language: python - :lines: 40-54 - -Both are used in ``rewire`` to produce a random connection matrix - -.. literalinclude:: ../../python/example/plasticity/random_network.py - :language: python - :lines: 56-65 - -We then proceed to run the simulation - -.. literalinclude:: ../../python/example/plasticity/random_network.py - :language: python - :lines: 68-79 - - and plot the results as before - -.. figure:: ../../python/example/plasticity/02-rates.svg - :width: 400 - :align: center - - -Note that we added a plot of the network connectivity using ``plot_network`` -from ``util`` as well. This generates images of the graph and connection matrix. - -.. figure:: ../../python/example/plasticity/02-matrix.svg - :width: 400 - :align: center - -.. figure:: ../../python/example/plasticity/02-graph.svg - :width: 400 - :align: center - - - -Adding Homeostasis ------------------- - -Under the homeostatic model, each cell was a setpoint for the firing rate :math:`\nu^*` -which is used to determine the creation or destruction of synaptic connections via - -.. math:: - - \frac{dC}{dt} = \alpha(\nu - \nu^*) - -Thus we need to add some extra information to our simulation; namely the -setpoint :math:`\nu^*_i` for each neuron :math:`i` and the sensitivity parameter -:math:`\alpha`. We will also use a simplified version of the differential -equation above, namely adding/deleting exactly one connection if the difference -of observed to desired spiking frequency exceeds :math:`\pm\alpha`. This is both -for simplicity and to avoid sudden changes in the network structure. - -As before, we set up global parameters - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 10-24 - -and prepare our simulation - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 37-39 - -Note that our new recipe is almost unaltered from the random network - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 27-33 - -all changes are contained to the way we run the simulation. To add a further -interesting feature, we skip the rewiring for the first half of the simulation. -The initial network is unconnected, but could be populated randomly (or any -other way) if desired by calling ``self.rewire()`` in the constructor of -``homeostatic_network`` before setting the maxima to eight. - -Plasticity is implemented by tweaking the connection table inside the recipe -between calls to ``run`` and calling ``simulation.update`` with the modified -recipe: - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 70 - -.. note:: - - As it is the central point here, it is worth emphasizing why this yields a - changed network. The call to ``sim.update(rec)`` causes Arbor to internally - re-build the connection table from scratch based on the data returned by - ``rec.connections_on``. However, here, this method just inspects the matrix - in ``rec.connections`` and converts the data into a ``arbor.connection``. - Thus, changing this matrix before ``update`` will build a different network. - - Important caveats: - - - without ``update``, changes to the recipe have no effect - - vice versa ``update`` has no effect if the recipe doesn't return different - data than before - - ``update`` will delete all existing connections and their parameters, so - all connections to be kept must be explicitly re-instantiated - - ``update`` will **not** delete synapses or their state, e.g. ODEs will - still be integrated even if not connected and currents might be produced - - neither synapses/targets nor detectors/sources can be altered. Create all - endpoints up front. - - only the network is updated (this might change in future versions!) - - be very aware that ``connections_on`` might be called in arbitrary order - and by multiples (potentially different) threads and processes! This - requires some thought and synchronization when dealing with random numbers - and updating data *inside* ``connections_on``. - -Changes are based on the difference of current rate we compute from the spikes -during the last interval - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 49-54 - -and the setpoint times the sensitivity - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 55 - -Then, each potential pairing of target and source is checked in random -order for whether adding or removing a connection is required - -.. literalinclude:: ../../python/example/plasticity/homeostasis.py - :language: python - :lines: 59-68 - -If we find an option to fulfill that requirement, we do so and proceed to the -next target. The randomization is important here, espcially for adding -connections as to avoid biases, in particular when there are too few eglible -connection partners. The ``randrange`` function produces a shuffled range ``[0, -N)``. We leverage the helper functions from the random network recipe to -manipulate the connection table, see the discussion above. - -Finally, we plot spiking rates as before; the jump at the half-way point is the -effect of the plasticity activating after which each neuron moves to the -setpoint - -.. figure:: ../../python/example/plasticity/03-rates.svg - :width: 400 - :align: center - -and the resulting network - -.. figure:: ../../python/example/plasticity/03-final-graph.svg - :width: 400 - :align: center - -Conclusion ----------- - -This concludes our foray into structural plasticity. While the building blocks - -- an explicit representation of the connections, -- running the simulation in batches (and calling ``simulation.update``!) -- a rule to derive the change - -will likely be the same in all approaches, the concrete implementation of the -rules is the centerpiece here. For example, although spike rate homeostasis was -used here, mechanism states and ion concentrations --- extracted via the normal -probe and sample interface --- can be leveraged to build rules. Due to the way -the Python interface is required to link to measurements, using the C++ API for -access to streaming spike and measurement data could help to address performance -issues. Plasticity as shown also meshes with the high-level connection builder. -External tools to build and update connections might be useful as well. diff --git a/doc/tutorial/single_cell_detailed.rst b/doc/tutorial/single_cell_detailed.rst index e7f6d6baa..5fe68d012 100644 --- a/doc/tutorial/single_cell_detailed.rst +++ b/doc/tutorial/single_cell_detailed.rst @@ -30,8 +30,8 @@ geometry and dynamics which is constructed from 3 components: 2. A **label dictionary** storing labelled expressions which define regions and locations of interest on the cell. 3. A **decor** defining various properties and dynamics on these regions and locations. -4. Finally, the cell needs to know how we will be splitting it into discrete - control volumes (CV). + The decor also includes hints about how the cell is to be modelled under the hood, by + splitting it into discrete control volumes (CV). The morphology ^^^^^^^^^^^^^^^ diff --git a/example/busyring/ring.cpp b/example/busyring/ring.cpp index b4903f84c..57a7e3384 100644 --- a/example/busyring/ring.cpp +++ b/example/busyring/ring.cpp @@ -421,7 +421,9 @@ arb::cable_cell complex_cell(arb::cell_gid_type gid, const cell_parameters& para if (params.synapses>1) decor.place(syns, arb::synapse("expsyn"), "s"); - return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; + decor.set_default(arb::cv_policy_every_segment()); + + return {arb::morphology(tree), decor}; } arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& params) { @@ -451,5 +453,7 @@ arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters& param } // Make a CV between every sample in the sample tree. - return {arb::morphology(tree), decor, {}, arb::cv_policy_every_segment()}; + decor.set_default(arb::cv_policy_every_segment()); + + return {arb::morphology(tree), decor}; } diff --git a/example/diffusion/diffusion.cpp b/example/diffusion/diffusion.cpp index 80cb3fe34..0d2952bac 100644 --- a/example/diffusion/diffusion.cpp +++ b/example/diffusion/diffusion.cpp @@ -26,7 +26,7 @@ namespace U = arb::units; struct linear: public recipe { linear(double ext, double dx, double Xi, double beta): l{ext}, d{dx}, i{Xi}, b{beta} { gprop.default_parameters = neuron_parameter_defaults; - gprop.default_parameters.discretization = cv_policy_max_extent(d); + gprop.default_parameters.discretization = cv_policy_max_extent{d}; gprop.add_ion("bla", 1, 23*U::mM, 42*U::mM, 0*U::mV, b*U::m2/U::s); } diff --git a/example/dryrun/branch_cell.hpp b/example/dryrun/branch_cell.hpp index d0ac1ff1b..665e4f53e 100644 --- a/example/dryrun/branch_cell.hpp +++ b/example/dryrun/branch_cell.hpp @@ -118,11 +118,12 @@ inline arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters .place(arb::mlocation{0,0}, arb::threshold_detector{10*U::mV}, "detector") // Add spike threshold detector at the soma. - .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse"); // Add a synapse to the mid point of the first dendrite. + .place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "synapse") // Add a synapse to the mid point of the first dendrite. + .set_default(arb::cv_policy_every_segment()); // Make a CV between every sample in the sample tree. // Add additional synapses that will not be connected to anything. for (unsigned i=1u; i>; - - std::pair probe_tbl[] { + std::pair>> probe_tbl[] { // located probes - {"v", {"v", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_membrane_voltage{L{0, x}}; }}}, - {"i_axial", {"i_axial", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_axial_current{L{0, x}}; }}}, - {"j_ion", {"j_ion", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_total_ion_current_density{L{0, x}}; }}}, - {"j_na", {"j_na", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_current_density{L{0, x}, "na"}; }}}, - {"j_k", {"j_k", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_current_density{L{0, x}, "k"}; }}}, - {"c_na", {"c_na", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_int_concentration{L{0, x}, "na"}; }}}, - {"c_k", {"c_k", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_ion_int_concentration{L{0, x}, "k"}; }}}, - {"hh_m", {"hh_m", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "m"}; }}}, - {"hh_h", {"hh_h", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "h"}; }}}, - {"hh_n", {"hh_n", true, [](std::any a) { auto x = std::any_cast(a); return arb::cable_probe_density_state{L{0, x}, "hh", "n"}; }}}, - {"expsyn_g", {"expsyn_ g", true, [](std::any a) { auto t = std::any_cast(a); return arb::cable_probe_point_state{t, "expsyn", "g"}; }}}, + {"v", {"v", true, [](double x) { return arb::cable_probe_membrane_voltage{L{0, x}}; }}}, + {"i_axial", {"i_axial", true, [](double x) { return arb::cable_probe_axial_current{L{0, x}}; }}}, + {"j_ion", {"j_ion", true, [](double x) { return arb::cable_probe_total_ion_current_density{L{0, x}}; }}}, + {"j_na", {"j_na", true, [](double x) { return arb::cable_probe_ion_current_density{L{0, x}, "na"}; }}}, + {"j_k", {"j_k", true, [](double x) { return arb::cable_probe_ion_current_density{L{0, x}, "k"}; }}}, + {"c_na", {"c_na", true, [](double x) { return arb::cable_probe_ion_int_concentration{L{0, x}, "na"}; }}}, + {"c_k", {"c_k", true, [](double x) { return arb::cable_probe_ion_int_concentration{L{0, x}, "k"}; }}}, + {"hh_m", {"hh_m", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "m"}; }}}, + {"hh_h", {"hh_h", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "h"}; }}}, + {"hh_n", {"hh_n", true, [](double x) { return arb::cable_probe_density_state{L{0, x}, "hh", "n"}; }}}, + {"expsyn_g", {"expsyn_ g", true, [](arb::cell_lid_type i) { return arb::cable_probe_point_state{i, "expsyn", "g"}; }}}, // all-of-cell probes - {"all_v", {"v", false, [](std::any) { return arb::cable_probe_membrane_voltage_cell{}; }}}, - {"all_i_ion", {"i_ion", false, [](std::any) { return arb::cable_probe_total_ion_current_cell{}; }}}, - {"all_i_na", {"i_na", false, [](std::any) { return arb::cable_probe_ion_current_cell{"na"}; }}}, - {"all_i_k", {"i_k", false, [](std::any) { return arb::cable_probe_ion_current_cell{"k"}; }}}, - {"all_i", {"i", false, [](std::any) { return arb::cable_probe_total_current_cell{}; }}}, - {"all_c_na", {"c_na", false, [](std::any) { return arb::cable_probe_ion_int_concentration_cell{"na"}; }}}, - {"all_c_k", {"c_k", false, [](std::any) { return arb::cable_probe_ion_int_concentration_cell{"k"}; }}}, - {"all_hh_m", {"hh_m", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "m"}; }}}, - {"all_hh_h", {"hh_h", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "h"}; }}}, - {"all_hh_n", {"hh_n", false, [](std::any) { return arb::cable_probe_density_state_cell{"hh", "n"}; }}}, - {"all_expsyn_g", {"expsyn_ g", false, [](std::any) { return arb::cable_probe_point_state_cell{"expsyn", "g"}; }}}, + {"all_v", {"v", false, [](double) { return arb::cable_probe_membrane_voltage_cell{}; }}}, + {"all_i_ion", {"i_ion", false, [](double) { return arb::cable_probe_total_ion_current_cell{}; }}}, + {"all_i_na", {"i_na", false, [](double) { return arb::cable_probe_ion_current_cell{"na"}; }}}, + {"all_i_k", {"i_k", false, [](double) { return arb::cable_probe_ion_current_cell{"k"}; }}}, + {"all_i", {"i", false, [](double) { return arb::cable_probe_total_current_cell{}; }}}, + {"all_c_na", {"c_na", false, [](double) { return arb::cable_probe_ion_int_concentration_cell{"na"}; }}}, + {"all_c_k", {"c_k", false, [](double) { return arb::cable_probe_ion_int_concentration_cell{"k"}; }}}, + {"all_hh_m", {"hh_m", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "m"}; }}}, + {"all_hh_h", {"hh_h", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "h"}; }}}, + {"all_hh_n", {"hh_n", false, [](double) { return arb::cable_probe_density_state_cell{"hh", "n"}; }}}, + {"all_expsyn_g", {"expsyn_ g", false, [](arb::cell_lid_type) { return arb::cable_probe_point_state_cell{"expsyn", "g"}; }}}, }; - probe_spec_t probe_spec; - std::any p_pos; - - auto double_or_string = [](const char* arg) -> to::maybe { - try { - return {{std::stod(arg)}}; - } - catch (const std::exception& e) { - return {{std::string(arg)}}; - } - }; + std::tuple> probe_spec; + double probe_pos = 0.5; to::option cli_opts[] = { { to::action(do_help), to::flag, to::exit, "-h", "--help" }, - { opt.sim_dt, "--dt" }, - { opt.sim_end, "--until" }, - { opt.sample_dt, "-t", "--sample" }, - { to::sink(p_pos, double_or_string), "-x", "--at" }, - { opt.n_cv, "-n", "--n-cv" }, { {probe_spec, to::keywords(probe_tbl)}, to::single }, + { opt.sim_dt, "--dt" }, + { opt.sim_end, "--until" }, + { opt.sample_dt, "-t", "--sample" }, + { probe_pos, "-x", "--at" }, + { opt.n_cv, "-n", "--n-cv" } }; - const auto& [p_name, p_scalar, p_addr] = probe_spec; - if (!p_pos.has_value() && (p_name == "exp_syn_g")) { - p_pos = "synapse0"; - } - else { - p_pos = 0.5; - } - if (!to::run(cli_opts, argc, argv+1)) return false; - if (!p_addr) throw to::user_option_error("missing PROBE"); + if (!get<2>(probe_spec)) throw to::user_option_error("missing PROBE"); if (argv[1]) throw to::user_option_error("unrecognized option"); - opt.value_name = p_name; - opt.scalar_probe = p_scalar; - opt.probe_addr = p_addr(p_pos); + + opt.value_name = get<0>(probe_spec); + opt.scalar_probe = get<1>(probe_spec); + opt.probe_addr = get<2>(probe_spec)(probe_pos); return true; } diff --git a/example/ring/branch_cell.hpp b/example/ring/branch_cell.hpp index d1e59942c..92aa9f611 100644 --- a/example/ring/branch_cell.hpp +++ b/example/ring/branch_cell.hpp @@ -124,5 +124,9 @@ inline arb::cable_cell branch_cell(arb::cell_gid_type gid, const cell_parameters } // Make a CV between every sample in the sample tree. - return arb::cable_cell(arb::morphology(tree), decor, labels, arb::cv_policy_every_segment()); + decor.set_default(arb::cv_policy_every_segment()); + + arb::cable_cell cell(arb::morphology(tree), decor, labels); + + return cell; } diff --git a/modcc/blocks.hpp b/modcc/blocks.hpp index 448e2f2d4..1e12b5428 100644 --- a/modcc/blocks.hpp +++ b/modcc/blocks.hpp @@ -4,8 +4,10 @@ #include #include #include +#include #include "identifier.hpp" +#include "location.hpp" #include "token.hpp" #include @@ -46,15 +48,6 @@ struct IonDep { bool writes_concentration_ext() const { return writes_variable(name + "o"); }; - bool reads_current() const { - return reads_variable("i" + name); - }; - bool reads_concentration_int() const { - return reads_variable(name + "i"); - }; - bool reads_concentration_ext() const { - return reads_variable(name + "o"); - }; bool writes_rev_potential() const { return writes_variable("e" + name); }; diff --git a/modcc/identifier.hpp b/modcc/identifier.hpp index 08a3f40fd..a2ce9fbbc 100644 --- a/modcc/identifier.hpp +++ b/modcc/identifier.hpp @@ -62,6 +62,10 @@ enum class sourceKind { no_source }; +inline std::string yesno(bool val) { + return std::string(val ? "yes" : "no"); +}; + //////////////////////////////////////////// // to_string functions convert types // to strings for printing diagnostics diff --git a/modcc/printer/infoprinter.cpp b/modcc/printer/infoprinter.cpp index f201fd5fb..f59f52f54 100644 --- a/modcc/printer/infoprinter.cpp +++ b/modcc/printer/infoprinter.cpp @@ -49,13 +49,12 @@ ARB_LIBMODCC_API std::string build_info_header(const Module& m, const printer_op id.unit_string(), val, lo, hi); }; auto fmt_ion = [&](const auto& ion) { - return fmt::format(FMT_COMPILE("{{ \"{}\", {}, {}, {}, {}, {}, {}, {}, {}, {}, {} }}"), - ion.name, - ion.writes_concentration_int(), ion.writes_concentration_ext(), - ion.reads_concentration_int(), ion.reads_concentration_ext(), - ion.uses_concentration_diff(), - ion.writes_rev_potential(), ion.uses_rev_potential(), - ion.uses_valence(), ion.verifies_valence(), ion.expected_valence); + return fmt::format(FMT_COMPILE("{{ \"{}\", {}, {}, {}, {}, {}, {}, {}, {} }}"), + ion.name, + ion.writes_concentration_int(), ion.writes_concentration_ext(), + ion.uses_concentration_diff(), + ion.writes_rev_potential(), ion.uses_rev_potential(), + ion.uses_valence(), ion.verifies_valence(), ion.expected_valence); }; auto fmt_random_variable = [&](const auto& rv_id) { return fmt::format(FMT_COMPILE("{{ \"{}\", {} }}"), rv_id.first.name(), rv_id.second); diff --git a/modcc/printer/printerutil.hpp b/modcc/printer/printerutil.hpp index 8033fc10a..7fc8cfa34 100644 --- a/modcc/printer/printerutil.hpp +++ b/modcc/printer/printerutil.hpp @@ -23,6 +23,14 @@ inline const char* arb_header_prefix() { return prefix; } +// TODO: this function will be obsoleted once arbor private/public headers are +// properly split. + +inline const char* arb_private_header_prefix() { + static const char* prefix = ""; + return prefix; +} + struct namespace_declaration_open { const std::vector& ids; namespace_declaration_open(const std::vector& ids): ids(ids) {} @@ -155,3 +163,16 @@ struct ARB_LIBMODCC_API indexed_variable_info { }; ARB_LIBMODCC_API indexed_variable_info decode_indexed_variable(IndexedVariable* sym); + +template +size_t emit_array(std::ostream& out, const C& vars) { + auto n = 0ul; + io::separator sep("", ", "); + out << "{ "; + for (const auto& var: vars) { + out << sep << var; + ++n; + } + out << " }"; + return n; +} diff --git a/python/CMakeLists.txt b/python/CMakeLists.txt index bbb356d57..d0262b297 100644 --- a/python/CMakeLists.txt +++ b/python/CMakeLists.txt @@ -102,25 +102,31 @@ set(ARB_PYTHON_LIB_PATH ${ARB_PYTHON_LIB_PATH_DEFAULT} CACHE PATH "path for inst message(VERBOSE "Python module installation path: ${ARB_PYTHON_LIB_PATH}") mark_as_advanced(FORCE ARB_PYTHON_LIB_PATH) -if(DEFINED SKBUILD_PROJECT_NAME) - # Building wheel through scikit-build-core - set(_python_module_install_path .) -else() - set(_python_module_install_path ${ARB_PYTHON_LIB_PATH}/arbor) -endif() - # generate type stubs and copy them to the expected places if(ARB_BUILD_PYTHON_STUBS) - find_program(PB11_STUBGEN NAMES pybind11-stubgen REQUIRED) + find_python_module(pybind11_stubgen REQUIRED) +else() + find_python_module(pybind11_stubgen) +endif() +if(HAVE_PYBIND11_STUBGEN) add_custom_command(TARGET pyarb POST_BUILD COMMAND - PYTHONPATH=${CMAKE_BINARY_DIR}/python:$ENV{PYTHONPATH} ${PB11_STUBGEN} -o ${CMAKE_BINARY_DIR}/stubs arbor + PYTHONPATH=${CMAKE_BINARY_DIR}/python:$ENV{PYTHONPATH} pybind11-stubgen -o ${CMAKE_BINARY_DIR}/stubs arbor BYPRODUCTS ${CMAKE_BINARY_DIR}/stubs USES_TERMINAL COMMENT "Generating type stubs") - install(DIRECTORY ${CMAKE_BINARY_DIR}/stubs/arbor/ DESTINATION ${_python_module_install_path}) +endif() + +if(DEFINED SKBUILD_PROJECT_NAME) + # Building wheel through scikit-build-core + set(_python_module_install_path .) +else() + set(_python_module_install_path ${ARB_PYTHON_LIB_PATH}/arbor) endif() install(TARGETS pyarb DESTINATION ${_python_module_install_path}) +if(HAVE_PYBIND11_STUBGEN) + install(DIRECTORY ${CMAKE_BINARY_DIR}/stubs/arbor/ DESTINATION ${_python_module_install_path}) +endif() install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/__init__.py DESTINATION ${_python_module_install_path}) install(FILES ${PROJECT_SOURCE_DIR}/VERSION ${PROJECT_SOURCE_DIR}/README.md ${PROJECT_SOURCE_DIR}/LICENSE DESTINATION ${_python_module_install_path}) diff --git a/python/cells.cpp b/python/cells.cpp index bd1eee744..730073431 100644 --- a/python/cells.cpp +++ b/python/cells.cpp @@ -358,30 +358,30 @@ void register_cells(py::module& m) { // arb::cell_cv_data cell_cv_data - .def_property_readonly("num_cv", [](const arb::cell_cv_data& data){return data.size();}, - "Return the number of CVs in the cell.") - .def("cables", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.cables(index); - }, - "index"_a, "Return a list of cables representing the CV at the given index.") - .def("children", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.children(index); - }, - "index"_a, - "Return a list of indices of the CVs representing the children of the CV at the given index.") - .def("parent", - [](const arb::cell_cv_data& d, unsigned index) { - if (index >= d.size()) throw py::index_error("index out of range"); - return d.parent(index); - }, - "index"_a, - "Return the index of the CV representing the parent of the CV at the given index.") - .def("__str__", [](const arb::cell_cv_data& p){return "";}) - .def("__repr__", [](const arb::cell_cv_data& p){return "";}); + .def_property_readonly("num_cv", [](const arb::cell_cv_data& data){return data.size();}, + "Return the number of CVs in the cell.") + .def("cables", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.cables(index); + }, + "index"_a, "Return a list of cables representing the CV at the given index.") + .def("children", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.children(index); + }, + "index"_a, + "Return a list of indices of the CVs representing the children of the CV at the given index.") + .def("parent", + [](const arb::cell_cv_data& d, unsigned index) { + if (index >= d.size()) throw py::index_error("index out of range"); + return d.parent(index); + }, + "index"_a, + "Return the index of the CV representing the parent of the CV at the given index.") + .def("__str__", [](const arb::cell_cv_data& p){return "";}) + .def("__repr__", [](const arb::cell_cv_data& p){return "";}); m.def("cv_data", [](const arb::cable_cell& cell) { return arb::cv_data(cell);}, "cell"_a, "the cable cell", @@ -913,22 +913,34 @@ void register_cells(py::module& m) { }, "locations"_a, "detector"_a, "label"_a, "Add a voltage spike detector at each location in locations." - "The group of spike detectors has the label 'label', used for forming connections between cells."); + "The group of spike detectors has the label 'label', used for forming connections between cells.") + .def("discretization", + [](arb::decor& dec, const arb::cv_policy& p) { return dec.set_default(p); }, + py::arg("policy"), + "A cv_policy used to discretise the cell into compartments for simulation") + .def("discretization", + [](arb::decor& dec, const std::string& p) { + return dec.set_default(arborio::parse_cv_policy_expression(p).unwrap()); + }, + py::arg("policy"), + "An s-expression string representing a cv_policy used to discretise the " + "cell into compartments for simulation"); + cable_cell .def(py::init( - [](const arb::morphology& m, const arb::decor& d, const std::optional& l, const std::optional& p) { - if (l) return arb::cable_cell(m, d, l->dict, p); - return arb::cable_cell(m, d, {}, p); + [](const arb::morphology& m, const arb::decor& d, const std::optional& l) { + if (l) return arb::cable_cell(m, d, l->dict); + return arb::cable_cell(m, d); }), - "morphology"_a, "decor"_a, "labels"_a=py::none(), "discretization"_a=py::none(), - "Construct with a morphology, decor, label dictionary, and cv policy.") + "morphology"_a, "decor"_a, "labels"_a=py::none(), + "Construct with a morphology, decor, and label dictionary.") .def(py::init( - [](const arb::segment_tree& t, const arb::decor& d, const std::optional& l, const std::optional& p) { - if (l) return arb::cable_cell({t}, d, l->dict, p); - return arb::cable_cell({t}, d, {}, p); + [](const arb::segment_tree& t, const arb::decor& d, const std::optional& l) { + if (l) return arb::cable_cell({t}, d, l->dict); + return arb::cable_cell({t}, d); }), - "segment_tree"_a, "decor"_a, "labels"_a=py::none(), "discretization"_a=py::none(), - "Construct with a morphology derived from a segment tree, decor, label dictionary, and cv policy.") + "segment_tree"_a, "decor"_a, "labels"_a=py::none(), + "Construct with a morphology derived from a segment tree, decor, and label dictionary.") .def_property_readonly("num_branches", [](const arb::cable_cell& c) {return c.morphology().num_branches();}, "The number of unbranched cable sections in the morphology.") @@ -940,21 +952,6 @@ void register_cells(py::module& m) { .def("cables", [](arb::cable_cell& c, const char* label) {return c.concrete_region(arborio::parse_region_expression(label).unwrap()).cables();}, "label"_a, "The cable segments of the cell morphology for a region label.") - // Discretization - .def("discretization", - [](const arb::cable_cell& c) { return c.discretization(); }, - "The cv_policy used to discretise the cell into compartments for simulation") - .def("discretization", - [](arb::cable_cell& c, const arb::cv_policy& p) { return c.discretization(p); }, - py::arg("policy"), - "A cv_policy used to discretise the cell into compartments for simulation") - .def("discretization", - [](arb::cable_cell& c, const std::string& p) { - return c.discretization(arborio::parse_cv_policy_expression(p).unwrap()); - }, - py::arg("policy"), - "An s-expression string representing a cv_policy used to discretise the " - "cell into compartments for simulation") // Stringification .def("__repr__", [](const arb::cable_cell&){return "";}) .def("__str__", [](const arb::cable_cell&){return "";}); diff --git a/python/example/diffusion.py b/python/example/diffusion.py index b4281815a..8dda5e78f 100644 --- a/python/example/diffusion.py +++ b/python/example/diffusion.py @@ -42,6 +42,7 @@ def event_generators(self, _): .set_ion("na", int_con=0.0 * U.mM, diff=0.005 * U.m2 / U.s) .place("(location 0 0.5)", A.synapse("inject/x=na", {"alpha": 200.0}), "Zap") .paint("(all)", A.density("decay/x=na")) + .discretization(A.cv_policy("(max-extent 5)")) # Set up ion diffusion .set_ion( "na", @@ -56,7 +57,7 @@ def event_generators(self, _): prb = [ A.cable_probe_ion_diff_concentration_cell("na", "nad"), ] -cel = A.cable_cell(tree, dec, discretization=A.cv_policy("(max-extent 5)")) +cel = A.cable_cell(tree, dec) rec = recipe(cel, prb) sim = A.simulation(rec) hdl = (sim.sample((0, "nad"), A.regular_schedule(0.1 * U.ms)),) diff --git a/python/example/network_two_cells_gap_junctions.py b/python/example/network_two_cells_gap_junctions.py index 3a99f078c..c12982307 100755 --- a/python/example/network_two_cells_gap_junctions.py +++ b/python/example/network_two_cells_gap_junctions.py @@ -60,11 +60,11 @@ def cell_description(self, gid): ) if self.max_extent is not None: - cvp = A.cv_policy_max_extent(self.max_extent) + decor.discretization(A.cv_policy_max_extent(self.max_extent)) else: - cvp = A.cv_policy_single() + decor.discretization(A.cv_policy_single()) - return A.cable_cell(tree, decor, labels, cvp) + return A.cable_cell(tree, decor, labels) def gap_junctions_on(self, gid): return [A.gap_junction_connection(((gid + 1) % 2, "gj"), "gj", 1)] diff --git a/python/example/plasticity/01-raster.svg b/python/example/plasticity/01-raster.svg deleted file mode 100644 index 11b1a2fbd..000000000 --- a/python/example/plasticity/01-raster.svg +++ /dev/null @@ -1,1863 +0,0 @@ - - - - - - - - 2024-10-08T11:34:50.435338 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/01-rates.svg b/python/example/plasticity/01-rates.svg deleted file mode 100644 index 087291dbb..000000000 --- a/python/example/plasticity/01-rates.svg +++ /dev/null @@ -1,2107 +0,0 @@ - - - - - - - - 2024-10-08T11:34:50.626245 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/02-graph.svg b/python/example/plasticity/02-graph.svg deleted file mode 100644 index e40f6759e..000000000 --- a/python/example/plasticity/02-graph.svg +++ /dev/null @@ -1,808 +0,0 @@ - - - - - - - - 2024-10-08T11:34:44.762762 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/02-matrix.svg b/python/example/plasticity/02-matrix.svg deleted file mode 100644 index ce78d1a5e..000000000 --- a/python/example/plasticity/02-matrix.svg +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - - 2024-10-08T11:34:44.531402 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/02-raster.svg b/python/example/plasticity/02-raster.svg deleted file mode 100644 index eba26e802..000000000 --- a/python/example/plasticity/02-raster.svg +++ /dev/null @@ -1,4005 +0,0 @@ - - - - - - - - 2024-10-08T11:34:44.998033 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/02-rates.svg b/python/example/plasticity/02-rates.svg deleted file mode 100644 index c2b4f9acf..000000000 --- a/python/example/plasticity/02-rates.svg +++ /dev/null @@ -1,2090 +0,0 @@ - - - - - - - - 2024-10-08T11:34:45.180583 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/03-final-graph.svg b/python/example/plasticity/03-final-graph.svg deleted file mode 100644 index 6b31fdae9..000000000 --- a/python/example/plasticity/03-final-graph.svg +++ /dev/null @@ -1,1208 +0,0 @@ - - - - - - - - 2024-10-08T11:34:38.359950 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/03-final-matrix.svg b/python/example/plasticity/03-final-matrix.svg deleted file mode 100644 index 746c6e5d7..000000000 --- a/python/example/plasticity/03-final-matrix.svg +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - - 2024-10-08T11:34:38.085467 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/03-initial-graph.svg b/python/example/plasticity/03-initial-graph.svg deleted file mode 100644 index f01b66872..000000000 --- a/python/example/plasticity/03-initial-graph.svg +++ /dev/null @@ -1,408 +0,0 @@ - - - - - - - - 2024-10-08T11:34:37.211454 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/03-initial-matrix.svg b/python/example/plasticity/03-initial-matrix.svg deleted file mode 100644 index 42c20f7b9..000000000 --- a/python/example/plasticity/03-initial-matrix.svg +++ /dev/null @@ -1,388 +0,0 @@ - - - - - - - - 2024-10-08T11:34:37.047482 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/03-raster.svg b/python/example/plasticity/03-raster.svg deleted file mode 100644 index e091e70cc..000000000 --- a/python/example/plasticity/03-raster.svg +++ /dev/null @@ -1,19409 +0,0 @@ - - - - - - - - 2024-10-08T11:34:38.815593 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/03-rates.svg b/python/example/plasticity/03-rates.svg deleted file mode 100644 index dbb954ad0..000000000 --- a/python/example/plasticity/03-rates.svg +++ /dev/null @@ -1,2013 +0,0 @@ - - - - - - - - 2024-10-08T11:34:39.088775 - image/svg+xml - - - Matplotlib v3.8.3, https://matplotlib.org/ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/python/example/plasticity/homeostasis.py b/python/example/plasticity/homeostasis.py deleted file mode 100644 index 4edf354ee..000000000 --- a/python/example/plasticity/homeostasis.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/usr/bin/env python3 - -import arbor as A -from arbor import units as U -import numpy as np - -from util import plot_spikes, plot_network, randrange -from random_network import random_network - -# global parameters -# cell count -N = 10 -# total runtime [ms] -T = 10000 -# one interval [ms] -t_interval = 100 -# numerical time step [ms] -dt = 0.1 -# Set seed for numpy -np.random.seed = 23 -# setpoint rate in kHz -setpoint_rate = 0.1 -# sensitivty towards deviations from setpoint -sensitivity = 200 - - -class homeostatic_network(random_network): - def __init__(self, N, setpoint_rate, sensitivity) -> None: - super().__init__(N) - self.max_inc = 8 - self.max_out = 8 - self.setpoint = setpoint_rate - self.alpha = sensitivity - - -if __name__ == "__main__": - rec = homeostatic_network(N, setpoint_rate, sensitivity) - sim = A.simulation(rec) - sim.record(A.spike_recording.all) - - plot_network(rec, prefix="03-initial-") - - t = 0 - while t < T: - sim.run((t + t_interval) * U.ms, dt * U.ms) - if t < T / 2: - t += t_interval - continue - rates = np.zeros(N) - for (gid, _), time in sim.spikes(): - if time < t: - continue - rates[gid] += 1 - rates /= t_interval # kHz - dC = ((rec.setpoint - rates) * rec.alpha).astype(int) - unchangeable = set() - added = [] - deled = [] - for tgt in randrange(N): - if dC[tgt] == 0: - continue - for src in randrange(N): - if dC[tgt] > 0 and rec.add_connection(src, tgt): - added.append((src, tgt)) - break - elif dC[tgt] < 0 and rec.del_connection(src, tgt): - deled.append((src, tgt)) - break - unchangeable.add(tgt) - sim.update(rec) - print(f" * t={t:>4} f={rates} [!] {list(unchangeable)} [+] {added} [-] {deled}") - t += t_interval - - plot_network(rec, prefix="03-final-") - plot_spikes(sim, N, t_interval, T, prefix="03-") diff --git a/python/example/plasticity/random_network.py b/python/example/plasticity/random_network.py deleted file mode 100644 index cc58c90af..000000000 --- a/python/example/plasticity/random_network.py +++ /dev/null @@ -1,79 +0,0 @@ -#!/usr/bin/env python3 - -import arbor as A -from arbor import units as U -import numpy as np - -from util import plot_spikes, plot_network -from unconnected import unconnected - -# global parameters -# cell count -N = 10 -# total runtime [ms] -T = 1000 -# one interval [ms] -t_interval = 10 -# numerical time step [ms] -dt = 0.1 - - -class random_network(unconnected): - def __init__(self, N) -> None: - super().__init__(N) - self.syn_weight = 80 - self.syn_delay = 0.5 * U.ms - # format [to, from] - self.connections = np.zeros(shape=(N, N), dtype=np.uint8) - self.inc = np.zeros(N, np.uint8) - self.out = np.zeros(N, np.uint8) - self.max_inc = 4 - self.max_out = 4 - - def connections_on(self, gid: int): - return [ - A.connection((source, "src"), "tgt", self.syn_weight, self.syn_delay) - for source in range(self.N) - for _ in range(self.connections[gid, source]) - ] - - def add_connection(self, src: int, tgt: int) -> bool: - if tgt == src or self.inc[tgt] >= self.max_inc or self.out[src] >= self.max_out: - return False - self.inc[tgt] += 1 - self.out[src] += 1 - self.connections[tgt, src] += 1 - return True - - def del_connection(self, src: int, tgt: int) -> bool: - if tgt == src or self.connections[tgt, src] <= 0: - return False - self.inc[tgt] -= 1 - self.out[src] -= 1 - self.connections[tgt, src] -= 1 - return True - - def rewire(self): - tries = self.N * self.N * self.max_inc * self.max_out - while ( - tries > 0 - and self.inc.sum() < self.N * self.max_inc - and self.out.sum() < self.N * self.max_out - ): - src, tgt = np.random.randint(self.N, size=2, dtype=int) - self.add_connection(src, tgt) - tries -= 1 - - -if __name__ == "__main__": - rec = random_network(10) - rec.rewire() - sim = A.simulation(rec) - sim.record(A.spike_recording.all) - t = 0 - while t < T: - t += t_interval - sim.run(t * U.ms, dt * U.ms) - - plot_network(rec, prefix="02-") - plot_spikes(sim, N, t_interval, T, prefix="02-") diff --git a/python/example/plasticity/unconnected.py b/python/example/plasticity/unconnected.py deleted file mode 100644 index e9a07ec99..000000000 --- a/python/example/plasticity/unconnected.py +++ /dev/null @@ -1,62 +0,0 @@ -#!/usr/bin/env python3 - -import arbor as A -from arbor import units as U -from util import plot_spikes - -# global parameters -# cell count -N = 10 -# total runtime [ms] -T = 1000 -# one interval [ms] -t_interval = 10 -# numerical time step [ms] -dt = 0.1 - - -class unconnected(A.recipe): - def __init__(self, N) -> None: - super().__init__() - self.N = N - # Cell prototype - self.cell = A.lif_cell("src", "tgt") - # random seed [0, 100] - self.seed = 42 - # event generator parameters - self.gen_weight = 20 - self.gen_freq = 1 * U.kHz - - def num_cells(self) -> int: - return self.N - - def event_generators(self, gid: int): - return [ - A.event_generator( - "tgt", - self.gen_weight, - A.poisson_schedule(freq=self.gen_freq, seed=self.cell_seed(gid)), - ) - ] - - def cell_description(self, gid: int): - return self.cell - - def cell_kind(self, gid: int) -> A.cell_kind: - return A.cell_kind.lif - - def cell_seed(self, gid: int): - return self.seed + gid * 100 - - -if __name__ == "__main__": - rec = unconnected(N) - sim = A.simulation(rec) - sim.record(A.spike_recording.all) - - t = 0 - while t < T: - t += t_interval - sim.run(t * U.ms, dt * U.ms) - - plot_spikes(sim, rec.num_cells(), t_interval, T, prefix="01-") diff --git a/python/example/plasticity/util.py b/python/example/plasticity/util.py deleted file mode 100644 index 554259ad3..000000000 --- a/python/example/plasticity/util.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python3 - -import matplotlib.pyplot as plt -import numpy as np -from scipy.signal import savgol_filter -import networkx as nx - - -def plot_network(rec, prefix=""): - fg, ax = plt.subplots() - ax.matshow(rec.connections) - fg.savefig(f"{prefix}matrix.pdf") - fg.savefig(f"{prefix}matrix.png") - fg.savefig(f"{prefix}matrix.svg") - - n = rec.num_cells() - fg, ax = plt.subplots() - g = nx.MultiDiGraph() - g.add_nodes_from(np.arange(n)) - for i in range(n): - for j in range(n): - for _ in range(rec.connections[i, j]): - g.add_edge(i, j) - nx.draw(g, with_labels=True, font_weight="bold") - fg.savefig(f"{prefix}graph.pdf") - fg.savefig(f"{prefix}graph.png") - fg.savefig(f"{prefix}graph.svg") - - -def plot_spikes(sim, n_cells, t_interval, T, prefix=""): - # number of intervals - n_interval = int((T + t_interval - 1) // t_interval) - print(n_interval, T, t_interval) - - # Extract spikes - times = [] - gids = [] - rates = np.zeros(shape=(n_interval, n_cells)) - for (gid, _), time in sim.spikes(): - times.append(time) - gids.append(gid) - it = int(time // t_interval) - rates[it, gid] += 1 - - fg, ax = plt.subplots() - ax.scatter(times, gids, c=gids) - ax.set_xlabel("Time $(t/ms)$") - ax.set_ylabel("GID") - ax.set_xlim(0, T) - fg.savefig(f"{prefix}raster.pdf") - fg.savefig(f"{prefix}raster.png") - fg.savefig(f"{prefix}raster.svg") - - ts = np.arange(n_interval) * t_interval - mean_rate = savgol_filter(rates.mean(axis=1), window_length=5, polyorder=2) - fg, ax = plt.subplots() - ax.plot(ts, rates) - ax.plot(ts, mean_rate, color="0.8", lw=4, label="Mean rate") - ax.set_xlabel("Time $(t/ms)$") - ax.legend() - ax.set_ylabel("Rate $(kHz)$") - ax.set_xlim(0, T) - fg.savefig(f"{prefix}rates.pdf") - fg.savefig(f"{prefix}rates.png") - fg.savefig(f"{prefix}rates.svg") - - -def randrange(n: int): - res = np.arange(n, dtype=int) - np.random.shuffle(res) - return res diff --git a/python/example/probe_lfpykit.py b/python/example/probe_lfpykit.py index 45a0ba5a9..795b325d0 100644 --- a/python/example/probe_lfpykit.py +++ b/python/example/probe_lfpykit.py @@ -88,16 +88,15 @@ def probes(self, _): .paint("(all)", A.density("pas/e=-65", g=0.0001)) # attach the stimulus .place(str(clamp_location), iclamp, "iclamp") + # use a fixed 3 CVs per branch + .discretization(A.cv_policy_fixed_per_branch(3)) ) -# use a fixed 3 CVs per branch -cvp = A.cv_policy_fixed_per_branch(3) - # place_pwlin can be queried with region/locset expressions to obtain # geometrical objects, like points and segments, essentially recovering # geometry from morphology. ppwl = A.place_pwlin(morphology) -cell = A.cable_cell(morphology, decor, discretization=cvp) +cell = A.cable_cell(morphology, decor) # instantiate recipe with cell rec = Recipe(cell) diff --git a/python/example/single_cell_allen.py b/python/example/single_cell_allen.py index 2cbcb2da0..b3d30c441 100644 --- a/python/example/single_cell_allen.py +++ b/python/example/single_cell_allen.py @@ -131,10 +131,10 @@ def make_cell(base, swc, fit): decor.place('"midpoint"', A.threshold_detector(-40 * U.mV), "sd") # (10) discretisation strategy: max compartment length - cvp = A.cv_policy_max_extent(20) + decor.discretization(A.cv_policy_max_extent(20)) # (11) Create cell - return A.cable_cell(morphology, decor, labels, cvp), offset + return A.cable_cell(morphology, decor, labels), offset # (12) Create cell, model diff --git a/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc b/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc index 0370e9754..ad5cfa7de 100644 --- a/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc +++ b/python/example/single_cell_bluepyopt/l5pc/C060114A7_axon_replacement.acc @@ -1,6 +1,6 @@ (arbor-component (meta-data - (version "0.10-dev")) + (version "0.9-dev")) (morphology (branch 0 -1 (segment 0 diff --git a/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc b/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc index 3ec66000d..317282f7d 100644 --- a/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc +++ b/python/example/single_cell_bluepyopt/l5pc/C060114A7_modified.acc @@ -1,6 +1,6 @@ (arbor-component (meta-data - (version "0.10-dev")) + (version "0.9-dev")) (morphology (branch 0 -1 (segment 0 diff --git a/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc b/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc index 73007e7ff..29831b671 100644 --- a/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc +++ b/python/example/single_cell_bluepyopt/l5pc/l5pc_decor.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (decor (default (membrane-potential -65 (scalar 1.0))) (default (temperature-kelvin 307.14999999999998 (scalar 1.0))) diff --git a/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc b/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc index a6ff3884a..c15ec6057 100644 --- a/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc +++ b/python/example/single_cell_bluepyopt/l5pc/l5pc_label_dict.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (label-dict (region-def "all" (all)) (region-def "apic" (tag 4)) diff --git a/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc b/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc index bb67b8b82..c46b2578e 100644 --- a/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc +++ b/python/example/single_cell_bluepyopt/simplecell/simple_cell_decor.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (decor (paint (region "soma") (membrane-capacitance 0.01 (scalar 1))) (paint (region "soma") (density (mechanism "default::hh" ("gnabar" 0.10299326453483033) ("gkbar" 0.027124836082684685)))))) diff --git a/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc b/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc index a6ff3884a..c15ec6057 100644 --- a/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc +++ b/python/example/single_cell_bluepyopt/simplecell/simple_cell_label_dict.acc @@ -1,5 +1,5 @@ (arbor-component - (meta-data (version "0.10-dev")) + (meta-data (version "0.9-dev")) (label-dict (region-def "all" (all)) (region-def "apic" (tag 4)) diff --git a/python/example/single_cell_bluepyopt_l5pc.py b/python/example/single_cell_bluepyopt_l5pc.py index 9639202e6..0a66b2e47 100755 --- a/python/example/single_cell_bluepyopt_l5pc.py +++ b/python/example/single_cell_bluepyopt_l5pc.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import arbor as A +import arbor import pandas import seaborn import sys @@ -12,6 +12,7 @@ exit(-42) # (1) Read the cell JSON description referencing morphology, label dictionary and decor. + if len(sys.argv) < 2: print("No JSON file passed to the program") sys.exit(0) @@ -20,6 +21,7 @@ cell_json, morpho, decor, labels = ephys.create_acc.read_acc(cell_json_filename) # (2) Define labels for stimuli and voltage recordings. + labels["soma_center"] = "(location 0 0.5)" labels["dend1"] = ( '(restrict-to (distal-translate (proximal (region "apic")) 660)' @@ -27,39 +29,42 @@ ) # (3) Define stimulus and spike detector, adjust discretization + decor.place( - '"soma_center"', A.iclamp(tstart=295, duration=5, current=1.9), "soma_iclamp" + '"soma_center"', arbor.iclamp(tstart=295, duration=5, current=1.9), "soma_iclamp" ) # Add spike detector -decor.place('"soma_center"', A.threshold_detector(-10), "detector") +decor.place('"soma_center"', arbor.threshold_detector(-10), "detector") # Adjust discretization (single CV on soma, default everywhere else) -cvp = A.cv_policy_max_extent(1.0) | A.cv_policy_single('"soma"') +decor.discretization(arbor.cv_policy_max_extent(1.0) | arbor.cv_policy_single('"soma"')) # (4) Create the cell. -cell = A.cable_cell(morpho, decor, labels, cvp) + +cell = arbor.cable_cell(morpho, decor, labels) # (5) Declare a probe. -probe = A.cable_probe_membrane_voltage('"dend1"') + +probe = arbor.cable_probe_membrane_voltage('"dend1"') -# (6) Create a class that inherits from A.recipe -class single_recipe(A.recipe): +# (6) Create a class that inherits from arbor.recipe +class single_recipe(arbor.recipe): # (6.1) Define the class constructor def __init__(self, cell, probes): # The base C++ class constructor must be called first, to ensure that # all memory in the C++ class is initialized correctly. - super().__init__() + arbor.recipe.__init__(self) self.the_cell = cell self.the_probes = probes - self.the_props = A.neuron_cable_properties() + self.the_props = arbor.neuron_cable_properties() # Add catalogues with explicit qualifiers - self.the_props.catalogue = A.catalogue() - self.the_props.catalogue.extend(A.default_catalogue(), "default::") - self.the_props.catalogue.extend(A.bbp_catalogue(), "BBP::") + self.the_props.catalogue = arbor.catalogue() + self.the_props.catalogue.extend(arbor.default_catalogue(), "default::") + self.the_props.catalogue.extend(arbor.bbp_catalogue(), "BBP::") # (6.2) Override the num_cells method def num_cells(self): @@ -67,7 +72,7 @@ def num_cells(self): # (6.3) Override the cell_kind method def cell_kind(self, gid): - return A.cell_kind.cable + return arbor.cell_kind.cable # (6.4) Override the cell_description method def cell_description(self, gid): @@ -87,13 +92,13 @@ def global_properties(self, gid): recipe = single_recipe(cell, [probe]) # (7) Create a simulation (using defaults for context and partition_load_balance) -sim = A.simulation(recipe) +sim = arbor.simulation(recipe) # Instruct the simulation to record the spikes and sample the probe -sim.record(A.spike_recording.all) +sim.record(arbor.spike_recording.all) -probe_id = A.cell_member(0, 0) -handle = sim.sample(probe_id, A.regular_schedule(0.02)) +probe_id = arbor.cell_member(0, 0) +handle = sim.sample(probe_id, arbor.regular_schedule(0.02)) # (8) Run the simulation sim.run(tfinal=600, dt=0.025) diff --git a/python/example/single_cell_bluepyopt_simplecell.py b/python/example/single_cell_bluepyopt_simplecell.py index 78676d716..bffcdce50 100755 --- a/python/example/single_cell_bluepyopt_simplecell.py +++ b/python/example/single_cell_bluepyopt_simplecell.py @@ -1,6 +1,6 @@ #!/usr/bin/env python3 -import arbor as A +import arbor import pandas import seaborn import sys @@ -12,6 +12,7 @@ exit(-42) # (1) Read the cell JSON description referencing morphology, label dictionary and decor. + if len(sys.argv) < 2: print("No JSON file passed to the program") sys.exit(0) @@ -20,29 +21,33 @@ cell_json, morpho, decor, labels = ephys.create_acc.read_acc(cell_json_filename) # (2) Define labels for stimuli and voltage recordings. + labels["soma_center"] = "(location 0 0.5)" # (3) Define stimulus and spike detector, adjust discretization + decor.place( - '"soma_center"', A.iclamp(tstart=100, duration=50, current=0.05), "soma_iclamp" + '"soma_center"', arbor.iclamp(tstart=100, duration=50, current=0.05), "soma_iclamp" ) # Add spike detector -decor.place('"soma_center"', A.threshold_detector(-10), "detector") +decor.place('"soma_center"', arbor.threshold_detector(-10), "detector") # Adjust discretization (single CV on soma, default everywhere else) -cvp = A.cv_policy_max_extent(1.0) | A.cv_policy_single('"soma"') +decor.discretization(arbor.cv_policy_max_extent(1.0) | arbor.cv_policy_single('"soma"')) # (4) Create the cell. -cell = A.cable_cell(morpho, decor, labels, cvp) + +cell = arbor.cable_cell(morpho, decor, labels) # (5) Make the single cell model. -m = A.single_cell_model(cell) + +m = arbor.single_cell_model(cell) # Add catalogues with qualifiers -m.properties.catalogue = A.catalogue() -m.properties.catalogue.extend(A.default_catalogue(), "default::") -m.properties.catalogue.extend(A.bbp_catalogue(), "BBP::") +m.properties.catalogue = arbor.catalogue() +m.properties.catalogue.extend(arbor.default_catalogue(), "default::") +m.properties.catalogue.extend(arbor.bbp_catalogue(), "BBP::") # (6) Attach voltage probe that samples at 50 kHz. m.probe("voltage", where='"soma_center"', frequency=50) diff --git a/python/example/single_cell_cable.py b/python/example/single_cell_cable.py index 02da84643..fc6cceb15 100755 --- a/python/example/single_cell_cable.py +++ b/python/example/single_cell_cable.py @@ -103,8 +103,9 @@ def cell_description(self, _): ) policy = A.cv_policy_max_extent(self.cv_policy_max_extent) + decor.discretization(policy) - return A.cable_cell(tree, decor, labels, policy) + return A.cable_cell(tree, decor, labels) def probes(self, _): return self.the_probes diff --git a/python/example/single_cell_detailed.py b/python/example/single_cell_detailed.py index 7ea7220f1..1e4b7659b 100755 --- a/python/example/single_cell_detailed.py +++ b/python/example/single_cell_detailed.py @@ -71,13 +71,12 @@ .place('"root"', A.iclamp(30 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp1") .place('"root"', A.iclamp(50 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp2") .place('"axon_terminal"', A.threshold_detector(-10 * U.mV), "detector") + # Set discretisation: Soma as one CV, 1um everywhere else + .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) -# Set discretisation: Soma as one CV, 1um everywhere else -cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') - # (4) Create the cell. -cell = A.cable_cell(morph, decor, labels, cvp) +cell = A.cable_cell(morph, decor, labels) # (5) Construct the model model = A.single_cell_model(cell) diff --git a/python/example/single_cell_detailed_recipe.py b/python/example/single_cell_detailed_recipe.py index b45f2076c..65013797a 100644 --- a/python/example/single_cell_detailed_recipe.py +++ b/python/example/single_cell_detailed_recipe.py @@ -67,13 +67,13 @@ .place('"root"', A.iclamp(30 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp1") .place('"root"', A.iclamp(50 * U.ms, 1 * U.ms, current=2 * U.nA), "iclamp2") .place('"axon_terminal"', A.threshold_detector(-10 * U.mV), "detector") + # Set discretisation: Soma as one CV, 1um everywhere else + .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) -# Set discretisation: Soma as one CV, 1um everywhere else -cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') -# (4) Create the cell -cell = A.cable_cell(lmrf.morphology, decor, labels, cvp) +# (4) Create the cell. +cell = A.cable_cell(lmrf.morphology, decor, labels) # (5) Create a class that inherits from A.recipe diff --git a/python/example/single_cell_nml.py b/python/example/single_cell_nml.py index 86eff5052..99f74e666 100755 --- a/python/example/single_cell_nml.py +++ b/python/example/single_cell_nml.py @@ -60,13 +60,12 @@ .place('"stim_site"', A.iclamp(8 * U.ms, 1 * U.ms, current=4 * U.nA), "iclamp3") # Detect spikes at the soma with a voltage threshold of -10 mV. .place('"axon_end"', A.threshold_detector(-10 * U.mV), "detector") + # Set discretisation: Soma as one CV, 1um everywhere else + .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) -# Set discretisation: Soma as one CV, 1um everywhere else -cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') - # Combine morphology with region and locset definitions to make a cable cell. -cell = A.cable_cell(morpho, decor, labels, cvp) +cell = A.cable_cell(morpho, decor, labels) print(cell.locations('"axon_end"')) diff --git a/python/example/single_cell_stdp.py b/python/example/single_cell_stdp.py index 2df25e81b..d460fbd7d 100755 --- a/python/example/single_cell_stdp.py +++ b/python/example/single_cell_stdp.py @@ -59,9 +59,7 @@ def event_generators(self, gid): def probes(self, gid): def mk(s, t): - return A.cable_probe_point_state( - "stpd_synapse", "expsyn_stdp", state=s, tag=t - ) + return A.cable_probe_point_state(1, "expsyn_stdp", state=s, tag=t) return [ A.cable_probe_membrane_voltage('"center"', "Um"), diff --git a/python/example/single_cell_swc.py b/python/example/single_cell_swc.py index 5186b7e1e..6455850ae 100755 --- a/python/example/single_cell_swc.py +++ b/python/example/single_cell_swc.py @@ -60,14 +60,13 @@ .place('"stim_site"', A.iclamp(8 * U.ms, 1 * U.ms, current=4 * U.nA), "iclamp3") # Detect spikes at the soma with a voltage threshold of -10 mV. .place('"axon_end"', A.threshold_detector(-10 * U.mV), "detector") + # Create the policy used to discretise the cell into CVs. + # Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. + .discretization('(replace (single (region "soma")) (max-extent 1.0))') ) -# Create the policy used to discretise the cell into CVs. -# Use a single CV for the soma, and CVs of maximum length 1 μm elsewhere. -cvp = A.cv_policy('(replace (single (region "soma")) (max-extent 1.0))') - # Combine morphology with region and locset definitions to make a cable cell. -cell = A.cable_cell(morpho, decor, labels, cvp) +cell = A.cable_cell(morpho, decor, labels) # Make single cell model. m = A.single_cell_model(cell) diff --git a/python/mechanism.cpp b/python/mechanism.cpp index adce18b95..ff74b72af 100644 --- a/python/mechanism.cpp +++ b/python/mechanism.cpp @@ -65,27 +65,19 @@ void register_mechanisms(pybind11::module& m) { .def(pybind11::init()) .def_readonly("write_int_con", &arb::ion_dependency::write_concentration_int) .def_readonly("write_ext_con", &arb::ion_dependency::write_concentration_ext) - .def_readonly("read_int_con", &arb::ion_dependency::read_concentration_int) - .def_readonly("read_ext_con", &arb::ion_dependency::read_concentration_ext) .def_readonly("write_rev_pot", &arb::ion_dependency::write_reversal_potential) .def_readonly("read_rev_pot", &arb::ion_dependency::read_reversal_potential) .def("__repr__", [](const arb::ion_dependency& dep) { auto tf = [](bool x) {return x? "True": "False";}; - return util::pprintf("{read_int_con: {}, write_int_con: {}, read_ext_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", - tf(dep.read_concentration_int), - tf(dep.write_concentration_int), - tf(dep.read_concentration_ext), - tf(dep.write_concentration_ext), + return util::pprintf("{write_int_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", + tf(dep.write_concentration_int), tf(dep.write_concentration_ext), tf(dep.write_reversal_potential), tf(dep.read_reversal_potential)); }) .def("__str__", [](const arb::ion_dependency& dep) { auto tf = [](bool x) {return x? "True": "False";}; - return util::pprintf("{read_int_con: {}, write_int_con: {}, read_ext_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", - tf(dep.read_concentration_int), - tf(dep.write_concentration_int), - tf(dep.read_concentration_ext), - tf(dep.write_concentration_ext), + return util::pprintf("{write_int_con: {}, write_ext_con: {}, write_rev_pot: {}, read_rev_pot: {}}", + tf(dep.write_concentration_int), tf(dep.write_concentration_ext), tf(dep.write_reversal_potential), tf(dep.read_reversal_potential)); }) ; diff --git a/python/probes.cpp b/python/probes.cpp index bb264688d..ad734fff8 100644 --- a/python/probes.cpp +++ b/python/probes.cpp @@ -194,7 +194,7 @@ arb::probe_info cable_probe_density_state_cell(const char* mechanism, const char return {arb::cable_probe_density_state_cell{mechanism, state}, tag}; }; -arb::probe_info cable_probe_point_state(const arb::cell_tag_type& target, const char* mechanism, const char* state, const std::string& tag) { +arb::probe_info cable_probe_point_state(arb::cell_lid_type target, const char* mechanism, const char* state, const std::string& tag) { return {arb::cable_probe_point_state{target, mechanism, state}, tag}; } @@ -257,17 +257,15 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { cable_probe_point_info .def_readwrite("target", &arb::cable_probe_point_info::target, - "The tag of the point process instance on the cell.") - .def_readwrite("lid", &arb::cable_probe_point_info::lid, - "The local index of the point process instance on the cell.") + "The target index of the point process instance on the cell.") .def_readwrite("multiplicity", &arb::cable_probe_point_info::multiplicity, "Number of coalesced point processes (linear synapses) associated with this instance.") .def_readwrite("location", &arb::cable_probe_point_info::loc, "Location of point process instance on cell.") .def("__str__", [](arb::cable_probe_point_info m) { - return pprintf("", m.target, m.lid, m.multiplicity, m.loc);}) + return pprintf("", m.target, m.multiplicity, m.loc);}) .def("__repr__",[](arb::cable_probe_point_info m) { - return pprintf("", m.target, m.lid, m.multiplicity, m.loc);}); + return pprintf("", m.target, m.multiplicity, m.loc);}); // Probe address constructors: @@ -308,8 +306,8 @@ void register_cable_probes(pybind11::module& m, pyarb_global_ptr global_ptr) { "mechanism"_a, "state"_a, "tag"_a); m.def("cable_probe_point_state", &cable_probe_point_state, - "Probe specification for a cable cell point mechanism state variable value at a given target index.", - "target"_a, "mechanism"_a, "state"_a, "tag"_a); + "Probe specification for a cable cell point mechanism state variable value at a given target index.", + "target"_a, "mechanism"_a, "state"_a, "tag"_a); m.def("cable_probe_point_state_cell", &cable_probe_point_state_cell, "Probe specification for a cable cell point mechanism state variable value at every corresponding target.", diff --git a/python/strprintf.hpp b/python/strprintf.hpp index 2ccf12c1a..638016772 100644 --- a/python/strprintf.hpp +++ b/python/strprintf.hpp @@ -168,7 +168,7 @@ namespace impl { for (auto& x: s.seq_) { if (!first) o << s.sep_; first = false; - o << s.f_(x); + o << s.f(x); } return o; } diff --git a/python/test/unit/test_io.py b/python/test/unit/test_io.py index 6a78beaa7..f9e4049aa 100644 --- a/python/test/unit/test_io.py +++ b/python/test/unit/test_io.py @@ -9,7 +9,7 @@ acc = """(arbor-component (meta-data - (version "0.10-dev")) + (version "0.9-dev")) (cable-cell (morphology (branch 0 -1 @@ -231,7 +231,9 @@ def cell_description(self, _): dec = A.decor() dec.paint("(all)", A.density("pas")) - return A.cable_cell(tree, dec, discretization=A.cv_policy("(max-extent 1)")) + dec.discretization(A.cv_policy("(max-extent 1)")) + + return A.cable_cell(tree, dec) def global_properties(self, _): return self.the_props diff --git a/python/test/unit/test_probes.py b/python/test/unit/test_probes.py index d38d4ce3d..f72bfb4e3 100644 --- a/python/test/unit/test_probes.py +++ b/python/test/unit/test_probes.py @@ -19,15 +19,12 @@ def __init__(self): st = A.segment_tree() st.append(A.mnpos, (0, 0, 0, 10), (1, 0, 0, 10), 1) - dec = ( - A.decor() - # This ensures we _read_ nai/nao and can probe them, too - .set_ion(ion="na", method="nernst/x=na") - .place("(location 0 0.08)", A.synapse("expsyn"), "syn0") - .place("(location 0 0.09)", A.synapse("exp2syn"), "syn1") - .place("(location 0 0.1)", A.iclamp(20.0 * U.nA), "iclamp") - .paint("(all)", A.density("hh")) - ) + dec = A.decor() + + dec.place("(location 0 0.08)", A.synapse("expsyn"), "syn0") + dec.place("(location 0 0.09)", A.synapse("exp2syn"), "syn1") + dec.place("(location 0 0.1)", A.iclamp(20.0 * U.nA), "iclamp") + dec.paint("(all)", A.density("hh")) self.cell = A.cable_cell(st, dec) @@ -43,7 +40,7 @@ def cell_kind(self, gid): def global_properties(self, kind): return self.props - def probes(self, gid): + def probes(self, _): # Use keyword arguments to check that the wrappers have actually declared keyword arguments correctly. # Place single-location probes at (location 0 0.01*j) where j is the index of the probe address in # the returned list. @@ -61,7 +58,7 @@ def probes(self, gid): ), A.cable_probe_density_state_cell(mechanism="hh", state="n", tag="hh-n-all"), A.cable_probe_point_state( - target="syn0", mechanism="expsyn", state="g", tag="expsyn-g" + target=0, mechanism="expsyn", state="g", tag="expsyn-g" ), A.cable_probe_point_state_cell( mechanism="exp2syn", state="B", tag="expsyn-B-all" @@ -130,14 +127,14 @@ def test_probe_addr_metadata(self): self.assertEqual(1, len(m)) self.assertEqual(A.location(0, 0.08), m[0].location) self.assertEqual(1, m[0].multiplicity) - self.assertEqual("syn0", m[0].target) + self.assertEqual(0, m[0].target) m = sim.probe_metadata((0, "expsyn-B-all")) self.assertEqual(1, len(m)) self.assertEqual(1, len(m[0])) self.assertEqual(A.location(0, 0.09), m[0][0].location) self.assertEqual(1, m[0][0].multiplicity) - self.assertEqual("syn1", m[0][0].target) + self.assertEqual(1, m[0][0].target) m = sim.probe_metadata((0, "ina")) self.assertEqual(1, len(m)) diff --git a/spack/package.py b/spack/package.py index 54381c199..08c3a5ce9 100644 --- a/spack/package.py +++ b/spack/package.py @@ -65,13 +65,11 @@ class Arbor(CMakePackage, CudaPackage): variant("doc", default=False, description="Build documentation.") variant("mpi", default=False, description="Enable MPI support") variant("python", default=True, description="Enable Python frontend support") - variant("pystubs", default=True, when="@0.11:", description="Python stub generation") variant( "vectorize", default=False, description="Enable vectorization of computational kernels", ) - variant("hwloc", default=False, description="support for thread pinning via HWLOC") variant( "gpu_rng", default=False, @@ -109,9 +107,6 @@ class Arbor(CMakePackage, CudaPackage): depends_on("mpi", when="+mpi") depends_on("py-mpi4py", when="+mpi+python", type=("build", "run")) - # hwloc - depends_on("hwloc@2:", when="+hwloc", type=("build", "run")) - # python (bindings) with when("+python"): extends("python") @@ -123,7 +118,6 @@ class Arbor(CMakePackage, CudaPackage): depends_on("py-pybind11@2.10.1:", when="@0.7.1:", type="build") depends_on("py-pybind11@2.10.1:", when="@0.7.1:", type="build") depends_on("py-pybind11@2.10.1:", when="@2.11.1:", type="build") - depends_on("py-pybind11-stubgen@2.5:", when="+pystubs", type="build") # sphinx based documentation with when("+doc"): @@ -141,8 +135,6 @@ def cmake_args(self): self.define_from_variant("ARB_WITH_MPI", "mpi"), self.define_from_variant("ARB_WITH_PYTHON", "python"), self.define_from_variant("ARB_VECTORIZE", "vectorize"), - self.define_from_variant("ARB_USE_HWLOC", "hwloc"), - self.define_from_variant("ARB_BUILD_PYTHON_STUBS", "pystubs"), ] if "+cuda" in self.spec: diff --git a/test/common_cells.cpp b/test/common_cells.cpp index d275de09b..903e4e0a4 100644 --- a/test/common_cells.cpp +++ b/test/common_cells.cpp @@ -97,9 +97,11 @@ mcable soma_cell_builder::cable(mcable cab) const { // Add a new branch that is attached to parent_branch. // Returns the id of the new branch. -msize_t soma_cell_builder::add_branch(msize_t parent_branch, - double len, double r1, double r2, int ncomp, - const std::string& region) { +msize_t soma_cell_builder::add_branch( + msize_t parent_branch, + double len, double r1, double r2, int ncomp, + const std::string& region) +{ // Get tag id of region (add a new tag if region does not already exist). int tag = get_tag(region); @@ -145,13 +147,18 @@ cable_cell_description soma_cell_builder::make_cell() const { // Make label dictionary with one entry for each tag. label_dict dict; - for (auto& [k, v]: tag_map) { - dict.set(k, reg::tagged(v)); + for (auto& tag: tag_map) { + dict.set(tag.first, reg::tagged(tag.second)); } auto boundaries = cv_boundaries; - for (auto& b: boundaries) b = location(b); - return {std::move(tree), std::move(dict), {}, cv_policy_explicit(boundaries)}; + for (auto& b: boundaries) { + b = location(b); + } + decor decorations; + decorations.set_default(cv_policy_explicit(boundaries)); + // Construct cable_cell from sample tree, dictionary and decorations. + return {std::move(tree), std::move(dict), std::move(decorations)}; } /* @@ -178,7 +185,7 @@ cable_cell_description make_cell_soma_only(bool with_stim) { "cc"); } - return c; + return {c.morph, c.labels, c.decorations}; } /* @@ -215,7 +222,7 @@ cable_cell_description make_cell_ball_and_stick(bool with_stim) { "cc"); } - return c; + return {c.morph, c.labels, c.decorations}; } /* @@ -258,7 +265,7 @@ cable_cell_description make_cell_ball_and_3stick(bool with_stim) { "cc1"); } - return c; + return {c.morph, c.labels, c.decorations}; } } // namespace arb diff --git a/test/common_cells.hpp b/test/common_cells.hpp index cf72da985..6e9cda85a 100644 --- a/test/common_cells.hpp +++ b/test/common_cells.hpp @@ -14,9 +14,10 @@ struct cable_cell_description { morphology morph; label_dict labels; decor decorations; - std::optional discretization; - operator cable_cell() const { return cable_cell(morph, decorations, labels, discretization); } + operator cable_cell() const { + return cable_cell(morph, decorations, labels); + } }; class soma_cell_builder { @@ -37,7 +38,7 @@ class soma_cell_builder { // Add a new branch that is attached to parent_branch. // Returns the id of the new branch. msize_t add_branch(msize_t parent_branch, double len, double r1, double r2, int ncomp, - const std::string& region); + const std::string& region); mlocation location(mlocation) const; mcable cable(mcable) const; diff --git a/test/ubench/CMakeLists.txt b/test/ubench/CMakeLists.txt index 962898765..56bf7b404 100644 --- a/test/ubench/CMakeLists.txt +++ b/test/ubench/CMakeLists.txt @@ -26,7 +26,7 @@ foreach(bench_src ${bench_sources}) string(REGEX REPLACE "\\.[^.]*$" "" bench_exe ${bench_src}) add_executable(${bench_exe} EXCLUDE_FROM_ALL "${bench_src}") - target_link_libraries(${bench_exe} arbor arborio arbor-private-headers ext-bench) + target_link_libraries(${bench_exe} arbor arborio arbor-private-headers benchmark) target_compile_options(${bench_exe} PRIVATE ${ARB_CXX_FLAGS_TARGET_FULL}) target_compile_definitions(${bench_exe} PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") list(APPEND bench_exe_list ${bench_exe}) diff --git a/test/ubench/event_binning.cpp b/test/ubench/event_binning.cpp index e56057e34..115328ca8 100644 --- a/test/ubench/event_binning.cpp +++ b/test/ubench/event_binning.cpp @@ -11,6 +11,7 @@ #include +#include "event_queue.hpp" #include "backends/event.hpp" diff --git a/test/ubench/event_setup.cpp b/test/ubench/event_setup.cpp index 035effc04..1b5ff57ce 100644 --- a/test/ubench/event_setup.cpp +++ b/test/ubench/event_setup.cpp @@ -17,9 +17,8 @@ #include -#include "backends/event.hpp" - #include "event_queue.hpp" +#include "backends/event.hpp" using namespace arb; diff --git a/test/ubench/fvm_discretize.cpp b/test/ubench/fvm_discretize.cpp index 2531a38d8..ce9cc719c 100644 --- a/test/ubench/fvm_discretize.cpp +++ b/test/ubench/fvm_discretize.cpp @@ -10,6 +10,7 @@ #include +#include "event_queue.hpp" #include "fvm_layout.hpp" #ifndef DATADIR @@ -59,6 +60,7 @@ void run_cv_geom_explicit(benchmark::State& state) { while (state.KeepRunning()) { auto ends = cv_policy_every_segment().cv_boundary_points(c); auto ends2 = cv_policy_explicit(std::move(ends)).cv_boundary_points(c); + benchmark::DoNotOptimize(cv_geometry(c, ends2)); } } @@ -66,28 +68,38 @@ void run_cv_geom_explicit(benchmark::State& state) { void run_discretize(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; const std::size_t ncv_per_branch = state.range(0); + + decor dec; auto morpho = from_swc(SWCFILE); + dec.set_default(cv_policy_fixed_per_branch(ncv_per_branch)); + while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_fixed_per_branch(ncv_per_branch)}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); } } void run_discretize_every_segment(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; + + decor dec; auto morpho = from_swc(SWCFILE); + dec.set_default(cv_policy_every_segment()); + while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_every_segment()}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); } } void run_discretize_explicit(benchmark::State& state) { auto gdflt = neuron_parameter_defaults; + decor dec; auto morpho = from_swc(SWCFILE); - auto ends = cv_policy_every_segment().cv_boundary_points(cable_cell{morpho, {}, {}, {}}); + auto ends = cv_policy_every_segment().cv_boundary_points(cable_cell{morpho, {}}); + dec.set_default(cv_policy_explicit(std::move(ends))); while (state.KeepRunning()) { - benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, {}, {}, cv_policy_explicit(ends)}, gdflt)); + benchmark::DoNotOptimize(fvm_cv_discretize(cable_cell{morpho, dec}, gdflt)); } } diff --git a/test/ubench/mech_vec.cpp b/test/ubench/mech_vec.cpp index d8ac02016..880774fda 100644 --- a/test/ubench/mech_vec.cpp +++ b/test/ubench/mech_vec.cpp @@ -63,6 +63,7 @@ class recipe_expsyn_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::tagged(1), arb::density("pas")); + decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); auto distribution = std::uniform_real_distribution(0.f, 1.0f); for(unsigned i = 0; i < num_synapse_; i++) { @@ -70,7 +71,7 @@ class recipe_expsyn_1_branch: public recipe { decor.place(arb::mlocation{0, distribution(gen)}, arb::synapse("expsyn"), "syn"); } - return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; + return arb::cable_cell{arb::morphology(tree), decor}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -109,7 +110,9 @@ class recipe_pas_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("pas")); - return arb::cable_cell {arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; + decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); + + return arb::cable_cell {arb::morphology(tree), decor}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -150,8 +153,9 @@ class recipe_pas_3_branches: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("pas")); + decor.set_default(arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)}; + return arb::cable_cell{arb::morphology(tree), decor}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -190,8 +194,9 @@ class recipe_hh_1_branch: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("hh")); + decor.set_default(arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length+soma_radius*2)/num_comp_)}; + return arb::cable_cell{arb::morphology(tree), decor}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { @@ -232,8 +237,9 @@ class recipe_hh_3_branches: public recipe { arb::decor decor; decor.paint(arb::reg::all(), arb::density("hh")); + decor.set_default(arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)); - return arb::cable_cell{arb::morphology(tree), decor, {}, arb::cv_policy_max_extent((dend_length*3+soma_radius*2)/num_comp_)}; + return arb::cable_cell{arb::morphology(tree), decor}; } virtual cell_kind get_cell_kind(cell_gid_type) const override { diff --git a/test/unit-distributed/test_communicator.cpp b/test/unit-distributed/test_communicator.cpp index 546bfe875..0e46459e0 100644 --- a/test/unit-distributed/test_communicator.cpp +++ b/test/unit-distributed/test_communicator.cpp @@ -202,9 +202,10 @@ namespace { arb::segment_tree tree; tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; + decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10*arb::units::mV}, "src"); decor.place(arb::mlocation{0, 0.5}, arb::synapse("expsyn"), "tgt"); - return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10)); + return arb::cable_cell(arb::morphology(tree), decor); } return arb::lif_cell("src", "tgt"); } @@ -273,9 +274,10 @@ namespace { arb::segment_tree tree; tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; + decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(arb::mlocation{0, 0.5}, arb::threshold_detector{10*arb::units::mV}, "src"); decor.place(arb::ls::uniform(arb::reg::all(), 0, size_, gid), arb::synapse("expsyn"), "tgt"); - return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10)); + return arb::cable_cell(arb::morphology(tree), decor); } cell_kind get_cell_kind(cell_gid_type gid) const override { return cell_kind::cable; diff --git a/test/unit-distributed/test_distributed_for_each.cpp b/test/unit-distributed/test_distributed_for_each.cpp index b9ca83b22..5c545e8c5 100644 --- a/test/unit-distributed/test_distributed_for_each.cpp +++ b/test/unit-distributed/test_distributed_for_each.cpp @@ -40,9 +40,10 @@ TEST(distributed_for_each, one_zero) { for (int i = 0; i < rank; ++i) { data.push_back(rank); } auto sample = [&](const util::range& range) { - std::size_t origin_rank = range.empty() ? 0 : range.front(); + const auto origin_rank = range.empty() ? 0 : range.front(); + EXPECT_EQ(origin_rank, range.size()); - for (std::size_t value: range) { EXPECT_EQ(value, origin_rank); } + for (const auto& value: range) { EXPECT_EQ(value, origin_rank); } ++call_count; }; @@ -70,14 +71,14 @@ TEST(distributed_for_each, multiple) { auto sample = [&](const util::range& range_1, const util::range& range_2, const util::range*>& range_3) { - std::size_t origin_rank = range_1.empty() ? 0 : range_1.front(); + const auto origin_rank = range_1.empty() ? 0 : range_1.front(); EXPECT_EQ(origin_rank + 1, range_1.size()); EXPECT_EQ(range_2.size(), 2 * range_1.size()); EXPECT_EQ(range_3.size(), 3 * range_1.size()); - for (std::size_t value: range_1) { EXPECT_EQ(value, origin_rank); } - for (auto value: range_2) { EXPECT_EQ(value, double(origin_rank)); } - for (auto value: range_3) { EXPECT_EQ(value, std::complex(origin_rank)); } + for (const auto& value: range_1) { EXPECT_EQ(value, origin_rank); } + for (const auto& value: range_2) { EXPECT_EQ(value, double(origin_rank)); } + for (const auto& value: range_3) { EXPECT_EQ(value, std::complex(origin_rank)); } ++call_count; }; diff --git a/test/unit-distributed/test_network_generation.cpp b/test/unit-distributed/test_network_generation.cpp index 1a6e09474..f7e3b55fe 100644 --- a/test/unit-distributed/test_network_generation.cpp +++ b/test/unit-distributed/test_network_generation.cpp @@ -125,8 +125,8 @@ TEST(network_generation, all) { } for (const auto& group: decomp.groups()) { - std::size_t num_dest = group.kind == cell_kind::spike_source ? 0 : 1; - for (std::size_t gid: group.gids) { + const auto num_dest = group.kind == cell_kind::spike_source ? 0 : 1; + for (const auto gid: group.gids) { EXPECT_EQ(connections_by_dest[gid].size(), num_cells * num_dest); } } @@ -141,7 +141,7 @@ TEST(network_generation, cable_only) { const auto weight = 2.0; const auto delay = 3.0; - const std::size_t num_cells = 3 * num_ranks; + const auto num_cells = 3 * num_ranks; auto rec = network_test_recipe(num_cells, selection, weight, delay); @@ -161,7 +161,7 @@ TEST(network_generation, cable_only) { for (const auto gid: group.gids) { // Only one third is a cable cell EXPECT_EQ(connections_by_dest[gid].size(), - group.kind == cell_kind::cable ? num_cells / 3 : 0); + group.kind == cell_kind::cable ? num_cells / 3 : 0); } } } diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 206f29fd4..09acca19c 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -68,10 +68,12 @@ set(unit_sources test_cv_geom.cpp test_cv_layout.cpp test_cv_policy.cpp + test_cycle.cpp test_domain_decomposition.cpp test_dry_run_context.cpp test_event_delivery.cpp test_event_generators.cpp + test_event_queue.cpp test_event_stream.cpp test_expected.cpp test_filter.cpp @@ -93,6 +95,7 @@ set(unit_sources test_matrix.cpp test_mcable_map.cpp test_cable_cell_group.cpp + test_mechanisms.cpp test_mech_temp_diam.cpp test_mechcat.cpp test_mechinfo.cpp @@ -190,7 +193,7 @@ make_catalogue_standalone( target_link_libraries(dummy-catalogue PRIVATE arbor-private-deps) add_dependencies(unit dummy-catalogue) -target_link_libraries(unit PRIVATE arbor-private-deps ext-gtest) +target_link_libraries(unit PRIVATE arbor-private-deps) target_compile_definitions(unit PRIVATE "-DDATADIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../swc\"") target_compile_definitions(unit PRIVATE "-DLIBDIR=\"${PROJECT_BINARY_DIR}/lib\"") target_include_directories(unit PRIVATE "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/test/unit/common.hpp b/test/unit/common.hpp index 23a08da97..e02b9df20 100644 --- a/test/unit/common.hpp +++ b/test/unit/common.hpp @@ -12,7 +12,6 @@ #include #include #include -#include #include diff --git a/test/unit/test_cv_geom.cpp b/test/unit/test_cv_geom.cpp index 4aec03149..7d49151b4 100644 --- a/test/unit/test_cv_geom.cpp +++ b/test/unit/test_cv_geom.cpp @@ -584,7 +584,8 @@ TEST(region_cv, custom_geometry) { { decor d; // Discretize by segment - auto cell = cable_cell(m, d, l, cv_policy_every_segment()); + d.set_default(cv_policy_every_segment()); + auto cell = cable_cell(m, d, l); auto geom = cv_data(cell); EXPECT_TRUE(geom); @@ -641,7 +642,8 @@ TEST(region_cv, custom_geometry) { {2, 0.2}, {2, 1} }); - auto cell = cable_cell(m, d, l, cv_policy_explicit(ls)); + d.set_default(cv_policy_explicit(ls)); + auto cell = cable_cell(m, d, l); auto geom = cv_data(cell); EXPECT_TRUE(geom); diff --git a/test/unit/test_cv_policy.cpp b/test/unit/test_cv_policy.cpp index 918285919..95f225971 100644 --- a/test/unit/test_cv_policy.cpp +++ b/test/unit/test_cv_policy.cpp @@ -1,3 +1,6 @@ +#include +#include +#include #include #include @@ -40,7 +43,8 @@ TEST(cv_policy, single) { for (region reg: {reg::all(), reg::branch(2), reg::cable(3, 0.25, 1.), join(reg::cable(1, 0.75, 1), reg::branch(3), reg::cable(2, 0, 0.5)), - join(reg::cable(2, 0, 0.5), reg::branch(3), reg::cable(4, 0, 0.5))}) { + join(reg::cable(2, 0, 0.5), reg::branch(3), reg::cable(4, 0, 0.5))}) + { locset expected = ls::cboundary(reg); EXPECT_TRUE(locset_eq(cell.provider(), ls::cboundary(reg), cv_policy_single(reg).cv_boundary_points(cell))); } @@ -90,7 +94,9 @@ TEST(cv_policy, explicit_policy) { TEST(cv_policy, empty_morphology) { // Any policy applied to an empty morphology should give an empty locset. - using enum cv_policy_flag; + + using namespace cv_policy_flag; + cv_policy policies[] = { cv_policy_fixed_per_branch(3), cv_policy_fixed_per_branch(3, interior_forks), @@ -110,7 +116,7 @@ TEST(cv_policy, empty_morphology) { } TEST(cv_policy, fixed_per_branch) { - using enum cv_policy_flag; + using namespace cv_policy_flag; using L = mlocation; // Root branch only: @@ -194,7 +200,7 @@ TEST(cv_policy, fixed_per_branch) { } TEST(cv_policy, max_extent) { - using enum cv_policy_flag; + using namespace cv_policy_flag; using L = mlocation; // Root branch only: @@ -262,7 +268,7 @@ TEST(cv_policy, max_extent) { } TEST(cv_policy, every_segment) { - using enum cv_policy_flag; + using namespace cv_policy_flag; // Cell with root branch and two child branches, with multiple samples per branch. // Fork is at (0., 0., 4.0). @@ -311,7 +317,7 @@ TEST(cv_policy, every_segment) { } TEST(cv_policy, domain) { - using enum cv_policy_flag; + using namespace cv_policy_flag; region reg1 = join(reg::branch(1), reg::cable(2, 0, 0.5)); region reg2 = join(reg::branch(1), reg::cable(2, 0.5, 1), reg::cable(4, 0, 1)); diff --git a/test/unit/test_cycle.cpp b/test/unit/test_cycle.cpp new file mode 100644 index 000000000..ad0b58c1a --- /dev/null +++ b/test/unit/test_cycle.cpp @@ -0,0 +1,225 @@ +#include + +#include +#include +#include + +#include "common.hpp" +#include +#include + +using namespace arb; + +TEST(cycle_iterator, construct) { + std::vector values = { 4, 2, 3 }; + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), values.cend()); + + { + // copy constructor + auto cycle_iter_copy(cycle_iter); + EXPECT_EQ(cycle_iter, cycle_iter_copy); + } + + { + // copy assignment + auto cycle_iter_copy = cycle_iter; + EXPECT_EQ(cycle_iter, cycle_iter_copy); + } + + { + // move constructor + auto cycle_iter_copy( + util::make_cyclic_iterator(values.cbegin(), values.cend()) + ); + EXPECT_EQ(cycle_iter, cycle_iter_copy); + } +} + + +TEST(cycle_iterator, increment) { + std::vector values = { 4, 2, 3 }; + + { + // test operator++ + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + auto cycle_iter_copy = cycle_iter; + + auto values_size = values.size(); + for (auto i = 0u; i < 2*values_size; ++i) { + EXPECT_EQ(values[i % values_size], *cycle_iter); + EXPECT_EQ(values[i % values_size], *cycle_iter_copy++); + ++cycle_iter; + } + } + + { + // test operator[] + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + + for (auto i = 0u; i < values.size(); ++i) { + EXPECT_EQ(values[i], cycle_iter[values.size() + i]); + } + } + + { + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + EXPECT_NE(cycle_iter + 1, cycle_iter + 10); + } +} + +TEST(cycle_iterator, decrement) { + std::vector values = { 4, 2, 3 }; + + { + // test operator-- + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + auto cycle_iter_copy = cycle_iter; + + auto values_size = values.size(); + for (auto i = 0u; i < 2*values_size; ++i) { + --cycle_iter; + cycle_iter_copy--; + auto val = values[values_size - i%values_size - 1]; + EXPECT_EQ(val, *cycle_iter); + EXPECT_EQ(val, *cycle_iter_copy); + } + } + + { + // test operator[] + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + int values_size = values.size(); + for (int i = 0; i < 2*values_size; ++i) { + auto pos = i % values_size; + pos = pos ? values_size - pos : 0; + EXPECT_EQ(values[pos], cycle_iter[-i]); + } + } + + { + auto cycle_iter = util::make_cyclic_iterator(values.cbegin(), + values.cend()); + EXPECT_NE(cycle_iter - 2, cycle_iter - 5); + EXPECT_NE(cycle_iter + 1, cycle_iter - 5); + } +} + +TEST(cycle_iterator, carray) { + int values[] = { 4, 2, 3 }; + auto cycle_iter = util::make_cyclic_iterator(std::cbegin(values), + std::cend(values)); + auto values_size = std::size(values); + for (auto i = 0u; i < 2*values_size; ++i) { + EXPECT_EQ(values[i % values_size], *cycle_iter++); + } +} + +TEST(cycle_iterator, sentinel) { + using testing::null_terminated; + + auto msg = "hello"; + auto cycle_iter = util::make_cyclic_iterator(msg, null_terminated); + + auto msg_len = std::string(msg).size(); + for (auto i = 0u; i < 2*msg_len; ++i) { + EXPECT_EQ(msg[i % msg_len], *cycle_iter++); + } +} + + +TEST(cycle, cyclic_view) { + std::vector values = { 4, 2, 3 }; + std::vector values_new; + + std::copy_n(util::cyclic_view(values).cbegin(), 10, + std::back_inserter(values_new)); + + EXPECT_EQ(10u, values_new.size()); + + auto i = 0; + for (auto const& v : values_new) { + EXPECT_EQ(values[i++ % values.size()], v); + } +} + +TEST(cycle, cyclic_view_initlist) { + std::vector values; + + std::copy_n(util::cyclic_view({2., 3., 4.}).cbegin(), 10, + std::back_inserter(values)); + + EXPECT_EQ(10u, values.size()); + + auto i = 0; + for (auto const& v : values) { + EXPECT_EQ(2 + i++ % 3, v); + } +} + +TEST(cycle_iterator, difference) { + int values[] = { 4, 2, 3 }; + + auto cycle = util::cyclic_view(values); + auto c1 = cycle.begin(); + + auto c2 = c1; + EXPECT_EQ(0, c2-c1); + + ++c2; + EXPECT_EQ(1, c2-c1); + + ++c1; + EXPECT_EQ(0, c2-c1); + + c2 += 6; + EXPECT_EQ(6, c2-c1); + + c1 += 2; + EXPECT_EQ(4, c2-c1); + + --c2; + EXPECT_EQ(3, c2-c1); + + c1 -= 3; + EXPECT_EQ(6, c2-c1); +} + +TEST(cycle_iterator, order) { + int values[] = { 4, 2, 3 }; + + auto cycle = util::cyclic_view(values); + auto c1 = cycle.begin(); + auto c2 = c1; + + EXPECT_FALSE(c1 < c2); + EXPECT_FALSE(c2 < c1); + EXPECT_TRUE(c1 <= c2); + EXPECT_TRUE(c1 >= c2); + + c2 += std::size(values); + + EXPECT_TRUE(c1 < c2); + EXPECT_FALSE(c2 < c1); + EXPECT_TRUE(c1 <= c2); + EXPECT_FALSE(c1 >= c2); +} + +TEST(cycle, cyclic_view_sentinel) { + const char *msg = "hello"; + auto cycle = util::cyclic_view( + util::make_range(msg, testing::null_terminated) + ); + + std::string msg_new; + auto msg_new_size = 2*std::string(msg).size(); + for (auto i = 0u; i < msg_new_size; ++i) { + msg_new += cycle[i]; + } + + EXPECT_EQ("hellohello", msg_new); +} diff --git a/test/unit/test_diffusion.cpp b/test/unit/test_diffusion.cpp index 8f51e38fa..264450ce7 100644 --- a/test/unit/test_diffusion.cpp +++ b/test/unit/test_diffusion.cpp @@ -37,7 +37,7 @@ constexpr int with_gpu = -1; struct linear: public recipe { linear(double x, double d, double c): extent{x}, diameter{d}, cv_length{c} { gprop.default_parameters = arb::neuron_parameter_defaults; - gprop.default_parameters.discretization = arb::cv_policy_max_extent(cv_length); + gprop.default_parameters.discretization = arb::cv_policy_max_extent{cv_length}; // Stick morphology // -----x----- segment_tree tree; diff --git a/test/unit/test_event_queue.cpp b/test/unit/test_event_queue.cpp new file mode 100644 index 000000000..2020d5155 --- /dev/null +++ b/test/unit/test_event_queue.cpp @@ -0,0 +1,160 @@ +#include + +#include +#include +#include + +#include + +#include "event_queue.hpp" + +using namespace arb; + +TEST(event_queue, push) { + using ps_event_queue = event_queue; + + ps_event_queue q; + + q.push({0u, 2.f, 2.f}); + q.push({1u, 1.f, 2.f}); + q.push({2u, 20.f, 2.f}); + q.push({3u, 8.f, 2.f}); + + EXPECT_EQ(4u, q.size()); + + std::vector times; + float maxtime(INFINITY); + while (!q.empty()) { + times.push_back(q.pop_if_before(maxtime)->time); + } + + EXPECT_TRUE(std::is_sorted(times.begin(), times.end())); +} + +TEST(event_queue, pop_if_before) { + using ps_event_queue = event_queue; + + cell_lid_type target[4] = {0u, 1u, 2u, 3u}; + + spike_event events[] = { + {target[0], 1.f, 2.f}, + {target[1], 2.f, 2.f}, + {target[2], 3.f, 2.f}, + {target[3], 4.f, 2.f} + }; + + ps_event_queue q; + for (const auto& ev: events) { + q.push(ev); + } + + EXPECT_EQ(4u, q.size()); + + auto e1 = q.pop_if_before(0.); + EXPECT_FALSE(e1); + EXPECT_EQ(4u, q.size()); + + auto e2 = q.pop_if_before(5.); + EXPECT_TRUE(e2); + EXPECT_EQ(e2->target, target[0]); + EXPECT_EQ(3u, q.size()); + + auto e3 = q.pop_if_before(5.); + EXPECT_TRUE(e3); + EXPECT_EQ(e3->target, target[1]); + EXPECT_EQ(2u, q.size()); + + auto e4 = q.pop_if_before(2.5); + EXPECT_FALSE(e4); + EXPECT_EQ(2u, q.size()); + + auto e5 = q.pop_if_before(5.); + EXPECT_TRUE(e5); + EXPECT_EQ(e5->target, target[2]); + EXPECT_EQ(1u, q.size()); + + q.pop_if_before(5.); + EXPECT_EQ(0u, q.size()); + EXPECT_TRUE(q.empty()); + + // empty queue should always return "false" + auto e6 = q.pop_if_before(100.); + EXPECT_FALSE(e6); +} + +TEST(event_queue, pop_if_not_after) { + struct event { + int time; + + event(int t): time(t) {} + }; + + event_queue queue; + + queue.push(1); + queue.push(3); + queue.push(5); + + auto e1 = queue.pop_if_not_after(2); + EXPECT_TRUE(e1); + EXPECT_EQ(1, e1->time); + + auto e2 = queue.pop_if_before(3); + EXPECT_FALSE(e2); + + auto e3 = queue.pop_if_not_after(3); + EXPECT_TRUE(e3); + EXPECT_EQ(3, e3->time); + + auto e4 = queue.pop_if_not_after(4); + EXPECT_FALSE(e4); +} + +// Event queues can be defined for arbitrary copy-constructible events +// for which `event_time(ev)` returns the corresponding time. Time values just +// need to be well-ordered on '>'. + +struct wrapped_float { + wrapped_float() {} + wrapped_float(float f): f(f) {} + + float f; + bool operator>(wrapped_float x) const { return f>x.f; } +}; + +struct minimal_event { + wrapped_float value; + explicit minimal_event(float x): value(x) {} +}; + +const wrapped_float& event_time(const minimal_event& ev) { return ev.value; } + +TEST(event_queue, minimal_event_impl) { + minimal_event events[] = { + minimal_event(3.f), + minimal_event(2.f), + minimal_event(2.f), + minimal_event(10.f) + }; + + std::vector expected; + for (const auto& ev: events) { + expected.push_back(ev.value.f); + } + std::sort(expected.begin(), expected.end()); + + event_queue q; + for (auto& ev: events) { + q.push(ev); + } + + wrapped_float maxtime(INFINITY); + + std::vector times; + while (q.size()) { + times.push_back(q.pop_if_before(maxtime)->value.f); + } + + EXPECT_EQ(expected, times); +} + diff --git a/test/unit/test_event_stream.cpp b/test/unit/test_event_stream.cpp index b7f8d762e..c6b53e04d 100644 --- a/test/unit/test_event_stream.cpp +++ b/test/unit/test_event_stream.cpp @@ -1,25 +1,110 @@ +#include +#include + +#include "timestep_range.hpp" +#include "backends/event.hpp" #include "backends/multicore/event_stream.hpp" -#include "./test_event_stream.hpp" +#include "util/rangeutil.hpp" + +using namespace arb; namespace { + constexpr cell_local_size_type mech = 13u; + + target_handle handle[4] = { + target_handle(mech, 0u), + target_handle(mech, 1u), + target_handle(mech, 4u), + target_handle(mech, 2u) + }; -template -void check(Result result) { - for (std::size_t step=0; step common_events = { + deliverable_event(2.0, handle[1], 2.f), + deliverable_event(3.0, handle[3], 4.f), + deliverable_event(3.0, handle[0], 1.f), + deliverable_event(5.0, handle[2], 3.f), + deliverable_event(5.5, handle[2], 6.f) + }; + + bool event_matches(const arb_deliverable_event_data& e, unsigned i) { + const auto& expected = common_events[i]; + return (e.weight == expected.weight); } } -} +TEST(event_stream, mark) { + using event_stream = multicore::event_stream; -TEST(event_stream, single_step) { - check(single_step()); -} + event_stream m; + + arb_deliverable_event_stream s; + + timestep_range dts{0, 6, 1.0}; + EXPECT_EQ(dts.size(), 6u); + + std::vector> events(dts.size()); + for (const auto& ev : common_events) { + events[dts.find(event_time(ev))-dts.begin()].push_back(ev); + } + arb_assert(util::sum_by(events, [] (const auto& v) {return v.size();}) == common_events.size()); + + m.init(events); + + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 0: no events + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 1: no events + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 2: 1 event at mech_index 1 + EXPECT_FALSE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 1u); + EXPECT_TRUE(event_matches(s.begin[0], 0u)); + + m.mark(); + // current time is 3: 2 events at mech_index 0 and 2 + EXPECT_FALSE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 2u); + // the order of these 2 events will be inverted on GPU due to sorting + EXPECT_TRUE(event_matches(s.begin[0], 1u)); + EXPECT_TRUE(event_matches(s.begin[1], 2u)); + + m.mark(); + // current time is 4: no events + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 5: 2 events at mech_index 4 + EXPECT_FALSE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 2u); + EXPECT_TRUE(event_matches(s.begin[0], 3u)); + EXPECT_TRUE(event_matches(s.begin[1], 4u)); + + m.mark(); + // current time is past time range + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); -TEST(event_stream, multi_step) { - check(multi_step()); + m.clear(); + // no events after clear + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); } diff --git a/test/unit/test_event_stream.hpp b/test/unit/test_event_stream.hpp deleted file mode 100644 index 412e5f723..000000000 --- a/test/unit/test_event_stream.hpp +++ /dev/null @@ -1,212 +0,0 @@ -#pragma once - -#include -#include -#include - -#include "timestep_range.hpp" -#include "backends/event.hpp" -#include "util/rangeutil.hpp" - -namespace { - -using namespace arb; - -void check_result(arb_deliverable_event_data const* results, std::vector const& expected) { - for (std::size_t i=0; i -struct result { - timestep_range steps; - std::unordered_map streams; - std::unordered_map>> expected; -}; - -template -result single_step() { - // events for 3 cells and 2 mechanisms and according targets - // - // target handles | events - // =================================================================== - // target cell div mech_id mech_index lid | t=[0,1) - // =================================================================== - // 0 0 0 0 0 0 | e@t=0.0,w=0.0 - // ------------------------------------------------------------------- - // 1 0 1 0 1 | e@t=0.1,w=1.0 - // =================================================================== - // 2 1 2 0 0 0 | e@t=0.5,w=0.2 - // 3 1 0 1 1 | e@t=0.1,w=0.3 - // ------------------------------------------------------------------- - // 4 1 1 0 2 | e@t=0.4,w=1.2 - // =================================================================== - // 5 3 5 0 0 0 | e@t=0.4,w=0.1 - // 6 3 0 1 1 | - // ------------------------------------------------------------------- - // 7 3 1 0 2 | e@t=0.3,w=1.1 - // 8 3 1 1 3 | - // 9 3 1 2 4 | e@t=0.3,w=1.3 - // =================================================================== - // 10 - - const std::vector handles = { - {0, 0}, - {1, 0}, - {0, 0}, - {0, 1}, - {1, 0}, - {0, 0}, - {0, 1}, - {1, 0}, - {1, 1}, - {1, 2} - }; - - const std::vector divs = {0, 2, 5, 10}; - - std::vector> events = { - {{0, 0.0, 0.0f}, {1, 0.0, 1.0f}}, - {{1, 0.1, 0.3f}, {2, 0.4, 1.2f}, {0, 0.5, 0.2f}}, - {{2, 0.3, 1.1f}, {4, 0.3, 1.3f}, {0, 0.4, 0.1f}} - }; - - // prepare return value - result res { - timestep_range{0,1,1}, - {{0u, Stream{}}, {1u, Stream{}}}, - {} - }; - - // expected outcome: one stream per mechanism, events ordered - res.expected[0u] = std::vector>{ - { {0, 0.0f}, {0, 0.1f}, {0, 0.2f}, {1, 0.3f} } }; - res.expected[1u] = std::vector>{ - { {0, 1.0f}, {0, 1.1f}, {0, 1.2f}, {2, 1.3f} } }; - - // initialize event streams - auto lanes = util::subrange_view(events, 0u, events.size()); - initialize(lanes, handles, divs, res.steps, res.streams); - - return res; -} - -template -result multi_step() { - // number of events, cells, mechanisms and targets - std::size_t num_events = 500; - std::size_t num_cells = 20; - std::size_t num_mechanisms = 5; - std::size_t num_targets_per_mechanism_and_cell = 8; - std::size_t num_steps = 200; - double end_time = 1.0; - - result res { - timestep_range(0.0, end_time, end_time/num_steps), - {}, - {} - }; - for (std::size_t mech_id=0; mech_id divs(num_cells+1, 0u); - std::vector handles; - handles.reserve(num_cells*num_mechanisms*num_targets_per_mechanism_and_cell); - for (std::size_t cell=0; cell(mech_id), static_cast(mech_index)); - } - } - divs[cell+1] = divs[cell] + num_mechanisms*num_targets_per_mechanism_and_cell; - } - - // events are binned by cell - std::vector> events(num_cells); - - // generate random events - std::mt19937 gen(42); - std::uniform_int_distribution<> cell_dist(0, num_cells-1); - std::uniform_int_distribution<> mech_id_dist(0, num_mechanisms-1); - std::uniform_int_distribution<> mech_index_dist(0, num_targets_per_mechanism_and_cell-1); - std::uniform_real_distribution<> time_dist(0.0, end_time); - for (std::size_t i=0; i(target), time, weight); - } - - // sort events by time - for (auto& v : events) { - std::stable_sort(v.begin(), v.end(), [](auto const& l, auto const& r) { return l.time < r.time; }); - } - - // compute expected order as permutation of a pair which indexes into events: - // first index is cell id, second index is item index - std::vector> expected_order; - expected_order.reserve(num_events); - for (std::size_t cell=0; cellt_begin(); - auto r_t0 = res.steps.find(r_event.time)->t_begin(); - auto const& l_handle = handles[divs[l_cell] + l_event.target]; - auto const& r_handle = handles[divs[r_cell] + r_event.target]; - auto l_mech_id = l_handle.mech_id; - auto r_mech_id = r_handle.mech_id; - auto l_mech_index = l_handle.mech_index; - auto r_mech_index = r_handle.mech_index; - - // sort by mech_id - if (l_mech_id < r_mech_id) return true; - if (l_mech_id > r_mech_id) return false; - // if same mech_id, sort by step - if (l_t0 < r_t0) return true; - if (l_t0 > r_t0) return false; - // if same step, sort by mech_index - if (l_mech_index < r_mech_index) return true; - if (l_mech_index > r_mech_index) return false; - // if same mech_index sort by time - return l_event.time < r_event.time; - }); - - // expected results are now mapped by mechanism id -> vector of vector of deliverable event data - // the outer vector represents time step bins, the inner vector the ordered stream of events - for (std::size_t mech_id=0; mech_id>(num_steps); - } - - // create expected results from the previously defined expected order and choose unique event weight - std::size_t cc=0; - for (auto [cell, idx] : expected_order) { - auto& event = events[cell][idx]; - auto step = res.steps.find(event.time) - res.steps.begin(); - auto const& handle = handles[divs[cell] + event.target]; - auto mech_id = handle.mech_id; - auto mech_index = handle.mech_index; - event.weight = cc++; - res.expected[mech_id][step].push_back(arb_deliverable_event_data{mech_index, event.weight}); - } - - // initialize event streams - auto lanes = util::subrange_view(events, 0u, events.size()); - initialize(lanes, handles, divs, res.steps, res.streams); - - return res; -} - -} diff --git a/test/unit/test_event_stream_gpu.cpp b/test/unit/test_event_stream_gpu.cpp index 56b9c44a9..4b548f91b 100644 --- a/test/unit/test_event_stream_gpu.cpp +++ b/test/unit/test_event_stream_gpu.cpp @@ -1,38 +1,125 @@ +#include +#include + +#include "timestep_range.hpp" +#include "backends/event.hpp" #include "backends/gpu/event_stream.hpp" #include "memory/memory.hpp" #include "memory/gpu_wrappers.hpp" #include "util/rangeutil.hpp" -#include "./test_event_stream.hpp" + +using namespace arb; namespace { + constexpr cell_local_size_type mech = 13u; -template -void cpy_d2h(T* dst, const T* src, std::size_t n) { - memory::gpu_memcpy_d2h(dst, src, sizeof(T)*n); -} + target_handle handle[4] = { + target_handle(mech, 0u), + target_handle(mech, 1u), + target_handle(mech, 4u), + target_handle(mech, 2u) + }; -template -void check(Result result) { - for (std::size_t step=0; step host_data(marked.end - marked.begin); - EXPECT_EQ(host_data.size(), result.expected[mech_id][step].size()); - if (host_data.size()) { - cpy_d2h(host_data.data(), marked.begin, host_data.size()); - check_result(host_data.data(), result.expected[mech_id][step]); - } - } + std::vector common_events = { + deliverable_event(2.0, handle[1], 2.f), + deliverable_event(3.0, handle[3], 4.f), + deliverable_event(3.0, handle[0], 1.f), + deliverable_event(5.0, handle[2], 3.f), + deliverable_event(5.5, handle[2], 6.f) + }; + + bool event_matches(const arb_deliverable_event_data& e, unsigned i) { + const auto& expected = common_events[i]; + return (e.weight == expected.weight); } -} + template + void cpy_d2h(T* dst, const T* src) { + memory::gpu_memcpy_d2h(dst, src, sizeof(T)); + } } -TEST(event_stream_gpu, single_step) { - check(single_step()); -} +TEST(event_stream_gpu, mark) { + using event_stream = gpu::event_stream; + + auto thread_pool = std::make_shared(); + + event_stream m(thread_pool); + + arb_deliverable_event_stream s; + arb_deliverable_event_data d; + + timestep_range dts{0, 6, 1.0}; + EXPECT_EQ(dts.size(), 6u); + + std::vector> events(dts.size()); + for (const auto& ev : common_events) { + events[dts.find(event_time(ev))-dts.begin()].push_back(ev); + } + arb_assert(util::sum_by(events, [] (const auto& v) {return v.size();}) == common_events.size()); + + m.init(events); + + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 0: no events + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 1: no events + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 2: 1 event at mech_index 1 + EXPECT_FALSE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 1u); + cpy_d2h(&d, s.begin+0); + EXPECT_TRUE(event_matches(d, 0u)); + + m.mark(); + // current time is 3: 2 events at mech_index 0 and 2 + EXPECT_FALSE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 2u); + // the order of these 2 events is inverted on GPU due to sorting + cpy_d2h(&d, s.begin+0); + EXPECT_TRUE(event_matches(d, 2u)); + cpy_d2h(&d, s.begin+1); + EXPECT_TRUE(event_matches(d, 1u)); + + m.mark(); + // current time is 4: no events + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); + + m.mark(); + // current time is 5: 2 events at mech_index 4 + EXPECT_FALSE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 2u); + cpy_d2h(&d, s.begin+0); + EXPECT_TRUE(event_matches(d, 3u)); + cpy_d2h(&d, s.begin+1); + EXPECT_TRUE(event_matches(d, 4u)); + + m.mark(); + // current time is past time range + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); -TEST(event_stream_gpu, multi_step) { - check(multi_step()); + m.clear(); + // no events after clear + EXPECT_TRUE(m.empty()); + s = m.marked_events(); + EXPECT_EQ(s.end - s.begin, 0u); } diff --git a/test/unit/test_fvm_layout.cpp b/test/unit/test_fvm_layout.cpp index 7d7a031cd..58ab02a6e 100644 --- a/test/unit/test_fvm_layout.cpp +++ b/test/unit/test_fvm_layout.cpp @@ -1402,7 +1402,7 @@ TEST(fvm_layout, density_norm_area_partial) { hh_end["gkbar"] = end_gkbar; auto desc = builder.make_cell(); - desc.discretization = cv_policy_fixed_per_branch(1); + desc.decorations.set_default(cv_policy_fixed_per_branch(1)); desc.decorations.paint(builder.cable({1, 0., 0.3}), density(hh_begin)); desc.decorations.paint(builder.cable({1, 0.4, 1.}), density(hh_end)); @@ -1693,7 +1693,8 @@ TEST(fvm_layout, vinterp_cable) { // CV midpoints at branch pos 0.1, 0.3, 0.5, 0.7, 0.9. // Expect voltage reference locations to be CV modpoints. - cable_cell cell{m, d, {}, cv_policy_fixed_per_branch(5)}; + d.set_default(cv_policy_fixed_per_branch(5)); + cable_cell cell{m, d}; fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Test locations, either side of CV midpoints plus extrema, CV boundaries. @@ -1752,7 +1753,8 @@ TEST(fvm_layout, vinterp_forked) { // CV 0 contains branch 0 and the fork point; CV 1 and CV 2 have CV 0 as parent, // and contain branches 1 and 2 respectively, excluding the fork point. mlocation_list cv_ends{{1, 0.}, {2, 0.}}; - cable_cell cell{m, d, {}, cv_policy_explicit(cv_ends)}; + d.set_default(cv_policy_explicit(cv_ends)); + cable_cell cell{m, d}; fvm_cv_discretization D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Points in branch 0 should only get CV 0 for interpolation. @@ -1804,10 +1806,12 @@ TEST(fvm_layout, iinterp) { if (p.second.empty()) continue; decor d; - cells.emplace_back(cable_cell{p.second, d, {}, cv_policy_fixed_per_branch(3)}); + d.set_default(cv_policy_fixed_per_branch(3)); + cells.emplace_back(cable_cell{p.second, d}); label.push_back(p.first+": forks-at-end"s); - cells.emplace_back(cable_cell{p.second, d, {}, cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)}); + d.set_default(cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)); + cells.emplace_back(cable_cell{p.second, d}); label.push_back(p.first+": interior-forks"s); } @@ -1855,7 +1859,8 @@ TEST(fvm_layout, iinterp) { // CV 0 contains branch 0 and the fork point; CV 1 and CV 2 have CV 0 as parent, // and contain branches 1 and 2 respectively, excluding the fork point. mlocation_list cv_ends{{1, 0.}, {2, 0.}}; - cable_cell cell{m, d, {}, cv_policy_explicit(cv_ends)}; + d.set_default(cv_policy_explicit(cv_ends)); + cable_cell cell{m, d}; D = fvm_cv_discretize(cell, neuron_parameter_defaults); // Expect axial current interpolations on branches 1 and 2 to match CV 1 and 2 @@ -1920,7 +1925,7 @@ TEST(fvm_layout, inhomogeneous_parameters) { auto param = neuron_parameter_defaults; param.membrane_capacitance = 42.0; - param.discretization = cv_policy_fixed_per_branch(30); + param.discretization = cv_policy_fixed_per_branch{30}; auto expected = std::vector{{8.37758,385.369,2}, // constant {8.37758,385.369,2}, diff --git a/test/unit/test_fvm_lowered.cpp b/test/unit/test_fvm_lowered.cpp index 3772f223b..2529b9841 100644 --- a/test/unit/test_fvm_lowered.cpp +++ b/test/unit/test_fvm_lowered.cpp @@ -227,9 +227,9 @@ TEST(fvm_lowered, target_handles) { // (in increasing target order) descriptions[0].decorations.place(mlocation{0, 0.7}, synapse("expsyn"), "syn0"); descriptions[0].decorations.place(mlocation{0, 0.3}, synapse("expsyn"), "syn1"); - descriptions[1].decorations.place(mlocation{2, 0.2}, synapse("exp2syn"), "syn2"); descriptions[1].decorations.place(mlocation{2, 0.8}, synapse("expsyn"), "syn3"); + descriptions[1].decorations.place(mlocation{0, 0}, threshold_detector{3.3*arb::units::mV}, "detector"); cable_cell cells[] = {descriptions[0], descriptions[1]}; @@ -268,6 +268,7 @@ TEST(fvm_lowered, target_handles) { fvm_cell fvcell1(*context); auto fvm_info1 = fvcell1.initialize({0, 1}, cable1d_recipe(cells, false)); test_target_handles(fvcell1); + } TEST(fvm_lowered, stimulus) { @@ -824,6 +825,7 @@ TEST(fvm_lowered, post_events_shared_state) { tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); arb::decor decor; + decor.set_default(arb::cv_policy_fixed_per_branch(ncv_)); auto ndetectors = detectors_per_cell_[gid]; auto offset = 1.0 / ndetectors; @@ -832,7 +834,7 @@ TEST(fvm_lowered, post_events_shared_state) { } decor.place(arb::mlocation{0, 0.5}, synapse_, "syanpse"); - return arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(ncv_)); + return arb::cable_cell(arb::morphology(tree), decor); } cell_kind get_cell_kind(cell_gid_type gid) const override { @@ -919,20 +921,22 @@ TEST(fvm_lowered, label_data) { tree.append(arb::mnpos, {0, 0, 0.0, 1.0}, {0, 0, 200, 1.0}, 1); { arb::decor decor; + decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(uniform(all(), 0, 3, 42), arb::synapse("expsyn"), "4_synapses"); decor.place(uniform(all(), 4, 4, 42), arb::synapse("expsyn"), "1_synapse"); decor.place(uniform(all(), 5, 5, 42), arb::threshold_detector{10*arb::units::mV}, "1_detector"); - cells_.push_back(arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10))); + cells_.push_back(arb::cable_cell(arb::morphology(tree), decor)); } { arb::decor decor; + decor.set_default(arb::cv_policy_fixed_per_branch(10)); decor.place(uniform(all(), 0, 2, 24), arb::threshold_detector{10*arb::units::mV}, "3_detectors"); decor.place(uniform(all(), 3, 4, 24), arb::threshold_detector{10*arb::units::mV}, "2_detectors"); decor.place(uniform(all(), 5, 6, 24), arb::junction("gj"), "2_gap_junctions"); decor.place(uniform(all(), 7, 7, 24), arb::junction("gj"), "1_gap_junction"); - cells_.push_back(arb::cable_cell(arb::morphology(tree), decor, {}, arb::cv_policy_fixed_per_branch(10))); + cells_.push_back(arb::cable_cell(arb::morphology(tree), decor)); } } diff --git a/test/unit/test_math.cpp b/test/unit/test_math.cpp index 22e788b5b..755e325c6 100644 --- a/test/unit/test_math.cpp +++ b/test/unit/test_math.cpp @@ -97,13 +97,15 @@ TEST(math, infinity) { EXPECT_GT(ldinf, 0.0l); // check default value promotes correctly (i.e., acts like INFINITY) - float f = infinity<>; - double d = infinity<>; - long double ld = infinity<>; - - EXPECT_EQ(std::numeric_limits::infinity(), f); - EXPECT_EQ(std::numeric_limits::infinity(), d); - EXPECT_EQ(std::numeric_limits::infinity(), ld); + struct { + float f; + double d; + long double ld; + } check = {infinity<>, infinity<>, infinity<>}; + + EXPECT_EQ(std::numeric_limits::infinity(), check.f); + EXPECT_EQ(std::numeric_limits::infinity(), check.d); + EXPECT_EQ(std::numeric_limits::infinity(), check.ld); } TEST(math, signum) { diff --git a/test/unit/test_matrix.cpp b/test/unit/test_matrix.cpp index c6a42f1c1..4a7a3b320 100644 --- a/test/unit/test_matrix.cpp +++ b/test/unit/test_matrix.cpp @@ -21,9 +21,10 @@ using value_type = arb_value_type; using vvec = std::vector; -TEST(matrix, construct_from_parent_only) { +TEST(matrix, construct_from_parent_only) +{ std::vector p = {0,0,1}; - solver_type m(p, {0, 3}, vvec(3), vvec(3)); + solver_type m(p, {0, 3}, vvec(3), vvec(3), vvec(3)); EXPECT_EQ(m.num_cells(), 1u); EXPECT_EQ(m.size(), 3u); EXPECT_EQ(p.size(), 3u); @@ -34,13 +35,14 @@ TEST(matrix, construct_from_parent_only) { EXPECT_EQ(mp[2], index_type(1)); } -TEST(matrix, solve_host) { +TEST(matrix, solve_host) +{ using util::make_span; using util::fill; // trivial case : 1x1 matrix { - solver_type m({0}, {0,1}, vvec(1), vvec(1)); + solver_type m({0}, {0,1}, vvec(1), vvec(1), vvec(1)); fill(m.d, 2); fill(m.u, -1); array x({1}); @@ -54,7 +56,7 @@ TEST(matrix, solve_host) { for(auto n : make_span(2, 1001)) { auto p = std::vector(n); std::iota(p.begin()+1, p.end(), 0); - solver_type m(p, {0, n}, vvec(n), vvec(n)); + solver_type m(p, {0, n}, vvec(n), vvec(n), vvec(n)); EXPECT_EQ(m.size(), (unsigned)n); EXPECT_EQ(m.num_cells(), 1u); @@ -76,7 +78,8 @@ TEST(matrix, solve_host) { } } -TEST(matrix, solve_multi_matrix) { +TEST(matrix, solve_multi_matrix) +{ // Use assemble method to construct same zero-diagonal // test case from CV data. @@ -104,7 +107,7 @@ TEST(matrix, solve_multi_matrix) { // Initial voltage of zero; currents alone determine rhs. auto v = vvec{0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0}; - array area(7, 1.0); + vvec area(7, 1.0); // (Scaled) membrane conductances contribute to diagonal. array mg = { 1000, 2000, 3000, 4000, 5000, 6000, 7000 }; @@ -118,9 +121,9 @@ TEST(matrix, solve_multi_matrix) { // Expected solution: // x = [ 4 5 6 7 8 9 10 ] - solver_type m(p, c, Cm, g); + solver_type m(p, c, Cm, g, area); std::vector expected = {4, 5, 6, 7, 8, 9, 10}; - m.solve(v, dt, i, mg, area); + m.solve(v, dt, i, mg); EXPECT_TRUE(testing::seq_almost_eq(expected, v)); } diff --git a/test/unit/test_mechanisms.cpp b/test/unit/test_mechanisms.cpp new file mode 100644 index 000000000..3cbf46bb4 --- /dev/null +++ b/test/unit/test_mechanisms.cpp @@ -0,0 +1,244 @@ +#include + +// TODO: Amend for new mechanism architecture +#if 0 + +// Prototype mechanisms in tests +#include "mech_proto/expsyn_cpu.hpp" +#include "mech_proto/exp2syn_cpu.hpp" +#include "mech_proto/hh_cpu.hpp" +#include "mech_proto/pas_cpu.hpp" +#include "mech_proto/test_kin1_cpu.hpp" +#include "mech_proto/test_kinlva_cpu.hpp" +#include "mech_proto/test_ca_cpu.hpp" + +// modcc generated mechanisms +#include "mechanisms/multicore/expsyn_cpu.hpp" +#include "mechanisms/multicore/exp2syn_cpu.hpp" +#include "mechanisms/multicore/hh_cpu.hpp" +#include "mechanisms/multicore/pas_cpu.hpp" +#include "mechanisms/multicore/test_kin1_cpu.hpp" +#include "mechanisms/multicore/test_kinlva_cpu.hpp" +#include "mechanisms/multicore/test_ca_cpu.hpp" + +#include +#include +#include +#include +#include +#include +#include + +TEST(mechanisms, helpers) { + using namespace arb; + using size_type = multicore::backend::size_type; + using value_type = multicore::backend::value_type; + + // verify that the hh and pas channels are available + EXPECT_TRUE(multicore::backend::has_mechanism("hh")); + EXPECT_TRUE(multicore::backend::has_mechanism("pas")); + + std::vector parent_index = {0,0,1,2,3,4,0,6,7,8}; + auto node_index = std::vector{0,6,7,8,9}; + auto weights = std::vector(node_index.size(), 1.0); + auto n = node_index.size(); + + // one cell + size_type ncell = 1; + std::vector cell_index(n, 0u); + + multicore::backend::array vec_i(n, 0.); + multicore::backend::array vec_v(n, 0.); + multicore::backend::array vec_t(ncell, 0.); + multicore::backend::array vec_t_to(ncell, 0.); + multicore::backend::array vec_dt(n, 0.); + + auto mech = multicore::backend::make_mechanism("hh", 0, + memory::make_view(cell_index), vec_t, vec_t_to, vec_dt, + vec_v, vec_i, weights, node_index); + + EXPECT_EQ(mech->name(), "hh"); + EXPECT_EQ(mech->size(), 5u); + + // check that an out_of_range exception is thrown if an invalid mechanism is requested + ASSERT_THROW( + multicore::backend::make_mechanism("dachshund", 0, + memory::make_view(cell_index), vec_t, vec_t_to, vec_dt, + vec_v, vec_i, weights, node_index), + std::out_of_range + ); +} + +// Setup and update mechanism +template +void mech_update(T* mech, unsigned num_iters) { + + using namespace arb; + std::map> ions; + + mech->set_params(); + mech->init(); + for (auto ion_kind : ion_kinds()) { + auto ion_indexes = util::make_copy>( + mech->node_index_ + ); + + // Create and fill in the ion + ion ion = ion_indexes; + + memory::fill(ion.current(), 5.); + memory::fill(ion.reversal_potential(), 100.); + memory::fill(ion.internal_concentration(), 10.); + memory::fill(ion.external_concentration(), 140.); + ions[ion_kind] = ion; + + if (mech->uses_ion(ion_kind).uses) { + mech->set_ion(ion_kind, ions[ion_kind], ion_indexes); + } + } + + for (auto i=0u; inode_index_.size(); ++i) { + mech->net_receive(i, 1.); + } + + for (auto i=0u; iupdate_current(); + mech->update_state(); + } +} + +template +void array_init(T& array, const Seq& seq) { + auto seq_iter = seq.cbegin(); + for (auto& e : array) { + e = *seq_iter++; + } +} + +template +struct mechanism_info { + using mechanism_type = S; + using proto_mechanism_type = T; + static constexpr bool index_aliasing = alias; +}; + +template +class mechanisms : public ::testing::Test { }; + +TYPED_TEST_CASE_P(mechanisms); + +TYPED_TEST_P(mechanisms, update) { + using mechanism_type = typename TypeParam::mechanism_type; + using proto_mechanism_type = typename TypeParam::proto_mechanism_type; + + // Type checking + EXPECT_TRUE((std::is_same::value)); + EXPECT_TRUE((std::is_same::value)); + EXPECT_TRUE((std::is_same::value)); + + int num_cell = 1; + int num_syn = 32; + int num_comp = num_syn; + + typename mechanism_type::iarray node_index(num_syn); + typename mechanism_type::array voltage(num_comp, -65.0); + typename mechanism_type::array current(num_comp, 1.0); + + typename mechanism_type::array weights(num_syn, 1.0); + + typename mechanism_type::iarray cell_index(num_comp, 0); + typename mechanism_type::array time(num_cell, 2.); + typename mechanism_type::array time_to(num_cell, 2.1); + typename mechanism_type::array dt(num_comp, 2.1-2.); + + array_init(voltage, arb::util::cyclic_view({ -65.0, -61.0, -63.0 })); + array_init(current, arb::util::cyclic_view({ 1.0, 0.9, 1.1 })); + array_init(weights, arb::util::cyclic_view({ 1.0 })); + + // Initialise indexes + std::vector index_freq; + if (TypeParam::index_aliasing) { + index_freq.assign({ 4, 2, 3 }); + } + else { + index_freq.assign({ 1 }); + } + + auto freq_begin = arb::util::cyclic_view(index_freq).cbegin(); + auto freq = freq_begin; + auto index = node_index.begin(); + while (index != node_index.end()) { + for (auto i = 0; i < *freq && index != node_index.end(); ++i) { + *index++ = freq - freq_begin; + } + ++freq; + } + + // Copy indexes, voltage and current to use for the prototype mechanism + typename mechanism_type::iarray node_index_copy(node_index); + typename mechanism_type::array voltage_copy(voltage); + typename mechanism_type::array current_copy(current); + typename mechanism_type::array weights_copy(weights); + + // Create mechanisms + auto mech = arb::make_mechanism( + 0, cell_index, time, time_to, dt, + voltage, current, std::move(weights), std::move(node_index) + ); + + auto mech_proto = arb::make_mechanism( + 0, cell_index, time, time_to, dt, + voltage_copy, current_copy, + std::move(weights_copy), std::move(node_index_copy) + ); + + mech_update(dynamic_cast(mech.get()), 10); + mech_update(dynamic_cast(mech_proto.get()), 10); + + auto citer = current_copy.begin(); + for (auto const& c: current) { + EXPECT_NEAR(*citer++, c, 1e-6); + } +} + +REGISTER_TYPED_TEST_CASE_P(mechanisms, update); + +using mechanism_types = ::testing::Types< + mechanism_info< + arb::multicore::mechanism_hh, + arb::multicore::mechanism_hh_proto + >, + mechanism_info< + arb::multicore::mechanism_pas, + arb::multicore::mechanism_pas_proto + >, + mechanism_info< + arb::multicore::mechanism_expsyn, + arb::multicore::mechanism_expsyn_proto, + true + >, + mechanism_info< + arb::multicore::mechanism_exp2syn, + arb::multicore::mechanism_exp2syn_proto, + true + >, + mechanism_info< + arb::multicore::mechanism_test_kin1, + arb::multicore::mechanism_test_kin1_proto + >, + mechanism_info< + arb::multicore::mechanism_test_kinlva, + arb::multicore::mechanism_test_kinlva_proto + >, + mechanism_info< + arb::multicore::mechanism_test_ca, + arb::multicore::mechanism_test_ca_proto + > +>; + +INSTANTIATE_TYPED_TEST_CASE_P(mechanism_types, mechanisms, mechanism_types); + +#endif // 0 diff --git a/test/unit/test_probe.cpp b/test/unit/test_probe.cpp index 489f9adaa..43449d017 100644 --- a/test/unit/test_probe.cpp +++ b/test/unit/test_probe.cpp @@ -108,7 +108,7 @@ void run_v_i_probe_test(context ctx) { builder.add_branch(0, 200, 1.0/2, 1.0/2, 1, "dend"); auto bs = builder.make_cell(); - bs.discretization = cv_policy_fixed_per_branch(1); + bs.decorations.set_default(cv_policy_fixed_per_branch(1)); auto stim = i_clamp::box(0.*U::ms, 100*U::ms, 0.3*U::nA); bs.decorations.place(mlocation{1, 1}, stim, "clamp"); @@ -209,11 +209,12 @@ void run_v_cell_probe_test(context ctx) { {"interior fork", cv_policy_fixed_per_branch(3, cv_policy_flag::interior_forks)}, }; - for (auto& [name, policy]: test_policies) { - SCOPED_TRACE(name); + for (auto& testcase: test_policies) { + SCOPED_TRACE(testcase.first); decor d; + d.set_default(testcase.second); - cable_cell cell(m, d, {}, policy); + cable_cell cell(m, d); cable1d_recipe rec(cell, false); rec.add_probe(0, "U_m", cable_probe_membrane_voltage_cell{}); @@ -235,7 +236,7 @@ void run_v_cell_probe_test(context ctx) { // Independetly discretize the cell so we can follow cable–CV relationship. - cv_geometry geom(cell, policy.cv_boundary_points(cell)); + cv_geometry geom(cell, testcase.second.cv_boundary_points(cell)); // For each cable in metadata, get CV from geom and confirm raw handle is // state voltage + CV. @@ -270,12 +271,12 @@ void run_expsyn_g_probe_test(context ctx) { auto bs = builder.make_cell(); bs.decorations.place(loc0, synapse("expsyn"), "syn0"); bs.decorations.place(loc1, synapse("expsyn"), "syn1"); - bs.discretization = cv_policy_fixed_per_branch(2); + bs.decorations.set_default(cv_policy_fixed_per_branch(2)); auto run_test = [&](bool coalesce_synapses) { cable1d_recipe rec(cable_cell(bs), coalesce_synapses); - rec.add_probe(0, "expsyn-g-1", cable_probe_point_state{"syn0", "expsyn", "g"}); - rec.add_probe(0, "expsyn-g-2", cable_probe_point_state{"syn1", "expsyn", "g"}); + rec.add_probe(0, "expsyn-g-1", cable_probe_point_state{0u, "expsyn", "g"}); + rec.add_probe(0, "expsyn-g-2", cable_probe_point_state{1u, "expsyn", "g"}); fvm_cell lcell(*ctx); auto fvm_info = lcell.initialize({0}, rec); @@ -361,23 +362,24 @@ void run_expsyn_g_cell_probe_test(context ctx) { auto policy = cv_policy_fixed_per_branch(3); arb::decor decor; + decor.set_default(policy); std::unordered_map expsyn_target_loc_map; unsigned n_expsyn = 0; for (unsigned bid = 0; bid<3u; ++bid) { for (unsigned j = 0; j<10; ++j) { - auto idx = 2*(bid*10 + j); + auto idx = (bid*10+j)*2; mlocation expsyn_loc{bid, 0.1*j}; decor.place(expsyn_loc, synapse("expsyn"), "syn"+std::to_string(idx)); expsyn_target_loc_map[2*n_expsyn] = expsyn_loc; - decor.place(mlocation{bid, 0.1*j + 0.05}, synapse("exp2syn"), "syn"+std::to_string(idx+1)); + decor.place(mlocation{bid, 0.1*j+0.05}, synapse("exp2syn"), "syn"+std::to_string(idx+1)); ++n_expsyn; } } - std::vector cells(2, arb::cable_cell(make_y_morphology(), decor, {}, policy)); + std::vector cells(2, arb::cable_cell(make_y_morphology(), decor)); // Weight for target (gid, lid) - auto weight = [](cell_gid_type gid, cell_lid_type lid) -> float { return lid + 100*gid; }; + auto weight = [](auto gid, auto tgt) -> float { return tgt + 100*gid; }; // Manually send an event to each expsyn synapse and integrate for a tiny time step. // Set up one stream per cell. @@ -429,16 +431,14 @@ void run_expsyn_g_cell_probe_test(context ctx) { std::unordered_map cv_expsyn_count; for (unsigned j = 0; j -typename B::array mk_array(size_t n, size_t a) { - return typename B::array(n, 0, util::padded_allocator<>(a)); -} - -#ifdef ARB_GPU_ENABLED -template<> -typename arb::gpu::backend::array mk_array(size_t n, size_t a) { - return arb::gpu::backend::array(n, 0); -} -#endif - template void run_ion_density_probe_test(context ctx) { using fvm_cell = typename backend_access::fvm_cell; @@ -504,9 +492,11 @@ void run_ion_density_probe_test(context ctx) { auto m = make_stick_morphology(); decor d; + d.set_default(cv_policy_fixed_per_branch(3)); // Calcium ions everywhere, half written by write_ca1, half by write_ca2. // Sodium ions only on distal half. + d.paint(mcable{0, 0., 0.5}, density("write_ca1")); d.paint(mcable{0, 0.5, 1.}, density("write_ca2")); d.paint(mcable{0, 0.5, 1.}, density("write_na3")); @@ -517,7 +507,7 @@ void run_ion_density_probe_test(context ctx) { mlocation loc1{0, 0.5}; mlocation loc2{0, 0.9}; - cable1d_recipe rec(cable_cell(m, d, {}, cv_policy_fixed_per_branch(3))); + cable1d_recipe rec(cable_cell(m, d)); rec.catalogue() = cat; rec.add_probe(0, "cai-l0", cable_probe_ion_int_concentration{loc0, "ca"}); @@ -543,34 +533,12 @@ void run_ion_density_probe_test(context ctx) { auto fvm_info = lcell.initialize({0}, rec); // We skipped FVM layout here, so we need to set these manually auto& state = backend_access::state(lcell); - auto align = state.alignment; - - auto& ca = state.ion_data["ca"]; - auto nca = ca.node_index_.size(); - auto cai = mk_array(nca, align); - ca.flags_.write_Xi_ = true; - ca.Xi_ = cai; - ca.init_Xi_ = cai; - ca.reset_Xi_ = cai; - auto cao = mk_array(nca, align); - ca.flags_.write_Xo_ = true; - ca.Xo_ = cao; - ca.init_Xo_ = cao; - ca.reset_Xo_ = cao; - - auto& na = state.ion_data["na"]; - auto nna = na.node_index_.size(); - auto nai = mk_array(nna, align); - na.flags_.write_Xi_ = true; - na.Xi_ = nai; - na.init_Xi_ = nai; - na.reset_Xi_ = nai; - auto nao = mk_array(nna, align); - na.flags_.write_Xo_ = true; - na.Xo_ = nao; - na.init_Xo_ = nao; - na.reset_Xo_ = nao; - + state.ion_data["ca"].write_Xi_ = true; + state.ion_data["ca"].write_Xo_ = true; + state.ion_data["ca"].init_concentration(); + state.ion_data["na"].write_Xi_ = true; + state.ion_data["na"].write_Xo_ = true; + state.ion_data["na"].init_concentration(); // Now, re-init cell lcell.reset(); @@ -680,8 +648,13 @@ void run_partial_density_probe_test(context ctx) { cable_cell cells[2]; // Each cell is a simple constant diameter cable, with 3 CVs each. + auto m = make_stick_morphology(); + decor d0, d1; + d0.set_default(cv_policy_fixed_per_branch(3)); + d1.set_default(cv_policy_fixed_per_branch(3)); + // Paint the mechanism on every second 10% interval of each cell. // Expected values on a CV are the weighted mean of the parameter values // over the intersections of the support and the CV. @@ -702,21 +675,20 @@ void run_partial_density_probe_test(context ctx) { auto mk_mech = [](double param) { return density(mechanism_desc("param_as_state").set("p", param)); }; - decor d0; d0.paint(mcable{0, 0.0, 0.1}, mk_mech(2)); d0.paint(mcable{0, 0.2, 0.3}, mk_mech(3)); d0.paint(mcable{0, 0.4, 0.5}, mk_mech(4)); d0.paint(mcable{0, 0.6, 0.7}, mk_mech(5)); d0.paint(mcable{0, 0.8, 0.9}, mk_mech(6)); - cells[0] = cable_cell(m, d0, {}, cv_policy_fixed_per_branch(3)); - decor d1; d1.paint(mcable{0, 0.1, 0.2}, mk_mech(7)); d1.paint(mcable{0, 0.3, 0.4}, mk_mech(8)); d1.paint(mcable{0, 0.5, 0.6}, mk_mech(9)); d1.paint(mcable{0, 0.7, 0.8}, mk_mech(10)); d1.paint(mcable{0, 0.9, 1.0}, mk_mech(11)); - cells[1] = cable_cell(m, d1, {}, cv_policy_fixed_per_branch(3)); + + cells[0] = cable_cell(m, d0); + cells[1] = cable_cell(m, d1); // Place probes in the middle of each 10% interval, i.e. at 0.05, 0.15, etc. struct test_probe { @@ -799,6 +771,7 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) { const unsigned n_cv = 3; cv_policy policy = cv_policy_fixed_per_branch(n_cv); + d.set_default(policy); d.place(mlocation{0, 0}, i_clamp(0.3*U::nA), "clamp"); @@ -810,7 +783,7 @@ void run_axial_and_ion_current_sampled_probe_test(context ctx) { d.set_default(membrane_capacitance{0.01*U::F/U::m2}); // [F/m²] auto tau = 0.1*U::ms; - cable1d_recipe rec(cable_cell(m, d, {}, policy)); + cable1d_recipe rec(cable_cell(m, d)); rec.catalogue() = cat; cable_cell cell(m, d); @@ -999,7 +972,7 @@ void run_v_sampled_probe_test(context ctx) { builder.add_branch(0, 200, 1.0/2, 1.0/2, 1, "dend"); auto bs = builder.make_cell(); - bs.discretization = cv_policy_fixed_per_branch(1); + bs.decorations.set_default(cv_policy_fixed_per_branch(1)); auto d0 = bs.decorations; auto d1 = bs.decorations; @@ -1084,7 +1057,9 @@ void run_total_current_probe_test(context ctx) { auto run_cells = [&](bool interior_forks) { auto flags = interior_forks? cv_policy_flag::interior_forks: cv_policy_flag::none; cv_policy policy = cv_policy_fixed_per_branch(n_cv_per_branch, flags); - std::vector cells = {{m, d0, {}, policy}, {m, d1, {}, policy}}; + d0.set_default(policy); + d1.set_default(policy); + std::vector cells = {{m, d0}, {m, d1}}; for (unsigned i = 0; i<2; ++i) { @@ -1188,15 +1163,17 @@ void run_stimulus_probe_test(context ctx) { cv_policy policy = cv_policy_fixed_per_branch(3); decor d0, d1; + d0.set_default(policy); d0.place(mlocation{0, 0.5}, i_clamp::box(stim_from, stim_until, 10.*U::nA), "clamp0"); d0.place(mlocation{0, 0.5}, i_clamp::box(stim_from, stim_until, 20.*U::nA), "clamp1"); double expected_stim0 = 30; + d1.set_default(policy); d1.place(mlocation{0, 1}, i_clamp::box(stim_from, stim_until, 30.*U::nA), "clamp0"); d1.place(mlocation{0, 1}, i_clamp::box(stim_from, stim_until, -10.*U::nA), "clamp1"); double expected_stim1 = 20; - std::vector cells = {{m, d0, {}, policy}, {m, d1, {}, policy}}; + std::vector cells = {{m, d0}, {m, d1}}; // Sample the cells during the stimulus, and after. diff --git a/test/unit/test_range.cpp b/test/unit/test_range.cpp index de86ef7ec..02e889fc0 100644 --- a/test/unit/test_range.cpp +++ b/test/unit/test_range.cpp @@ -14,6 +14,7 @@ #include "util/meta.hpp" #include "util/range.hpp" #include "util/rangeutil.hpp" +#include "util/sentinel.hpp" #include "util/transform.hpp" #include "common.hpp" @@ -441,34 +442,41 @@ struct foo { }; TEST(range, sort) { - { - // simple sort - char cstr[] = "howdy"; - util::sort(util::range_n(cstr, 5)); - EXPECT_EQ("dhowy"s, cstr); - } - { - // reverse sort by transform c to -c - char cstr[] = "howdy"; - util::sort_by(util::range_n(cstr, 5), - [](char c) { return -c; }); - EXPECT_EQ("ywohd"s, cstr); - } - { - // stable sort: move capitals to front, numbers to back - char mixed[] = "t5hH4E3erLL2e1O"; - util::stable_sort_by(util::strict_view(util::make_range(std::begin(mixed), null_terminated)), - [](char c) { return std::isupper(c)? 0: std::isdigit(c)? 2: 1; }); - EXPECT_EQ("HELLOthere54321"s, mixed); - } - { - // sort with user-provided less comparison function - std::vector X = {{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}}; - util::sort(X, [](const foo& l, const foo& r) {return l.y{{5, 0}, {4, 1}, {3, 2}, {2, 3}, {1, 4}, {0, 5}})); - util::sort(X, [](const foo& l, const foo& r) {return l.x{{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}})); - } + char cstr[] = "howdy"; + + auto cstr_range = util::make_range(std::begin(cstr), null_terminated); + + // Alas, no forward_iterator sort yet, so make a strict (non-sentinel) + // range to sort on below + + // simple sort + util::sort(util::strict_view(cstr_range)); + EXPECT_EQ("dhowy"s, cstr); + + // reverse sort by transform c to -c + util::sort_by(util::strict_view(cstr_range), [](char c) { return -c; }); + EXPECT_EQ("ywohd"s, cstr); + + // stable sort: move capitals to front, numbers to back + auto rank = [](char c) { + return std::isupper(c)? 0: std::isdigit(c)? 2: 1; + }; + + char mixed[] = "t5hH4E3erLL2e1O"; + auto mixed_range = util::make_range(std::begin(mixed), null_terminated); + + util::stable_sort_by(util::strict_view(mixed_range), rank); + EXPECT_EQ("HELLOthere54321"s, mixed); + + + // sort with user-provided less comparison function + + std::vector X = {{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}}; + + util::sort(X, [](const foo& l, const foo& r) {return l.y{{5, 0}, {4, 1}, {3, 2}, {2, 3}, {1, 4}, {0, 5}})); + util::sort(X, [](const foo& l, const foo& r) {return l.x{{0, 5}, {1, 4}, {2, 3}, {3, 2}, {4, 1}, {5, 0}})); } TEST(range, sum) { diff --git a/test/unit/test_s_expr.cpp b/test/unit/test_s_expr.cpp index c45701fe4..f08607ce5 100644 --- a/test/unit/test_s_expr.cpp +++ b/test/unit/test_s_expr.cpp @@ -235,10 +235,11 @@ std::string round_trip_network_value(const char* in) { } } + TEST(cv_policies, round_tripping) { auto literals = {"(every-segment (tag 42))", - "(fixed-per-branch 23 (segment 0) (flag-interior-forks))", - "(max-extent 23.1 (segment 0) (flag-interior-forks))", + "(fixed-per-branch 23 (segment 0) 1)", + "(max-extent 23.1 (segment 0) 1)", "(single (segment 0))", "(explicit (terminal) (segment 0))", "(join (every-segment (tag 42)) (single (segment 0)))", @@ -251,8 +252,8 @@ TEST(cv_policies, round_tripping) { TEST(cv_policies, literals) { EXPECT_NO_THROW("(every-segment (tag 42))"_cvp); - EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) (flag-interior-forks))"_cvp); - EXPECT_NO_THROW("(max-extent 23.1 (segment 0) (flag-interior-forks))"_cvp); + EXPECT_NO_THROW("(fixed-per-branch 23 (segment 0) 1)"_cvp); + EXPECT_NO_THROW("(max-extent 23.1 (segment 0) 1)"_cvp); EXPECT_NO_THROW("(single (segment 0))"_cvp); EXPECT_NO_THROW("(explicit (terminal) (segment 0))"_cvp); EXPECT_NO_THROW("(join (every-segment (tag 42)) (single (segment 0)))"_cvp); @@ -859,7 +860,8 @@ TEST(decor_literals, round_tripping) { "(scaled-mechanism (density (mechanism \"pas\" (\"g\" 0.02))) (\"g\" (exp (add (radius 2.1) (scalar 3.2)))))", }; auto default_literals = { - "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))" + "(ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\"))", + "(cv-policy (single (segment 0)))" }; auto place_literals = { "(current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 10 0.25)", @@ -921,7 +923,8 @@ TEST(decor_expressions, round_tripping) { "(default (ion-internal-concentration \"ca\" 5 (scalar 75.1)))", "(default (ion-external-concentration \"h\" 6 (scalar -50.1)))", "(default (ion-reversal-potential \"na\" 7 (scalar 30)))", - "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))" + "(default (ion-reversal-potential-method \"ca\" (mechanism \"nernst/ca\")))", + "(default (cv-policy (max-extent 2 (region \"soma\") 2)))" }; auto decorate_place_literals = { "(place (location 3 0.2) (current-clamp (envelope (10 0.5) (110 0.5) (110 0)) 0.5 0.25) \"clamp\")", @@ -1001,6 +1004,11 @@ TEST(decor, round_tripping) { " (default \n" " (ion-reversal-potential-method \"na\" \n" " (mechanism \"nernst\")))\n" + " (default \n" + " (cv-policy \n" + " (fixed-per-branch 10 \n" + " (all)\n" + " 1)))\n" " (paint \n" " (region \"dend\")\n" " (density \n" @@ -1266,11 +1274,7 @@ TEST(cable_cell, round_tripping) { " (110.000000 0.500000)\n" " (110.000000 0.000000))\n" " 0.000000 0.000000)\n" - " \"iclamp\"))\n" - " (cv-policy \n" - " (fixed-per-branch 10 \n" - " (all)\n" - " (flag-interior-forks)))))"; + " \"iclamp\"))))"; EXPECT_EQ(component_str, round_trip_component(component_str.c_str())); diff --git a/test/unit/test_sde.cpp b/test/unit/test_sde.cpp index e0f097db9..ed9b1a1ef 100644 --- a/test/unit/test_sde.cpp +++ b/test/unit/test_sde.cpp @@ -254,13 +254,15 @@ class simple_sde_recipe: public simple_recipe_base { // set cvs explicitly double const cv_size = 1.0; + dec.set_default(cv_policy_max_extent(cv_size)); + // generate cells unsigned const n1 = ncvs/2; for (unsigned int i=0; ib; diff --git a/test/unit/test_spikes.cpp b/test/unit/test_spikes.cpp index cceb3d20d..031b9bce8 100644 --- a/test/unit/test_spikes.cpp +++ b/test/unit/test_spikes.cpp @@ -222,11 +222,12 @@ TEST(SPIKES_TEST_CLASS, threshold_watcher_interpolation) { for (unsigned i = 0; i < 8; i++) { arb::decor decor; + decor.set_default(arb::cv_policy_every_segment()); decor.place("mid"_lab, arb::threshold_detector{10*arb::units::mV}, "detector"); decor.place("mid"_lab, arb::i_clamp::box(0.01*arb::units::ms + i*dt, duration, 0.5*arb::units::nA), "clamp"); decor.place("mid"_lab, arb::synapse("expsyn"), "synapse"); - arb::cable_cell cell(morpho, decor, dict, arb::cv_policy_every_segment()); + arb::cable_cell cell(morpho, decor, dict); cable1d_recipe rec({cell}); auto decomp = arb::partition_load_balance(rec, context);