From a4b07b788c3af27a6f4e87bf4373a0b8dc1ccdca Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Mon, 28 Aug 2023 22:33:47 +0200 Subject: [PATCH 01/20] Add reverse addition operation to NodeCollection --- pynest/nest/lib/hl_api_types.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/pynest/nest/lib/hl_api_types.py b/pynest/nest/lib/hl_api_types.py index 27ab915547..137398c080 100644 --- a/pynest/nest/lib/hl_api_types.py +++ b/pynest/nest/lib/hl_api_types.py @@ -23,8 +23,13 @@ Classes defining the different PyNEST types """ -from ..ll_api import sli_func, sps, sr, spp, take_array_index +import json +from math import floor, log + +import numpy + from .. import pynestkernel as kernel +from ..ll_api import sli_func, spp, sps, sr, take_array_index from .hl_api_helper import ( broadcast, get_parameters, @@ -35,10 +40,6 @@ ) from .hl_api_simulation import GetKernelStatus -import numpy -import json -from math import floor, log - try: import pandas @@ -220,6 +221,11 @@ def __add__(self, other): return sli_func("join", self._datum, other._datum) + def __radd__(self, other): + if other == 0: + other = NodeCollection() + return sli_func("join", other._datum, self._datum) + def __getitem__(self, key): if isinstance(key, slice): if key.start is None: From a64fc1ecfdafe84a0199327c393c280e54af99dd Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Mon, 28 Aug 2023 23:24:34 +0200 Subject: [PATCH 02/20] More flexible NodeCollection concatenation --- pynest/nest/lib/hl_api_types.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/pynest/nest/lib/hl_api_types.py b/pynest/nest/lib/hl_api_types.py index 137398c080..a8bc92c4d0 100644 --- a/pynest/nest/lib/hl_api_types.py +++ b/pynest/nest/lib/hl_api_types.py @@ -217,14 +217,25 @@ def __iter__(self): def __add__(self, other): if not isinstance(other, NodeCollection): - raise NotImplementedError() + if other == 0: + other = NodeCollection() + else: + raise NotImplementedError() return sli_func("join", self._datum, other._datum) def __radd__(self, other): if other == 0: other = NodeCollection() - return sli_func("join", other._datum, self._datum) + + if not isinstance(other, NodeCollection): + raise TypeError( + "A 'NodeCollection' can only be concatenated with " + "another 'NodeCollection', you passed an object of " + f"type '{type(other).__name__}'" + ) + + return sli_func("join", self._datum, other._datum) def __getitem__(self, key): if isinstance(key, slice): From dc2b9a75a9d90beb5a8a229155b79d8e81623c29 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Wed, 30 Aug 2023 13:31:34 +0200 Subject: [PATCH 03/20] WIP [skip ci] --- pynest/nest/lib/hl_api_types.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pynest/nest/lib/hl_api_types.py b/pynest/nest/lib/hl_api_types.py index a8bc92c4d0..95c0a8c1f9 100644 --- a/pynest/nest/lib/hl_api_types.py +++ b/pynest/nest/lib/hl_api_types.py @@ -24,6 +24,7 @@ """ import json +import numbers from math import floor, log import numpy @@ -217,13 +218,20 @@ def __iter__(self): def __add__(self, other): if not isinstance(other, NodeCollection): - if other == 0: + if isinstance(other, numbers.Number) and other == 0: + other = NodeCollection() + else: + raise TypeError(f"Cannot add object of type '{type(other).__name__}' to 'NodeCollection'") + """ + if isinstance(other, int) and other == 0: other = NodeCollection() else: raise NotImplementedError() + """ return sli_func("join", self._datum, other._datum) + """ def __radd__(self, other): if other == 0: other = NodeCollection() @@ -236,6 +244,10 @@ def __radd__(self, other): ) return sli_func("join", self._datum, other._datum) + """ + + def __radd__(self, other): + return self + other def __getitem__(self, key): if isinstance(key, slice): From 7de693130cbd972239ec12a541a7a74fd01a1bfe Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Wed, 30 Aug 2023 13:41:12 +0200 Subject: [PATCH 04/20] Improve add methods --- pynest/nest/lib/hl_api_types.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/pynest/nest/lib/hl_api_types.py b/pynest/nest/lib/hl_api_types.py index 95c0a8c1f9..843f17611b 100644 --- a/pynest/nest/lib/hl_api_types.py +++ b/pynest/nest/lib/hl_api_types.py @@ -222,30 +222,9 @@ def __add__(self, other): other = NodeCollection() else: raise TypeError(f"Cannot add object of type '{type(other).__name__}' to 'NodeCollection'") - """ - if isinstance(other, int) and other == 0: - other = NodeCollection() - else: - raise NotImplementedError() - """ return sli_func("join", self._datum, other._datum) - """ - def __radd__(self, other): - if other == 0: - other = NodeCollection() - - if not isinstance(other, NodeCollection): - raise TypeError( - "A 'NodeCollection' can only be concatenated with " - "another 'NodeCollection', you passed an object of " - f"type '{type(other).__name__}'" - ) - - return sli_func("join", self._datum, other._datum) - """ - def __radd__(self, other): return self + other From 23713f538d478730935d992b025407a1acc44ef8 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Thu, 31 Aug 2023 13:38:58 +0200 Subject: [PATCH 05/20] Start migrating node collection tests --- testsuite/pytests/test_NodeCollection.py | 22 +----- .../test_node_collection_operations.py | 79 +++++++++++++++++++ 2 files changed, 80 insertions(+), 21 deletions(-) create mode 100644 testsuite/pytests/test_node_collection_operations.py diff --git a/testsuite/pytests/test_NodeCollection.py b/testsuite/pytests/test_NodeCollection.py index 77d3bed536..596e966e3f 100644 --- a/testsuite/pytests/test_NodeCollection.py +++ b/testsuite/pytests/test_NodeCollection.py @@ -24,6 +24,7 @@ """ import unittest + import nest try: @@ -92,27 +93,6 @@ def test_NodeCollection_to_numpy(self): self.assertEqual(arr[start : start + n_neurons].tolist(), nc.tolist()) - def test_equal(self): - """Equality of NodeCollections""" - - n = nest.Create("iaf_psc_exp", 10) - n_list = n.tolist() - - nest.ResetKernel() - - n_new = nest.Create("iaf_psc_exp", 10) - new_list = n_new.tolist() - self.assertEqual(n_list, new_list) - self.assertEqual(n, n_new) - - nest.ResetKernel() - - nc = nest.Create("iaf_psc_alpha", 10) - ngc = nest.NodeCollection(nc.tolist()) - self.assertEqual(nc, ngc) - - self.assertNotEqual(nc, n) - def test_indexing(self): """Index of NodeCollections""" diff --git a/testsuite/pytests/test_node_collection_operations.py b/testsuite/pytests/test_node_collection_operations.py new file mode 100644 index 0000000000..79d2150b08 --- /dev/null +++ b/testsuite/pytests/test_node_collection_operations.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- +# +# test_node_collection_operations.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Test basic operations with ``NodeCollection``. +""" + +import numpy as np +import pytest + +import nest + + +@pytest.fixture(autouse=True) +def reset(): + nest.ResetKernel() + + +def test_node_collection_equal(): + """Test equality of NodeCollections.""" + + nc_exp_1 = nest.Create("iaf_psc_exp", 10) + nc_exp_list_1 = nc_exp_1.tolist() + + nest.ResetKernel() + + nc_exp_2 = nest.Create("iaf_psc_exp", 10) + nc_exp_list_2 = nc_exp_2.tolist() + + assert nc_exp_1 == nc_exp_2 + assert nc_exp_list_1 == nc_exp_list_2 + + nest.ResetKernel() + + nc_alpha_1 = nest.Create("iaf_psc_alpha", 10) + nc_alpha_2 = nest.NodeCollection(nc_alpha_1.tolist()) + + assert nc_alpha_1 == nc_alpha_2 + assert nc_alpha_1 != nc_exp_1 + + +def test_node_collection_indexing(): + """Test indexing of NodeCollections.""" + + nc = nest.Create("iaf_psc_alpha", 5) + nc_0 = nest.NodeCollection([1]) + nc_2 = nest.NodeCollection([3]) + nc_4 = nest.NodeCollection([5]) + + assert nc[0] == nc_0 + assert nc[2] == nc_2 + assert nc[4] == nc_4 + assert nc[-1] == nc_4 + assert nc[-3] == nc_2 + assert nc[-5] == nc_0 + + with pytest.raises(IndexError): + nc[5] + + with pytest.raises(IndexError): + nc[-6] From 2dc1da6575167b06ea98d077a08e73a0ffca02cc Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 01:54:04 +0200 Subject: [PATCH 06/20] Refactor GetConnections tests from test_NodeCollection.py --- testsuite/pytests/test_get_connections.py | 145 ++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 testsuite/pytests/test_get_connections.py diff --git a/testsuite/pytests/test_get_connections.py b/testsuite/pytests/test_get_connections.py new file mode 100644 index 0000000000..df2005730f --- /dev/null +++ b/testsuite/pytests/test_get_connections.py @@ -0,0 +1,145 @@ +# -*- coding: utf-8 -*- +# +# test_get_connections.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Tests for ``GetConnections``. + +NOTE: This is supposed to be the pytest conversions of unittest tests in +test_getconnections.py and other GetConnections tests that may be placed +elsewhere. +""" + +import pandas as pd +import pandas.testing as pdtest +import pytest + +import nest + + +@pytest.fixture(autouse=True) +def reset(): + nest.ResetKernel() + + +def test_get_connections_all_with_node_collection(): + """ + Test that ``GetConnections`` works with ``NodeCollection``. + """ + + nodes = nest.Create("iaf_psc_alpha", 3) + nest.Connect(nodes, nodes) + + conns_no_args = nest.GetConnections() + conns_nodes_args = nest.GetConnections(nodes, nodes) + + assert conns_nodes_args == conns_no_args + + +def test_get_connections_with_node_collection_step(): + """ + Test that ``GetConnections`` works with ``NodeCollection`` sliced in step. + """ + + nodes = nest.Create("iaf_psc_alpha", 3) + nest.Connect(nodes, nodes) + + conns = nest.GetConnections(nodes[::2]) + actual_sources = conns.get("source") + actual_targets = conns.get("target") + + expected_sources = [1, 1, 1, 3, 3, 3] + expected_targets = [1, 2, 3, 1, 2, 3] + + assert actual_sources == expected_sources + assert actual_targets == expected_targets + + +def test_get_connections_with_sliced_node_collection(): + """Test that ``GetConnections`` works with sliced ``NodeCollection``.""" + + nodes = nest.Create("iaf_psc_alpha", 11) + nest.Connect(nodes, nodes) + + conns = nest.GetConnections(nodes[1:9:3]) + actual_sources = conns.get("source") + + expected_sources = [2] * 11 + [5] * 11 + [8] * 11 + assert actual_sources == expected_sources + + +def test_get_connections_bad_source_raises(): + """Test that ``GetConnections`` raises an error when called with 0.""" + + nodes = nest.Create("iaf_psc_alpha", 3) + nest.Connect(nodes, nodes) + + with pytest.raises(TypeError): + nest.GetConnections([0, 1]) + + +def test_get_connections_correct_table_with_node_collection_step(): + """ + Test that ``GetConnections`` table from ``NodeCollection`` sliced in step match expectations. + """ + + nodes = nest.Create("iaf_psc_alpha", 3) + nest.Connect(nodes, nodes) + + conns = nest.GetConnections(nodes[::2]) + actual_row = [ + conns.get("source")[3], + conns.get("target")[3], + conns.get("target_thread")[3], + conns.get("synapse_id")[3], + conns.get("port")[3], + ] + + expected_syn_id = nest.GetDefaults("static_synapse", "synapse_modelid") + expected_row = [3, 1, 0, expected_syn_id, 6] + + assert actual_row == expected_row + + +def test_get_connections_correct_table_with_node_collection_index(): + """ + Test that ``GetConnections`` table from ``NodeCollection`` index match expectations. + """ + + nodes = nest.Create("iaf_psc_alpha", 3) + nest.Connect(nodes, nodes) + + print(nodes[0], type(nodes[0])) + + actual_conns = pd.DataFrame( + nest.GetConnections(nodes[0]).get(["source", "target", "target_thread", "synapse_id", "port"]) + ) + + expected_syn_id = nest.GetDefaults("static_synapse", "synapse_modelid") + expected_conns = pd.DataFrame( + [ + [1, 1, 0, expected_syn_id, 0], + [1, 2, 0, expected_syn_id, 1], + [1, 3, 0, expected_syn_id, 2], + ], + columns=["source", "target", "target_thread", "synapse_id", "port"], + ) + + pdtest.assert_frame_equal(actual_conns, expected_conns) From 23325926ef490038763d1cddef88e1c58de215e1 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 12:54:41 +0200 Subject: [PATCH 07/20] Refactor parameter apply tests from test_NodeCollection.py --- .../test_spatial/test_parameter_apply.py | 116 ++++++++++++++++++ 1 file changed, 116 insertions(+) create mode 100644 testsuite/pytests/test_spatial/test_parameter_apply.py diff --git a/testsuite/pytests/test_spatial/test_parameter_apply.py b/testsuite/pytests/test_spatial/test_parameter_apply.py new file mode 100644 index 0000000000..897c683155 --- /dev/null +++ b/testsuite/pytests/test_spatial/test_parameter_apply.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- +# +# test_parameter_apply.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Test ``Parameter`` ``apply`` method for spatial ``NodeCollection``. +""" + +import numpy as np +import pytest + +import nest + + +@pytest.fixture(autouse=True) +def reset(): + nest.ResetKernel() + + +def test_parameter_apply_given_node_collection(): + """Test parameter apply with just passing the ``NodeCollection``.""" + + nc = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) + param = nest.spatial.pos.x + ref_positions = np.array(nest.GetPosition(nc)) + + assert param.apply(nc) == tuple(ref_positions[:, 0]) + assert param.apply(nc[0]) == (ref_positions[0, 0],) + assert param.apply(nc[::2]) == tuple(ref_positions[::2, 0]) + + # raises for non-existing dimension + with pytest.raises(nest.kernel.NESTError): + nest.spatial.pos.z.apply(nc) + + +def test_parameter_apply_given_node_collection_and_single_target_position(): + """Test parameter apply with passing the ``NodeCollection`` and single target position.""" + + nc = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) + param = nest.spatial.distance + + # Single target position + target = [ + [1.0, 2.0], + ] + + for source in nc: + source_x, source_y = nest.GetPosition(source) + target_x, target_y = (target[0][0], target[0][1]) + ref_distance = np.sqrt((target_x - source_x) ** 2 + (target_y - source_y) ** 2) + + assert param.apply(source, target) == ref_distance + + +def test_parameter_apply_given_node_collection_and_multiple_target_positions(): + """Test parameter apply with passing the ``NodeCollection`` and multiple target positions.""" + + nc = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) + param = nest.spatial.distance + + # Multiple target positions + targets = np.array(nest.GetPosition(nc)) + + for source in nc: + source_x, source_y = nest.GetPosition(source) + ref_distances = np.sqrt((targets[:, 0] - source_x) ** 2 + (targets[:, 1] - source_y) ** 2) + + assert param.apply(source, list(targets)) == tuple(ref_distances) + + +def test_parameter_apply_source_multiple_node_ids_raises(): + """Test parameter apply with passing source ``NodeCollection`` with multiple node IDs raises.""" + + source = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) + target = [[1.0, 2.0]] + param = nest.spatial.distance + + with pytest.raises(ValueError): + param.apply(source, target) + + +def test_parameter_apply_erroneous_position_specification(): + """Test parameter apply with erroneous position specification raises an error.""" + + nc = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) + source = nc[0] + param = nest.spatial.distance + + # Too many dimensions + with pytest.raises(nest.kernel.NESTError): + param.apply(source, [[1.0, 2.0, 3.0]]) + + # Not a list of lists + with pytest.raises(TypeError): + param.apply(source, [1.0, 2.0]) + + # Not consistent dimensions + with pytest.raises(ValueError): + param.apply(source, [[1.0, 2.0], [1.0, 2.0, 3.0]]) From 14732d83307a9e518bd5c959ee70d73598684937 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 12:55:49 +0200 Subject: [PATCH 08/20] Move get/set tests from test_NodeCollection.py --- testsuite/pytests/test_get_set.py | 52 ++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/testsuite/pytests/test_get_set.py b/testsuite/pytests/test_get_set.py index 1f6deb4bcb..4de6a7bc5f 100644 --- a/testsuite/pytests/test_get_set.py +++ b/testsuite/pytests/test_get_set.py @@ -23,9 +23,10 @@ NodeCollection get/set tests """ +import json import unittest + import nest -import json try: import numpy as np @@ -131,6 +132,55 @@ def test_get(self): } self.assertEqual(g, g_reference) + def test_SetStatus_and_GetStatus(self): + """ + Test that SetStatus and GetStatus works as expected with + NodeCollection + + NOTE: This test was moved from test_NodeCollection.py and may overlap + with test already present in this test suite. If that is the case, + consider to just drop this test. + """ + + num_nodes = 10 + n = nest.Create("iaf_psc_alpha", num_nodes) + nest.SetStatus(n, {"V_m": 3.5}) + self.assertEqual(nest.GetStatus(n, "V_m")[0], 3.5) + + V_m = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] + nest.SetStatus(n, "V_m", V_m) + for i in range(num_nodes): + self.assertEqual(nest.GetStatus(n, "V_m")[i], V_m[i]) + + with self.assertRaises(TypeError): + nest.SetStatus(n, [{"V_m": 34.0}, {"V_m": -5.0}]) + + nest.ResetKernel() + + nc = nest.Create("iaf_psc_exp", 5) # noqa: F841 + + with self.assertRaises(nest.kernel.NESTError): + nest.SetStatus(n, {"V_m": -40.0}) + with self.assertRaises(nest.kernel.NESTError): + nest.GetStatus(n) + + nest.ResetKernel() + n = nest.Create("iaf_psc_alpha", 3) + nest.SetStatus(n, [{"V_m": 10.0}, {"V_m": -10.0}, {"V_m": -20.0}]) + self.assertEqual(nest.GetStatus(n, "V_m"), (10.0, -10.0, -20.0)) + + def test_set_on_empty_node_collection(self): + """ + Checks that setting on empty NC does not raise an error. + + NOTE: This test was moved from test_NodeCollection.py and may overlap + with test already present in this test suite. If that is the case, + consider to just drop this test. + """ + + for empty_nc in [nest.NodeCollection(), nest.NodeCollection([])]: + self.assertIsNone(empty_nc.set()) + def test_get_sliced(self): """ Test that get works on sliced NodeCollections From e19a51445f704e0d4351f2463f3635244a53d9a1 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 12:56:27 +0200 Subject: [PATCH 09/20] Refactor connect tests from test_NodeCollection.py --- .../pytests/test_connect_node_collection.py | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 testsuite/pytests/test_connect_node_collection.py diff --git a/testsuite/pytests/test_connect_node_collection.py b/testsuite/pytests/test_connect_node_collection.py new file mode 100644 index 0000000000..da323fef9c --- /dev/null +++ b/testsuite/pytests/test_connect_node_collection.py @@ -0,0 +1,71 @@ +# -*- coding: utf-8 -*- +# +# test_connect_node_collection.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Test basic connection with ``NodeCollection``. +""" + +import pytest + +import nest + + +@pytest.fixture(autouse=True) +def reset(): + nest.ResetKernel() + + +def test_connect_node_collection(): + """Test that ``Connect`` works with ``NodeCollection``.""" + + nc = nest.Create("iaf_psc_exp", 10) + nest.Connect(nc, nc, {"rule": "one_to_one"}) + + assert nest.num_connections == 10 + + for node in nc: + nest.Connect(node, node) + + assert nest.num_connections == 20 + + +def test_connect_node_collection_index(): + """Test that ``Connect`` works with indexed ``NodeCollection``.""" + + nc = nest.Create("iaf_psc_alpha", 2) + nest.Connect(nc[0], nc[1]) + assert nest.num_connections == 1 + + +@pytest.mark.parametrize("empty_nc", [nest.NodeCollection(), nest.NodeCollection([])]) +def test_connect_empty_node_collection_raises(empty_nc): + """Test that ``Connect`` with empty ``NodeCollection`` raises an error.""" + + nc = nest.Create("iaf_psc_alpha", 5) + + with pytest.raises(nest.kernel.NESTErrors.IllegalConnection): + nest.Connect(nc, empty_nc) + + with pytest.raises(nest.kernel.NESTErrors.IllegalConnection): + nest.Connect(empty_nc, nc) + + with pytest.raises(nest.kernel.NESTErrors.IllegalConnection): + nest.Connect(empty_nc, empty_nc) From 5de50e4b749af00e69bdc7299fbcb05895b8e09a Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 12:57:08 +0200 Subject: [PATCH 10/20] Move create tests from test_NodeCollection.py --- testsuite/pytests/test_create.py | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/testsuite/pytests/test_create.py b/testsuite/pytests/test_create.py index e169c75b43..235c2e5935 100644 --- a/testsuite/pytests/test_create.py +++ b/testsuite/pytests/test_create.py @@ -25,6 +25,7 @@ import unittest import warnings + import nest @@ -50,6 +51,26 @@ def test_ModelCreateN(self): nodes = nest.Create(model, num_nodes) self.assertEqual(len(nodes), num_nodes) + def test_correct_node_collection_model_created(self): + """ + Ensure that the correct model is created for node in ``NodeCollection``. + + NOTE: This test was moved from test_NodeCollection.py and may overlap + with test already present in this test suite. If that is the case, + consider to just drop this test. + """ + + models = nest.node_models + nc = nest.NodeCollection() + + for model in models: + nc += nest.Create(model) + + self.assertTrue(len(nc) > 0) + + for i, node in enumerate(nc): + self.assertEqual(node.model, models[i]) + def test_ModelCreateNdict(self): """Model Creation with N and dict""" @@ -59,6 +80,16 @@ def test_ModelCreateNdict(self): self.assertEqual(nest.GetStatus(n, "V_m"), (voltage,) * num_nodes) + def test_Create_accepts_empty_params_dict(self): + """ + Create with empty parameter dictionary + + NOTE: This test was moved from test_NodeCollection.py and may overlap + with test already present in this test suite. If that is the case, + consider to just drop this test. + """ + nest.Create("iaf_psc_delta", params={}) + def test_erroneous_param_to_create(self): """Erroneous param to Create raises exception""" num_nodes = 3 From 3b9aeee15ee4bcbd08fa851f63b35f5aadda45f5 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 12:58:47 +0200 Subject: [PATCH 11/20] Refactor to and from object tests from test_NodeCollection.py --- .../test_node_collection_to_from_object.py | 112 ++++++++++++++++++ 1 file changed, 112 insertions(+) create mode 100644 testsuite/pytests/test_node_collection_to_from_object.py diff --git a/testsuite/pytests/test_node_collection_to_from_object.py b/testsuite/pytests/test_node_collection_to_from_object.py new file mode 100644 index 0000000000..354b72bf7c --- /dev/null +++ b/testsuite/pytests/test_node_collection_to_from_object.py @@ -0,0 +1,112 @@ +# -*- coding: utf-8 -*- +# +# test_node_collection_to_from_object.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Test ``NodeCollection`` to and from object functionality. +""" + + +import numpy as np +import numpy.testing as nptest +import pytest + +import nest + + +@pytest.fixture(autouse=True) +def reset(): + nest.ResetKernel() + + +def test_node_collection_to_list(): + """Test conversion from ``NodeCollection`` to ``list``.""" + + N = 10 + nc = nest.Create("iaf_psc_alpha", N) + assert nc.tolist() == list(range(1, N + 1)) + + +def test_node_collection_from_list_no_created_nodes_raises(): + """Ensure exception if creating ``NodeCollection`` from ``list`` without creating nodes first.""" + + node_ids_in = [5, 10, 15, 20] + with pytest.raises(nest.kernel.NESTErrors.UnknownNode): + nc = nest.NodeCollection(node_ids_in) + + +def test_primitive_node_collection_from_list(): + """Test conversion from ``list`` to primitive ``NodeCollection``.""" + + nest.Create("iaf_psc_alpha", 10) + node_ids_in = list(range(2, 8)) + nc = nest.NodeCollection(node_ids_in) + assert nc.tolist() == node_ids_in + + +def test_composite_node_collection_from_list(): + """Test conversion from ``list`` to composite ``NodeCollection``.""" + + # Creating composite NodeCollection from list + nest.Create("iaf_psc_alpha", 20) + node_ids_in = [5, 10, 15, 20] + nc = nest.NodeCollection(node_ids_in) + for node, expected_node_id in zip(nc, node_ids_in): + assert node.global_id == expected_node_id + + +def test_node_collection_to_numpy_direct(): + """Test conversion from ``NodeCollection`` to NumPy array.""" + + N = 10 + nc = nest.Create("iaf_psc_alpha", N) + + # direct array conversion + nc_arr = np.array(nc) + + expected_arr = np.linspace(1, N, N, dtype=int) + nptest.assert_array_equal(nc_arr, expected_arr) + + +def test_node_collection_to_numpy_bigger_array(): + """Test conversion from ``NodeCollection`` to NumPy array twice the size.""" + + N = 10 + nc = nest.Create("iaf_psc_alpha", N) + + # incorporation to bigger array + arr = np.zeros(2 * N, dtype=int) + start = 2 + arr[start : start + N] = nc + + expected_arr = np.array([0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 0, 0, 0, 0, 0, 0, 0, 0]) + nptest.assert_array_equal(arr, expected_arr) + + +def test_node_collection_from_unsorted_list_raises(): + """Test that creating a ``NodeCollection`` from an unsorted ``list`` raises error.""" + + nest.Create("iaf_psc_alpha", 10) + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nest.NodeCollection([5, 4, 6]) + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nest.NodeCollection([5, 6, 4]) From 174f0cdda00856ac18c5d15164fd85ce9c8a6f7b Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 12:59:35 +0200 Subject: [PATCH 12/20] Refactor indexing and slicing test from test_NodeCollection.py --- .../test_node_collection_indexing_slicing.py | 274 ++++++++++++++++++ 1 file changed, 274 insertions(+) create mode 100644 testsuite/pytests/test_node_collection_indexing_slicing.py diff --git a/testsuite/pytests/test_node_collection_indexing_slicing.py b/testsuite/pytests/test_node_collection_indexing_slicing.py new file mode 100644 index 0000000000..b27d2cc813 --- /dev/null +++ b/testsuite/pytests/test_node_collection_indexing_slicing.py @@ -0,0 +1,274 @@ +# -*- coding: utf-8 -*- +# +# test_node_collection_indexing_slicing.py +# +# This file is part of NEST. +# +# Copyright (C) 2004 The NEST Initiative +# +# NEST is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 of the License, or +# (at your option) any later version. +# +# NEST is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with NEST. If not, see . + +""" +Test ``NodeCollection`` indexing and slicing. +""" + + +import numpy as np +import pytest + +import nest + + +@pytest.fixture(autouse=True) +def reset(): + nest.ResetKernel() + + +def test_node_collection_indexing(): + """Test ``NodeCollection`` indexing.""" + + nc = nest.Create("iaf_psc_alpha", 5) + nc_0 = nest.NodeCollection([1]) + nc_2 = nest.NodeCollection([3]) + nc_4 = nest.NodeCollection([5]) + + assert nc[0] == nc_0 + assert nc[2] == nc_2 + assert nc[4] == nc_4 + assert nc[-1] == nc_4 + assert nc[-3] == nc_2 + assert nc[-5] == nc_0 + + with pytest.raises(IndexError): + nc[5] + + with pytest.raises(IndexError): + nc[-6] + + +def test_node_collection_index_method(): + """ + Test ``NodeCollection`` index method. + + Test NodeCollection index against list index, and that elements specified + in inverse reference are not found. + """ + + N_alpha = 10 + N_exp = 5 + N_tot = N_alpha + N_exp + start = 3 + stop = 12 + step = 3 + + # case 1: primitive NodeCollection + prim_nc = nest.Create("iaf_psc_alpha", N_alpha) + prim_inv_ref = [] + + # case 2: composite NodeCollection + comp_nc = prim_nc + nest.Create("iaf_psc_exp", N_exp) + comp_inv_ref = [] + + # case 3: sliced NodeCollection + sliced_nc = comp_nc[start:stop] + sliced_inv_ref = list(range(1, N_tot)) + del sliced_inv_ref[start:stop] + + # case 4: NodeCollection with step + step_nc = comp_nc[::step] + step_inv_ref = list(range(1, N_tot)) + del step_inv_ref[::step] + + # case 5: sliced NodeCollection with step + sliced_step_nc = comp_nc[start:stop:step] + sliced_step_inv_ref = list(range(1, N_tot)) + del sliced_step_inv_ref[start:stop:step] + + # concatenate cases + ncs = [prim_nc, comp_nc, sliced_nc, step_nc, sliced_step_nc] + inv_refs = [prim_inv_ref, comp_inv_ref, sliced_inv_ref, step_inv_ref, sliced_step_inv_ref] + + # iterate and test the different cases + for nc, inv_ref in zip(ncs, inv_refs): + nc_list = nc.tolist() + + for i in nc_list: + assert nc.index(i) == nc_list.index(i) + + for j in inv_ref: + with pytest.raises(ValueError): + nc.index(j) + + with pytest.raises(ValueError): + nc.index(nc_list[-1] + 1) + + with pytest.raises(ValueError): + nc.index(0) + + with pytest.raises(ValueError): + nc.index(-1) + + +def test_node_collection_indexing_counter(): + """Test ``NodeCollection`` indexing with loop counter.""" + + nodes = nest.Create("iaf_psc_alpha", 10) + + counter = 1 + for node in nodes: + assert node.global_id == counter + counter += 1 + + for i in range(10): + nc = nest.NodeCollection([i + 1]) + assert nc == nodes[i] + + +def test_multiple_node_collection_call_correct_index(): + """Test that multiple ``NodeCollection`` calls give correct indexing.""" + + expected_begin = 1 + expected_end = 11 + for model in nest.node_models: + nc = nest.Create(model, 10) + expected_lst = list(range(expected_begin, expected_end)) + expected_begin += 10 + expected_end += 10 + assert nc.tolist() == expected_lst + + +def test_node_collection_slicing(): + """Test ``NodeCollection`` slicing.""" + + nc = nest.Create("iaf_psc_alpha", 10) + + assert nc[:5].tolist() == [1, 2, 3, 4, 5] + assert nc[2:7].tolist() == [3, 4, 5, 6, 7] + assert nc[::2].tolist() == [1, 3, 5, 7, 9] + assert nc[1:6:3].tolist() == [2, 5] + assert nc[5:].tolist() == [6, 7, 8, 9, 10] + assert nc[-4:].tolist() == [7, 8, 9, 10] + assert nc[:-3:].tolist() == [1, 2, 3, 4, 5, 6, 7] + assert nc[-7:-4].tolist() == [4, 5, 6] + + with pytest.raises(IndexError): + nc[-15:] + + with pytest.raises(IndexError): + nc[:15] + + with pytest.raises(IndexError): + nc[-13:17] + + with pytest.raises(IndexError): + nc[::-3] + + +def test_node_collection_primitive_composite_slicing(): + """Test primitive and composite ``NodeCollection`` slicing.""" + + nc_prim = nest.Create("iaf_psc_alpha", 5) + nc_comp = nc_prim + nest.Create("iaf_psc_exp") + + for nodes in [nc_prim, nc_comp]: + nc_list = nodes.tolist() + + # slice without arguments + assert nodes[:].tolist() == nc_list + assert nodes[::].tolist() == nc_list + + # slice with start value + for start in range(-len(nodes), len(nodes)): + assert nodes[start:].tolist() == nc_list[start:] + + # slice with stop value + for stop in range(-len(nodes) + 1, len(nodes) + 1): + if stop == 0: + continue # slicing an empty NodeCollection is not allowed. + assert nodes[:stop].tolist() == nc_list[:stop] + + # slice with step value + for step in range(1, len(nodes)): + assert nodes[::step].tolist() == nc_list[::step] + + # slice with start and step values + for start in range(-len(nodes), len(nodes)): + for step in range(1, len(nodes)): + assert nodes[start::step].tolist() == nc_list[start::step] + + # slice with start and step values + for stop in range(-len(nodes) + 1, len(nodes) + 1): + if stop == 0: + continue # slicing an empty NodeCollection is not allowed. + for step in range(1, len(nodes)): + assert nodes[:stop:step].tolist() == nc_list[:stop:step] + + # slice with start, stop and step values + for start in range(-len(nodes), len(nodes)): + for stop in range(start + 1, len(nodes) + 1): + if stop == 0 or (start < 0 and start + len(nodes) >= stop): + continue # cannot slice an empty NodeCollection, or use stop <= start. + for step in range(1, len(nodes)): + assert nodes[start:stop:step].tolist() == nc_list[start:stop:step] + + +def test_node_collection_slice_unsorted_raises(): + """Test that slicing ``NodeCollection`` with an unsorted list raises error.""" + + nc = nest.Create("iaf_psc_alpha", 10) + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nc[[6, 5, 4]] + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nc[[5, 4, 6]] + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nc[[5, 4, 6]] + + +def test_primitive_node_collection_correct_len(): + """Test that the ``len()`` function works as expected on primitive ``NodeCollection``.""" + + prim_nc = nest.Create("iaf_psc_alpha", 10) + assert len(prim_nc) == 10 + + +def test_composite_node_collection_correct_len(): + """Test that the ``len()`` function works as expected on composite ``NodeCollection``.""" + + comp_nc = nest.Create("iaf_psc_alpha", 10) + nest.Create("iaf_psc_exp", 7) + assert len(comp_nc) == 17 + + +def test_sliced_node_collection_correct_len(): + """Test that the ``len()`` function works as expected on sliced ``NodeCollection``.""" + + nc = nest.Create("iaf_psc_delta", 20) + assert len(nc[3:17:4]) == 4 + + +def test_node_collection_with_nonunique_nodes_raises(): + """Test that non-unique nodes in ``NodeCollection`` raises an error.""" + + nc = nest.Create("iaf_psc_alpha", 10) + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nc[1:3] + nc[2:5] + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nest.NodeCollection([2, 2]) + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + nest.NodeCollection([2]) + nest.NodeCollection([1, 2]) From ca5014f0a1f73b681a7103a8718bfb956ab3d7fa Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 13:00:14 +0200 Subject: [PATCH 13/20] Refactor operation tests from test_NodeCollection.py --- .../test_node_collection_operations.py | 116 +++++++++++++++--- 1 file changed, 99 insertions(+), 17 deletions(-) diff --git a/testsuite/pytests/test_node_collection_operations.py b/testsuite/pytests/test_node_collection_operations.py index 79d2150b08..3a5141828b 100644 --- a/testsuite/pytests/test_node_collection_operations.py +++ b/testsuite/pytests/test_node_collection_operations.py @@ -35,7 +35,7 @@ def reset(): def test_node_collection_equal(): - """Test equality of NodeCollections.""" + """Test ``NodeCollection`` equality.""" nc_exp_1 = nest.Create("iaf_psc_exp", 10) nc_exp_list_1 = nc_exp_1.tolist() @@ -57,23 +57,105 @@ def test_node_collection_equal(): assert nc_alpha_1 != nc_exp_1 -def test_node_collection_indexing(): - """Test indexing of NodeCollections.""" +def test_primitive_node_collection_add(): + """Test primitive ``NodeCollection`` addition.""" - nc = nest.Create("iaf_psc_alpha", 5) - nc_0 = nest.NodeCollection([1]) - nc_2 = nest.NodeCollection([3]) - nc_4 = nest.NodeCollection([5]) + nodes_a = nest.Create("iaf_psc_alpha", 2) + nodes_b = nest.Create("iaf_psc_alpha", 2) + prim_nc = nodes_a + nodes_b + assert prim_nc.tolist() == [1, 2, 3, 4] - assert nc[0] == nc_0 - assert nc[2] == nc_2 - assert nc[4] == nc_4 - assert nc[-1] == nc_4 - assert nc[-3] == nc_2 - assert nc[-5] == nc_0 - with pytest.raises(IndexError): - nc[5] +def test_node_collection_add(): + """Test""" - with pytest.raises(IndexError): - nc[-6] + n_nrns_a = 10 + n_nrns_b = 15 + n_nrns_c = 7 + n_nrns_d = 5 + n_nrns_a_b = n_nrns_a + n_nrns_b + n_nrns_a_b_c = n_nrns_a_b + n_nrns_c + + +def test_node_collection_iteration(): + """Test ``NodeCollection`` iteration.""" + + nc = nest.Create("iaf_psc_alpha", 15) + for i, node in enumerate(nc): + assert node == nc[i] + + +def test_node_collection_membership(): + """ + Test ``NodeCollection`` membership. + + Test that all node IDs in reference are in NodeCollection, and that + elements in inverse reference are not. + """ + + N_alpha = 10 + N_exp = 5 + N_tot = N_alpha + N_exp + start = 3 + stop = 12 + step = 3 + + # case 1: primitive NodeCollection + prim_nc = nest.Create("iaf_psc_alpha", N_alpha) + prim_ref = range(1, N_alpha + 1) + prim_inv_ref = [] + + # case 2: composite NodeCollection + comp_nc = prim_nc + nest.Create("iaf_psc_exp", N_exp) + comp_ref = range(1, N_tot + 1) + comp_inv_ref = [] + + # case 3: sliced NodeCollection + sliced_nc = comp_nc[start:stop] + sliced_ref = range(start + 1, stop + 1) + sliced_inv_ref = list(range(1, N_tot)) + del sliced_inv_ref[start:stop] + + # case 4: NodeCollection with step + step_nc = comp_nc[::step] + step_ref = range(1, N_tot + 1, step) + step_inv_ref = list(range(1, N_tot)) + del step_inv_ref[::step] + + # case 5: sliced NodeCollection with step + sliced_step_nc = comp_nc[start:stop:step] + sliced_step_ref = range(start + 1, stop + 1, step) + sliced_step_inv_ref = list(range(1, N_tot)) + del sliced_step_inv_ref[start:stop:step] + + # concatenate cases + ncs = [prim_nc, comp_nc, sliced_nc, step_nc, sliced_step_nc] + refs = [prim_ref, comp_ref, sliced_ref, step_ref, sliced_step_ref] + inv_refs = [prim_inv_ref, comp_inv_ref, sliced_inv_ref, step_inv_ref, sliced_step_inv_ref] + + # iterate and test the different cases + for nc, ref, inv_ref in zip(ncs, refs, inv_refs): + for node_id in ref: + assert node_id in nc + for node_id in inv_ref: + assert not node_id in nc + assert not ref[-1] + 1 in nc + assert not 0 in nc + assert not -1 in nc + + +def test_composite_node_collection_add_sliced_raises(): + """ + Test that an error is raised when trying to add a sliced composite and ``NodeCollection``. + """ + + # composite NodeCollection + comp_nc = nest.Create("iaf_psc_alpha", 10) + nest.Create("iaf_psc_exp", 7) + + # sliced composite + sliced_comp_nc = comp_nc[::2] + + nc = nest.Create("iaf_psc_delta") + + with pytest.raises(nest.kernel.NESTErrors.BadProperty): + sliced_comp_nc + nc From 9b78614e0452bb0fb7fe96b0eb7a62ef29e176fc Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 13:01:19 +0200 Subject: [PATCH 14/20] WIP: Convert unittest tests to pytest --- testsuite/pytests/test_NodeCollection.py | 577 +---------------------- 1 file changed, 6 insertions(+), 571 deletions(-) diff --git a/testsuite/pytests/test_NodeCollection.py b/testsuite/pytests/test_NodeCollection.py index 596e966e3f..4abc82d97a 100644 --- a/testsuite/pytests/test_NodeCollection.py +++ b/testsuite/pytests/test_NodeCollection.py @@ -42,203 +42,10 @@ class TestNodeCollection(unittest.TestCase): def setUp(self): nest.ResetKernel() - def test_NodeCollection_to_list(self): - """Conversion from NodeCollection to list""" - - n_neurons = 10 - n = nest.Create("iaf_psc_alpha", n_neurons) - n_list = n.tolist() - self.assertEqual(n_list, list(range(1, n_neurons + 1))) - - def test_list_to_NodeCollection(self): - """Conversion from list to NodeCollection""" - - # Creating NodeCollection from list without creating the nodes first - node_ids_in = [5, 10, 15, 20] - with self.assertRaises(nest.kernel.NESTError): - nc = nest.NodeCollection(node_ids_in) - - # Creating composite NodeCollection from list - nest.Create("iaf_psc_alpha", 20) - node_ids_in = [5, 10, 15, 20] - nc = nest.NodeCollection(node_ids_in) - for node_id, compare in zip(nc, node_ids_in): - self.assertEqual(node_id.global_id, compare) - - nest.ResetKernel() - - # Creating primitive NodeCollection from list - nest.Create("iaf_psc_alpha", 10) - node_ids_in = list(range(2, 8)) - nc = nest.NodeCollection(node_ids_in) - self.assertEqual(nc.tolist(), node_ids_in) - - def test_NodeCollection_to_numpy(self): - """Conversion from NodeCollection to NumPy array""" - if HAVE_NUMPY: - n_neurons = 10 - nc = nest.Create("iaf_psc_alpha", n_neurons) - - # direct array conversion - n_arr = np.array(nc) - - self.assertEqual(n_arr.tolist(), nc.tolist()) - - # incorporation to bigger array - arr = np.zeros(2 * n_neurons, dtype=int) - - start = 2 - - arr[start : start + n_neurons] = nc - - self.assertEqual(arr[start : start + n_neurons].tolist(), nc.tolist()) - - def test_indexing(self): - """Index of NodeCollections""" - - n = nest.Create("iaf_psc_alpha", 5) - nc_0 = nest.NodeCollection([1]) - nc_2 = nest.NodeCollection([3]) - nc_4 = nest.NodeCollection([5]) - - self.assertEqual(n[0], nc_0) - self.assertEqual(n[2], nc_2) - self.assertEqual(n[4], nc_4) - self.assertEqual(n[-1], nc_4) - self.assertEqual(n[-3], nc_2) - self.assertEqual(n[-5], nc_0) - with self.assertRaises(IndexError): - n[5] - with self.assertRaises(IndexError): - n[-6] - - nest.ResetKernel() - - nodes = nest.Create("iaf_psc_alpha", 10) - counter = 1 - for nc in nodes: - self.assertEqual(nc.get("global_id"), counter) - counter += 1 - for i in range(10): - nc = nest.NodeCollection([i + 1]) - self.assertEqual(nc, nodes[i]) - - def test_slicing(self): - """Slices of NodeCollections""" - - n = nest.Create("iaf_psc_alpha", 10) - n_slice = n[:5] - n_list = n_slice.tolist() - self.assertEqual(n_list, [1, 2, 3, 4, 5]) - - n_slice_middle = n[2:7] - n_list_middle = n_slice_middle.tolist() - self.assertEqual(n_list_middle, [3, 4, 5, 6, 7]) - - n_slice_skip = n[::2] - n_list_skip = n_slice_skip.tolist() - self.assertEqual(n_list_skip, [1, 3, 5, 7, 9]) - - n_slice_skip_part = n[1:6:3] - n_list_skip_part = n_slice_skip_part.tolist() - self.assertEqual(n_list_skip_part, [2, 5]) - - n_slice_end = n[5:] - n_list_end = n_slice_end.tolist() - self.assertEqual(n_list_end, [6, 7, 8, 9, 10]) - - n_slice_negative = n[-4:] - n_list_negative = n_slice_negative.tolist() - self.assertEqual(n_list_negative, [7, 8, 9, 10]) - - n_slice_negative_end = n[:-3:] - n_list_negative_end = n_slice_negative_end.tolist() - self.assertEqual(n_list_negative_end, [1, 2, 3, 4, 5, 6, 7]) - - n_slice_negative_start_end = n[-7:-4] - n_list_negative_start_end = n_slice_negative_start_end.tolist() - self.assertEqual(n_list_negative_start_end, [4, 5, 6]) - - with self.assertRaises(IndexError): - n[-15:] - - with self.assertRaises(IndexError): - n[:15] - - with self.assertRaises(IndexError): - n[-13:17] - - with self.assertRaises(IndexError): - n[::-3] - - primitive = n - composite = n + nest.Create("iaf_psc_exp") - for nodes in [primitive, composite]: - n_list = nodes.tolist() - # With slice without arguments - self.assertEqual(nodes[:].tolist(), n_list[:]) - self.assertEqual(nodes[::].tolist(), n_list[::]) - - # With start values - for start in range(-len(nodes), len(nodes)): - self.assertEqual(nodes[start:].tolist(), n_list[start:], f"with [{start}:]") - - # With stop values - for stop in range(-len(nodes) + 1, len(nodes) + 1): - if stop == 0: - continue # Slicing an empty NodeCollection is not allowed. - self.assertEqual(nodes[:stop].tolist(), n_list[:stop], f"with [:{stop}]") - - # With step values - for step in range(1, len(nodes)): - self.assertEqual(nodes[::step].tolist(), n_list[::step], f"with [::{step}]") - - # With start and step values - for start in range(-len(nodes), len(nodes)): - for step in range(1, len(nodes)): - self.assertEqual(nodes[start::step].tolist(), n_list[start::step], f"with [{start}::{step}]") - - # With stop and step values - for stop in range(-len(nodes) + 1, len(nodes) + 1): - if stop == 0: - continue # Slicing an empty NodeCollection is not allowed. - for step in range(1, len(nodes)): - self.assertEqual(nodes[:stop:step].tolist(), n_list[:stop:step], f"with [:{stop}:{step}]") - - # With start, stop, and step values - for start in range(-len(nodes), len(nodes)): - for stop in range(start + 1, len(nodes) + 1): - if stop == 0 or (start < 0 and start + len(nodes) >= stop): - continue # Cannot slice an empty NodeCollection, or use stop <= start. - for step in range(1, len(nodes)): - self.assertEqual( - nodes[start:stop:step].tolist(), n_list[start:stop:step], f"with [{start}:{stop}:{step}]" - ) - - def test_correct_index(self): - """Multiple NodeCollection calls give right indexing""" - compare_begin = 1 - compare_end = 11 - for model in nest.node_models: - n = nest.Create(model, 10) - n_list = n.tolist() - compare = list(range(compare_begin, compare_end)) - compare_begin += 10 - compare_end += 10 - self.assertEqual(n_list, compare) - - def test_iterating(self): - """Iteration of NodeCollections""" - - n = nest.Create("iaf_psc_alpha", 15) - compare = 0 - for nc in n: - self.assertEqual(nc, n[compare]) - compare += 1 - def test_NodeCollection_addition(self): """Addition of NodeCollections""" + # DONE nodes_a = nest.Create("iaf_psc_alpha", 2) nodes_b = nest.Create("iaf_psc_alpha", 2) all_nodes = nodes_a + nodes_b @@ -248,6 +55,7 @@ def test_NodeCollection_addition(self): nest.ResetKernel() + # CURRENT TODO n_neurons_a = 10 n_neurons_b = 15 n_neurons_c = 7 @@ -309,147 +117,10 @@ def test_NodeCollection_addition(self): with self.assertRaises(nest.kernel.NESTError): nc_sum = nc_a + nc_b + nc_c # noqa: F841 - def test_NodeCollection_membership(self): - """Membership in NodeCollections""" - - def check_membership(nc, reference, inverse_ref): - """Checks that all node IDs in reference are in nc, and that elements in inverse_ref are not in the nc.""" - for i in reference: - self.assertTrue(i in nc, f"{i} in {nc.tolist()}") - for j in inverse_ref: - self.assertFalse(j in nc, f"{j} not in {nc.tolist()}") - - self.assertFalse(reference[-1] + 1 in nc) - self.assertFalse(0 in nc) - self.assertFalse(-1 in nc) - - # Primitive NodeCollection - N = 10 - primitive = nest.Create("iaf_psc_alpha", N) - check_membership(primitive, range(1, N + 1), []) - - # Composite NodeCollection - exp_N = 5 - N += exp_N - composite = primitive + nest.Create("iaf_psc_exp", exp_N) - check_membership(composite, range(1, N + 1), []) - - # Sliced NodeCollection - low = 3 - high = 12 - sliced = composite[low:high] - inverse_reference = list(range(1, N)) - del inverse_reference[low:high] - check_membership(sliced, range(low + 1, high + 1), inverse_reference) - - # NodeCollection with step - step = 3 - stepped = composite[::step] - inverse_reference = list(range(1, N)) - del inverse_reference[::step] - check_membership(stepped, range(1, N + 1, step), inverse_reference) - - # Sliced NodeCollection with step - sliced_stepped = composite[low:high:step] - inverse_reference = list(range(1, N)) - del inverse_reference[low:high:step] - check_membership(sliced_stepped, range(low + 1, high + 1, step), inverse_reference) - - def test_NodeCollection_index(self): - """NodeCollections index function""" - - def check_index_against_list(nc, inverse_ref): - """Checks NC index against list index, and that elements specified in inverse_ref are not found.""" - for i in nc.tolist(): - self.assertEqual(nc.index(i), nc.tolist().index(i), "i={}".format(i)) - for j in inverse_ref: - with self.assertRaises(ValueError): - nc.index(j) - with self.assertRaises(ValueError): - nc.index(nc.tolist()[-1] + 1) - with self.assertRaises(ValueError): - nc.index(0) - with self.assertRaises(ValueError): - nc.index(-1) - - # Primitive NodeCollection - N = 10 - primitive = nest.Create("iaf_psc_alpha", N) - check_index_against_list(primitive, []) - - # Composite NodeCollection - exp_N = 5 - composite = primitive + nest.Create("iaf_psc_exp", exp_N) - check_index_against_list(composite, []) - - # Sliced NodeCollection - low = 3 - high = 12 - sliced = composite[low:high] - inverse_reference = list(range(1, N)) - del inverse_reference[low:high] - check_index_against_list(sliced, inverse_reference) - - # NodeCollection with step - step = 3 - stepped = composite[::step] - inverse_reference = list(range(1, N)) - del inverse_reference[::step] - check_index_against_list(stepped, inverse_reference) - - # Sliced NodeCollection with step - sliced_stepped = composite[low:high:step] - inverse_reference = list(range(1, N)) - del inverse_reference[low:high:step] - check_index_against_list(sliced_stepped, inverse_reference) - - def test_correct_len_on_NodeCollection(self): - """len function on NodeCollection""" - - a = nest.Create("iaf_psc_exp", 10) - self.assertEqual(len(a), 10) - - b = nest.Create("iaf_psc_alpha", 7) - nodes = a + b - self.assertEqual(len(nodes), 17) - - c = nest.Create("iaf_psc_delta", 20) - c = c[3:17:4] - self.assertEqual(len(c), 4) - - def test_raises_with_nonunique_nodes(self): - """Non-unique nodes in NodeCollection raises error""" - n = nest.Create("iaf_psc_alpha", 10) - - with self.assertRaises(nest.kernel.NESTError): - n[1:3] + n[2:5] - with self.assertRaises(nest.kernel.NESTError): - nest.NodeCollection([2, 2]) - with self.assertRaises(nest.kernel.NESTError): - nest.NodeCollection([2]) + nest.NodeCollection([1, 2]) - - def test_from_list_unsorted_raises(self): - """Creating NodeCollection from unsorted list raises error""" - nest.Create("iaf_psc_alpha", 10) - - with self.assertRaises(nest.kernel.NESTError): - nest.NodeCollection([5, 4, 6]) - with self.assertRaises(nest.kernel.NESTError): - nest.NodeCollection([5, 6, 4]) - - def test_slice_with_unsorted_raises(self): - """Slicing NodeCollection with unsorted list raises error""" - n = nest.Create("iaf_psc_alpha", 10) - - with self.assertRaises(nest.kernel.NESTError): - n[[6, 5, 4]] - with self.assertRaises(nest.kernel.NESTError): - n[[5, 4, 6]] - with self.assertRaises(nest.kernel.NESTError): - n[[5, 6, 4]] - def test_composite_NodeCollection(self): """Tests composite NodeCollection with patched node IDs""" + # Move to indexing and slicing + # test_composite_node_collection_with_patched_node_ids num_a = 10 num_b = 15 @@ -490,163 +161,13 @@ def test_composite_NodeCollection(self): self.assertEqual(n_list_middle, compare_list_middle) self.assertEqual(n_list_middle_jump, compare_list_middle_jump) - def test_composite_wrong_slice(self): - """ - A NESTError is raised when trying to add a sliced composite and - NodeCollection - """ - - a = nest.Create("iaf_psc_alpha", 10) - b = nest.Create("iaf_psc_exp", 7) - c = a + b - d = c[::2] - e = nest.Create("iaf_psc_delta", 13) - - with self.assertRaises(nest.kernel.NESTError): - f = d + e # noqa: F841 - - def test_model(self): - """Correct NodeCollection model""" - - n = nest.Create("iaf_psc_alpha") - - for model in nest.node_models: - n += nest.Create(model) - - self.assertTrue(len(n) > 0) - - models = ["iaf_psc_alpha"] + list(nest.node_models) - for count, nc in enumerate(n): - self.assertEqual(nc.get("model"), models[count]) - - def test_connect(self): - """Connect works with NodeCollections""" - - n = nest.Create("iaf_psc_exp", 10) - nest.Connect(n, n, {"rule": "one_to_one"}) - self.assertEqual(nest.num_connections, 10) - - for nc in n: - nest.Connect(nc, nc) - self.assertEqual(nest.num_connections, 20) - - nest.ResetKernel() - - n = nest.Create("iaf_psc_alpha", 2) - nest.Connect(n[0], n[1]) - self.assertEqual(nest.num_connections, 1) - - def test_SetStatus_and_GetStatus(self): - """ - Test that SetStatus and GetStatus works as expected with - NodeCollection - """ - - num_nodes = 10 - n = nest.Create("iaf_psc_alpha", num_nodes) - nest.SetStatus(n, {"V_m": 3.5}) - self.assertEqual(nest.GetStatus(n, "V_m")[0], 3.5) - - V_m = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0] - nest.SetStatus(n, "V_m", V_m) - for i in range(num_nodes): - self.assertEqual(nest.GetStatus(n, "V_m")[i], V_m[i]) - - with self.assertRaises(TypeError): - nest.SetStatus(n, [{"V_m": 34.0}, {"V_m": -5.0}]) - - nest.ResetKernel() - - nc = nest.Create("iaf_psc_exp", 5) # noqa: F841 - - with self.assertRaises(nest.kernel.NESTError): - nest.SetStatus(n, {"V_m": -40.0}) - with self.assertRaises(nest.kernel.NESTError): - nest.GetStatus(n) - - nest.ResetKernel() - n = nest.Create("iaf_psc_alpha", 3) - nest.SetStatus(n, [{"V_m": 10.0}, {"V_m": -10.0}, {"V_m": -20.0}]) - self.assertEqual(nest.GetStatus(n, "V_m"), (10.0, -10.0, -20.0)) - - def test_GetConnections(self): - """ - GetConnection works as expected - """ - - n = nest.Create("iaf_psc_alpha", 3) - nest.Connect(n, n) - - get_conn = nest.GetConnections() - get_conn_all = nest.GetConnections(n, n) - get_conn_some = nest.GetConnections(n[::2]) - - self.assertEqual(get_conn_all, get_conn) - - compare_source = [1, 1, 1, 3, 3, 3] - compare_target = [1, 2, 3, 1, 2, 3] - self.assertEqual(get_conn_some.get("source"), compare_source) - self.assertEqual(get_conn_some.get("target"), compare_target) - - expected_syn_id = nest.GetDefaults("static_synapse", "synapse_modelid") - - compare_list = [3, 1, 0, expected_syn_id, 6] - conn = [ - get_conn_some.get("source")[3], - get_conn_some.get("target")[3], - get_conn_some.get("target_thread")[3], - get_conn_some.get("synapse_id")[3], - get_conn_some.get("port")[3], - ] - self.assertEqual(conn, compare_list) - - conns = nest.GetConnections(n[0]).get() - - connections = [ - [ - conns["source"][i], - conns["target"][i], - conns["target_thread"][i], - conns["synapse_id"][i], - conns["port"][i], - ] - for i in range(len(nest.GetConnections(n[0]))) - ] - - ref = [[1, 1, 0, expected_syn_id, 0], [1, 2, 0, expected_syn_id, 1], [1, 3, 0, expected_syn_id, 2]] - for conn, conn_ref in zip(connections, ref): - self.assertEqual(conn, conn_ref) - - def test_GetConnections_with_slice(self): - """ - GetConnection with sliced works NodeCollections - """ - - nodes = nest.Create("iaf_psc_alpha", 11) - nest.Connect(nodes, nodes) - - conns = nest.GetConnections(nodes[1:9:3]) - source = conns.get("source") - source_ref = [2] * 11 + [5] * 11 + [8] * 11 - - self.assertEqual(source_ref, source) - - def test_GetConnections_bad_source(self): - """ - GetConnection raises a TypeError when called with 0 - """ - - n = nest.Create("iaf_psc_alpha", 3) - nest.Connect(n, n) - - with self.assertRaises(TypeError): - nest.GetConnections([0, 1]) - def test_senders_and_targets(self): """ Senders and targets for weight recorder works as NodeCollection and list """ + # move to test_weight_recorder + wr = nest.Create("weight_recorder") pre = nest.Create("parrot_neuron", 5) post = nest.Create("parrot_neuron", 5) @@ -669,68 +190,6 @@ def test_senders_and_targets(self): self.assertEqual(gss.tolist(), [2, 6]) self.assertEqual(gst.tolist(), [8, 9]) - def test_apply(self): - """ - NodeCollection apply - """ - n = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) - param = nest.spatial.pos.x - ref_positions = np.array(nest.GetPosition(n)) - self.assertEqual(param.apply(n), tuple(ref_positions[:, 0])) - self.assertEqual(param.apply(n[0]), (ref_positions[0, 0],)) - self.assertEqual(param.apply(n[::2]), tuple(ref_positions[::2, 0])) - - with self.assertRaises(nest.kernel.NESTError): - nest.spatial.pos.z.apply(n) - - def test_apply_positions(self): - """ - NodeCollection apply with positions - """ - n = nest.Create("iaf_psc_alpha", positions=nest.spatial.grid([2, 2])) - param = nest.spatial.distance - # Single target position - target = [ - [1.0, 2.0], - ] - for source in n: - source_x, source_y = nest.GetPosition(source) - target_x, target_y = (target[0][0], target[0][1]) - ref_distance = np.sqrt((target_x - source_x) ** 2 + (target_y - source_y) ** 2) - self.assertEqual(param.apply(source, target), ref_distance) - - # Multiple target positions - targets = np.array(nest.GetPosition(n)) - for source in n: - source_x, source_y = nest.GetPosition(source) - ref_distances = np.sqrt((targets[:, 0] - source_x) ** 2 + (targets[:, 1] - source_y) ** 2) - self.assertEqual(param.apply(source, list(targets)), tuple(ref_distances)) - - # Raises when passing source with multiple node IDs - with self.assertRaises(ValueError): - param.apply(n, target) - - # Erroneous position specification - source = n[0] - with self.assertRaises(nest.kernel.NESTError): - param.apply( - source, - [ - [1.0, 2.0, 3.0], - ], - ) # Too many dimensions - with self.assertRaises(TypeError): - param.apply(source, [1.0, 2.0]) # Not a list of lists - with self.assertRaises(ValueError): - # Not consistent dimensions - param.apply(source, [[1.0, 2.0], [1.0, 2.0, 3.0]]) - - def test_Create_accepts_empty_params_dict(self): - """ - Create with empty parameter dictionary - """ - nest.Create("iaf_psc_delta", params={}) - def test_array_indexing(self): """NodeCollection array indexing""" n = nest.Create("iaf_psc_alpha", 10) @@ -785,30 +244,6 @@ def test_array_indexing_bools(self): with self.assertRaises(err): sliced = n[case] - def test_empty_nc(self): - """Connection with empty NodeCollection raises error""" - nodes = nest.Create("iaf_psc_alpha", 5) - - for empty_nc in [nest.NodeCollection(), nest.NodeCollection([])]: - with self.assertRaises(nest.kernel.NESTErrors.IllegalConnection): - nest.Connect(nodes, empty_nc) - - with self.assertRaises(nest.kernel.NESTErrors.IllegalConnection): - nest.Connect(empty_nc, nodes) - - with self.assertRaises(nest.kernel.NESTErrors.IllegalConnection): - nest.Connect(empty_nc, empty_nc) - - with self.assertRaises(ValueError): - empty_nc.get() - - with self.assertRaises(AttributeError): - empty_nc.V_m - - self.assertFalse(empty_nc) - self.assertTrue(nodes) - self.assertIsNone(empty_nc.set()) # Also checking that it does not raise an error - def test_empty_nc_addition(self): """Combine NodeCollection with empty NodeCollection and connect""" n = 5 From bd507d911efd2b0b88c95ac2adfe4b5dad52581f Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 21:24:01 +0200 Subject: [PATCH 15/20] Move weight recorder test from test_NodeCollection.py --- testsuite/pytests/test_weight_recorder.py | 37 ++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/testsuite/pytests/test_weight_recorder.py b/testsuite/pytests/test_weight_recorder.py index 57ad6b9288..a5fc730ac1 100644 --- a/testsuite/pytests/test_weight_recorder.py +++ b/testsuite/pytests/test_weight_recorder.py @@ -24,9 +24,11 @@ """ import unittest -import nest + import numpy as np +import nest + HAVE_GSL = nest.ll_api.sli_func("statusdict/have_gsl ::") HAVE_OPENMP = nest.ll_api.sli_func("is_threaded") @@ -201,6 +203,39 @@ def testDefinedTargetsAndSenders(self): self.addTypeEqualityFunc(type(wr_targets), self.is_subset) self.assertEqual(wr_targets, targets) + def test_senders_and_targets(self): + """ + Senders and targets for weight recorder works as NodeCollection and list. + + NOTE: This test was moved from test_NodeCollection.py and may overlap + with test already present in this test suite. If that is the case, + consider to just drop this test. + """ + + nest.ResetKernel() + + wr = nest.Create("weight_recorder") + pre = nest.Create("parrot_neuron", 5) + post = nest.Create("parrot_neuron", 5) + + # Senders and targets lists empty + self.assertFalse(nest.GetStatus(wr, "senders")[0]) + self.assertFalse(nest.GetStatus(wr, "targets")[0]) + + nest.SetStatus(wr, {"senders": pre[1:3], "targets": post[3:]}) + + gss = nest.GetStatus(wr, "senders")[0] + gst = nest.GetStatus(wr, "targets")[0] + + self.assertEqual(gss.tolist(), [3, 4]) + self.assertEqual(gst.tolist(), [10, 11]) + + nest.SetStatus(wr, {"senders": [2, 6], "targets": [8, 9]}) + gss = nest.GetStatus(wr, "senders")[0] + gst = nest.GetStatus(wr, "targets")[0] + self.assertEqual(gss.tolist(), [2, 6]) + self.assertEqual(gst.tolist(), [8, 9]) + def testMultapses(self): """Weight Recorder Multapses""" From 2a27684a88c05b205e40769832c1a16fdc023cdd Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 22:27:56 +0200 Subject: [PATCH 16/20] Refactor indexing and slicing tests from test_NodeCollection.py --- .../test_node_collection_indexing_slicing.py | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/testsuite/pytests/test_node_collection_indexing_slicing.py b/testsuite/pytests/test_node_collection_indexing_slicing.py index b27d2cc813..03479bf2f2 100644 --- a/testsuite/pytests/test_node_collection_indexing_slicing.py +++ b/testsuite/pytests/test_node_collection_indexing_slicing.py @@ -272,3 +272,142 @@ def test_node_collection_with_nonunique_nodes_raises(): with pytest.raises(nest.kernel.NESTErrors.BadProperty): nest.NodeCollection([2]) + nest.NodeCollection([1, 2]) + + +def test_composite_node_collection_with_patched_node_ids_correct_nodes(): + """Test correct node IDs for composite ``NodeCollection`` with patched node IDs.""" + + nc_a = nest.Create("iaf_psc_exp", 10) + nest.Create("iaf_psc_alpha", 15) # will not be part of composite + nc_c = nest.Create("iaf_psc_delta", 30) + + comp_nc = nc_a + nc_c + step_comp_nc = comp_nc[::2] + step_comp_nc_list = step_comp_nc.tolist() + + expected = list(range(1, 11))[::2] + list(range(26, 55))[::2] + assert step_comp_nc_list == expected + + +def test_sliced_composite_node_collection_with_patched_node_ids_iteration(): + """Test iteration of sliced composite ``NodeCollection`` with patched node IDs.""" + + nc_a = nest.Create("iaf_psc_exp", 10) + nest.Create("iaf_psc_alpha", 15) # will not be part of composite + nc_c = nest.Create("iaf_psc_delta", 30) + + comp_nc = nc_a + nc_c + step_comp_nc = comp_nc[::2] + + compare_list = list(range(1, 11))[::2] + list(range(26, 55))[::2] + + i = 0 + for node in step_comp_nc: + assert node == nest.NodeCollection([compare_list[i]]) + i += 1 + + +def test_composite_node_collection_with_patched_node_ids_slicing(): + """Test correct node IDs for sliced composite ``NodeCollection`` with patched node IDs.""" + + nc_a = nest.Create("iaf_psc_exp", 10) + nest.Create("iaf_psc_alpha", 15) # will not be part of composite + nc_c = nest.Create("iaf_psc_delta", 30) + + comp_nc = nc_a + nc_c + + nc_slice_first = comp_nc[:10] + nc_slice_middle = comp_nc[2:7] + nc_slice_middle_jump = comp_nc[2:12:2] + + expected_first = list(range(1, 11)) + expected_middle = list(range(3, 8)) + expected_middle_jump = [3, 5, 7, 9, 26] + + assert nc_slice_first.tolist() == expected_first + assert nc_slice_middle.tolist() == expected_middle + assert nc_slice_middle_jump.tolist() == expected_middle_jump + + +@pytest.mark.parametrize("indices", [[1, 2], [2, 5], [0, 2, 5, 7, 9], (2, 5), []]) +def test_node_collection_array_indexing(indices): + """Test ``NodeCollection`` array indexing.""" + + nc = nest.Create("iaf_psc_alpha", 10) + + expected_node_ids = [i + 1 for i in indices] + + # indexing with standard Python data structures + assert nc[indices].tolist() == expected_node_ids + + # indexing with NumPy array + indices_numpy = np.array(indices) + assert nc[indices_numpy].tolist() == expected_node_ids + + +@pytest.mark.parametrize( + "indices, expected_error", + [ + ([5, 10, 15], IndexError), # Index not in NodeCollection + ([2, 5.5], TypeError), # Not all indices are ints + ([[2, 4], [6, 8]], TypeError), # Too many dimensions + ([2, 2], ValueError), # Non-unique elements + ], +) +def test_node_collection_erroneous_array_indexing_raises(indices, expected_error): + """Test that``NodeCollection`` erroneous array indexing raises error.""" + + nc = nest.Create("iaf_psc_alpha", 10) + + # erroneous indexing with standard Python data structures + with pytest.raises(expected_error): + nc[indices] + + # erroneous indexing with Numpy array + indices_numpy = np.array(indices) + with pytest.raises(expected_error): + nc[indices_numpy] + + +@pytest.mark.parametrize("indices", [[True] * 5, [False] * 5, [True, False, True, False, True]]) +def test_node_collection_bool_array_indexing(indices): + """Test ``NodeCollection`` bool array indexing.""" + + nc = nest.Create("iaf_psc_alpha", 5) + + expected_node_ids = [i for i, b in zip(range(1, 6), indices) if b] + + # indexing with standard Python data structures + assert nc[indices].tolist() == expected_node_ids + + # indexing with NumPy array + indices_numpy = np.array(indices) + assert nc[indices_numpy].tolist() == expected_node_ids + + +@pytest.mark.parametrize( + "indices, expected_error", + [ + ([True] * 4, IndexError), # Too few bools + ([True] * 6, IndexError), # Too many bools + ([[True, False], [True, False]], TypeError), # Too many dimensions + ([True, False, 2.5, False, True], TypeError), # Not all indices are bools + ([1, False, 1, False, 1], TypeError), # Mixing bools and ints + ], +) +def test_node_collection_erroneous_bool_array_indexing_raises(indices, expected_error): + """Test that``NodeCollection`` erroneous array indexing raises error.""" + + nc = nest.Create("iaf_psc_alpha", 5) + + # erroneous indexing with standard Python data structures + with pytest.raises(expected_error): + nc[indices] + + # erroneous indexing with NumPy array + if all(isinstance(i, bool) for i in indices): + # Omit cases that mix bools and ints, because converting them to + # NumPy arrays converts bools to ints. + indices_numpy = np.array(indices) + with pytest.raises(expected_error): + nc[indices_numpy] From c98e47d1cd300635c0ca6211786776202df90295 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 23:09:09 +0200 Subject: [PATCH 17/20] Refactor operation tests from test_NodeCollection.py --- .../test_node_collection_operations.py | 85 ++++++++++++++++--- 1 file changed, 72 insertions(+), 13 deletions(-) diff --git a/testsuite/pytests/test_node_collection_operations.py b/testsuite/pytests/test_node_collection_operations.py index 3a5141828b..ac09b7cd20 100644 --- a/testsuite/pytests/test_node_collection_operations.py +++ b/testsuite/pytests/test_node_collection_operations.py @@ -66,23 +66,82 @@ def test_primitive_node_collection_add(): assert prim_nc.tolist() == [1, 2, 3, 4] -def test_node_collection_add(): - """Test""" +def test_primitive_node_collection_reverse_add_sorted(): + """Test primitive ``NodeCollection`` reverse addition sorted correctly.""" - n_nrns_a = 10 - n_nrns_b = 15 - n_nrns_c = 7 - n_nrns_d = 5 - n_nrns_a_b = n_nrns_a + n_nrns_b - n_nrns_a_b_c = n_nrns_a_b + n_nrns_c + nodes_a = nest.Create("iaf_psc_alpha", 10) + nodes_b = nest.Create("iaf_psc_alpha", 15) + prim_nc = nodes_b + nodes_a + + expected = nodes_a.tolist() + nodes_b.tolist() + assert prim_nc.tolist() == expected + + +def test_composite_node_collection_addition(): + """Test composite ``NodeCollection`` addition.""" + + nodes_a = nest.Create("iaf_psc_alpha", 10) + nodes_b = nest.Create("iaf_psc_alpha", 15) + nodes_c = nest.Create("iaf_psc_exp", 7) + nodes_d = nest.Create("iaf_psc_delta", 5) + + comp_nc_ac = nodes_a + nodes_c + comp_nc_bd = nodes_b + nodes_d + + comp_nc_abcd = comp_nc_bd + comp_nc_ac + + expected = nodes_a.tolist() + nodes_b.tolist() + nodes_c.tolist() + nodes_d.tolist() + + assert comp_nc_abcd.tolist() == expected + + +def test_node_collection_add_all_node_models(): + """Test against expectation when adding all possible ``NodeCollection`` neuron models.""" + + models = nest.node_models + nc_list = [] + + for model in models: + nc = nest.Create(model, 10) + nc_list += nc.tolist() + + expected = list(range(1, len(models) * 10 + 1)) + + assert nc_list == expected + + +def test_node_collection_add_overlapping_raises(): + """Test that joining overlapping ``NodeCollection``s raises an error.""" + + nc_a = nest.Create("iaf_psc_alpha", 10) + nc_b = nest.Create("iaf_psc_exp", 7) + nc_c = nest.NodeCollection([6, 8, 10, 12, 14]) + + with pytest.raises(nest.kernel.NESTError): + nc_a + nc_b + nc_c + + +def test_empty_node_collection_add(): + """Test left add of empty ``NodeCollection``.""" + + n_nrns = 5 + + nc = nest.NodeCollection() + nc += nest.Create("iaf_psc_alpha", n_nrns) + + nest.Connect(nc, nc) + + assert nest.num_connections == n_nrns * n_nrns def test_node_collection_iteration(): """Test ``NodeCollection`` iteration.""" nc = nest.Create("iaf_psc_alpha", 15) - for i, node in enumerate(nc): + i = 0 + for node in nc: assert node == nc[i] + i += 1 def test_node_collection_membership(): @@ -138,10 +197,10 @@ def test_node_collection_membership(): for node_id in ref: assert node_id in nc for node_id in inv_ref: - assert not node_id in nc - assert not ref[-1] + 1 in nc - assert not 0 in nc - assert not -1 in nc + assert node_id not in nc + assert ref[-1] + 1 not in nc + assert 0 not in nc + assert -1 not in nc def test_composite_node_collection_add_sliced_raises(): From b58b344ffef5accaaeffa8e307224038222791a4 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 23:30:51 +0200 Subject: [PATCH 18/20] Remove test_NodeCollection.py --- testsuite/pytests/test_NodeCollection.py | 284 ----------------------- 1 file changed, 284 deletions(-) delete mode 100644 testsuite/pytests/test_NodeCollection.py diff --git a/testsuite/pytests/test_NodeCollection.py b/testsuite/pytests/test_NodeCollection.py deleted file mode 100644 index 4abc82d97a..0000000000 --- a/testsuite/pytests/test_NodeCollection.py +++ /dev/null @@ -1,284 +0,0 @@ -# -*- coding: utf-8 -*- -# -# test_NodeCollection.py -# -# This file is part of NEST. -# -# Copyright (C) 2004 The NEST Initiative -# -# NEST is free software: you can redistribute it and/or modify -# it under the terms of the GNU General Public License as published by -# the Free Software Foundation, either version 2 of the License, or -# (at your option) any later version. -# -# NEST is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with NEST. If not, see . - -""" -NodeCollection tests -""" - -import unittest - -import nest - -try: - import numpy as np - - HAVE_NUMPY = True -except ImportError: - HAVE_NUMPY = False - - -@nest.ll_api.check_stack -class TestNodeCollection(unittest.TestCase): - """NodeCollection tests""" - - def setUp(self): - nest.ResetKernel() - - def test_NodeCollection_addition(self): - """Addition of NodeCollections""" - - # DONE - nodes_a = nest.Create("iaf_psc_alpha", 2) - nodes_b = nest.Create("iaf_psc_alpha", 2) - all_nodes = nodes_a + nodes_b - all_nodes_list = all_nodes.tolist() - test_list = [1, 2, 3, 4] - self.assertEqual(all_nodes_list, test_list) - - nest.ResetKernel() - - # CURRENT TODO - n_neurons_a = 10 - n_neurons_b = 15 - n_neurons_c = 7 - n_neurons_d = 5 - n_a_b = n_neurons_a + n_neurons_b - n_a_b_c = n_a_b + n_neurons_c - nodes_a = nest.Create("iaf_psc_alpha", n_neurons_a) - nodes_b = nest.Create("iaf_psc_alpha", n_neurons_b) - nodes_ba = nest.Create("iaf_psc_alpha", n_neurons_b) - nodes_bb = nest.Create("iaf_psc_alpha", n_neurons_b) - nodes_c = nest.Create("iaf_psc_exp", n_neurons_c) - nodes_d = nest.Create("iaf_cond_alpha", n_neurons_d) - - node_b_a = nodes_b + nodes_a - node_b_a_list = node_b_a.tolist() - test_b_a_list = nodes_b.tolist() + nodes_a.tolist() - test_b_a_list.sort() - self.assertEqual(node_b_a_list, test_b_a_list) - - node_a_c = nodes_a + nodes_c - node_a_c_list = node_a_c.tolist() - test_a_c_list = nodes_a.tolist() + nodes_c.tolist() - test_a_c_list.sort() - self.assertEqual(node_a_c_list, test_a_c_list) - - # Add two composite NodeCollections - node_a_c = nodes_a + nodes_c - node_b_d = nodes_b + nodes_d - node_abcd = node_b_d + node_a_c - test_abcd_list = nodes_a.tolist() + nodes_b.tolist() + nodes_c.tolist() + nodes_d.tolist() - test_abcd_list.sort() - self.assertEqual(node_abcd.tolist(), test_abcd_list) - - node_a_ba = nodes_a + nodes_ba - node_b_bb = nodes_b + nodes_bb - node_aba_bbb = node_a_ba + node_b_bb - test_aba_bbb_list = nodes_a.tolist() + nodes_ba.tolist() + nodes_b.tolist() + nodes_bb.tolist() - test_aba_bbb_list.sort() - self.assertEqual(node_aba_bbb.tolist(), test_aba_bbb_list) - - nest.ResetKernel() - - n_list = [] - n_models = 0 - for model in nest.node_models: - n = nest.Create(model, 10) - n_list += n.tolist() - n_models += 1 - - compare_list = list(range(1, n_models * 10 + 1)) - self.assertEqual(n_list, compare_list) - - nest.ResetKernel() - - nc_a = nest.Create("iaf_psc_alpha", 10) - nc_b = nest.Create("iaf_psc_exp", 7) - nc_c = nest.NodeCollection([6, 8, 10, 12, 14]) - - with self.assertRaises(nest.kernel.NESTError): - nc_sum = nc_a + nc_b + nc_c # noqa: F841 - - def test_composite_NodeCollection(self): - """Tests composite NodeCollection with patched node IDs""" - # Move to indexing and slicing - # test_composite_node_collection_with_patched_node_ids - - num_a = 10 - num_b = 15 - num_c = 30 - - n_a = nest.Create("iaf_psc_exp", num_a) - n_b = nest.Create("iaf_psc_alpha", num_b) # noqa: F841 - n_c = nest.Create("iaf_psc_delta", num_c) - - nodes = n_a + n_c - nodes_step = nodes[::2] - nodes_list = nodes_step.tolist() - compare_list = list(range(1, 11))[::2] + list(range(26, 55))[::2] - self.assertEqual(nodes_list, compare_list) - - self.assertEqual(nodes_list[2], 5) - self.assertEqual(nodes_list[5], 26) - self.assertEqual(nodes_list[19], 54) - - # Test iteration of sliced NodeCollection - i = 0 - for n in nodes_step: - self.assertEqual(n, nest.NodeCollection([compare_list[i]])) - i += 1 - - n_slice_first = nodes[:10] - n_slice_middle = nodes[2:7] - n_slice_middle_jump = nodes[2:12:2] - - n_list_first = n_slice_first.tolist() - n_list_middle = n_slice_middle.tolist() - n_list_middle_jump = n_slice_middle_jump.tolist() - - compare_list_first = list(range(1, 11)) - compare_list_middle = list(range(3, 8)) - compare_list_middle_jump = [3, 5, 7, 9, 26] - self.assertEqual(n_list_first, compare_list_first) - self.assertEqual(n_list_middle, compare_list_middle) - self.assertEqual(n_list_middle_jump, compare_list_middle_jump) - - def test_senders_and_targets(self): - """ - Senders and targets for weight recorder works as NodeCollection and list - """ - - # move to test_weight_recorder - - wr = nest.Create("weight_recorder") - pre = nest.Create("parrot_neuron", 5) - post = nest.Create("parrot_neuron", 5) - - # Senders and targets lists empty - self.assertFalse(nest.GetStatus(wr, "senders")[0]) - self.assertFalse(nest.GetStatus(wr, "targets")[0]) - - nest.SetStatus(wr, {"senders": pre[1:3], "targets": post[3:]}) - - gss = nest.GetStatus(wr, "senders")[0] - gst = nest.GetStatus(wr, "targets")[0] - - self.assertEqual(gss.tolist(), [3, 4]) - self.assertEqual(gst.tolist(), [10, 11]) - - nest.SetStatus(wr, {"senders": [2, 6], "targets": [8, 9]}) - gss = nest.GetStatus(wr, "senders")[0] - gst = nest.GetStatus(wr, "targets")[0] - self.assertEqual(gss.tolist(), [2, 6]) - self.assertEqual(gst.tolist(), [8, 9]) - - def test_array_indexing(self): - """NodeCollection array indexing""" - n = nest.Create("iaf_psc_alpha", 10) - cases = [[1, 2], [2, 5], [0, 2, 5, 7, 9], (2, 5), []] - fail_cases = [ - ([5, 10, 15], IndexError), # Index not in NodeCollection - ([2, 5.5], TypeError), # Not all indices are ints - ([[2, 4], [6, 8]], TypeError), # Too many dimensions - ([2, 2], ValueError), # Non-unique elements - ] - if HAVE_NUMPY: - cases += [np.array(c) for c in cases] - fail_cases += [(np.array(c), e) for c, e in fail_cases] - for case in cases: - print(type(case), case) - ref = [i + 1 for i in case] - ref.sort() - sliced = n[case] - self.assertEqual(sliced.tolist(), ref) - for case, err in fail_cases: - print(type(case), case) - with self.assertRaises(err): - sliced = n[case] - - def test_array_indexing_bools(self): - """NodeCollection array indexing with bools""" - n = nest.Create("iaf_psc_alpha", 5) - cases = [ - [True for _ in range(len(n))], - [False for _ in range(len(n))], - [True, False, True, False, True], - ] - fail_cases = [ - ([True for _ in range(len(n) - 1)], IndexError), # Too few bools - ([True for _ in range(len(n) + 1)], IndexError), # Too many bools - ([[True, False], [True, False]], TypeError), # Too many dimensions - ([True, False, 2.5, False, True], TypeError), # Not all indices are bools - ([1, False, 1, False, 1], TypeError), # Mixing bools and ints - ] - if HAVE_NUMPY: - cases += [np.array(c) for c in cases] - # Cutting off fail_cases before cases that mix bools and ints, - # because converting them to NumPy arrays converts bools to ints. - fail_cases += [(np.array(c), e) for c, e in fail_cases[:-2]] - for case in cases: - print(type(case), case) - ref = [i for i, b in zip(range(1, 11), case) if b] - sliced = n[case] - self.assertEqual(sliced.tolist(), ref) - for case, err in fail_cases: - print(type(case), case) - with self.assertRaises(err): - sliced = n[case] - - def test_empty_nc_addition(self): - """Combine NodeCollection with empty NodeCollection and connect""" - n = 5 - vm = -50.0 - - nodes_a = nest.NodeCollection() - nodes_a += nest.Create("iaf_psc_alpha", n) - nest.Connect(nodes_a, nodes_a) - self.assertEqual(nest.num_connections, n * n) - self.assertTrue(nodes_a) - self.assertIsNotNone(nodes_a.get()) - nodes_a.V_m = vm - self.assertEqual(nodes_a.V_m, n * (vm,)) - - nest.ResetKernel() - - nodes_b = nest.Create("iaf_psc_alpha", n) - nodes_b += nest.NodeCollection([]) - nest.Connect(nodes_b, nodes_b) - self.assertEqual(nest.num_connections, n * n) - self.assertTrue(nodes_b) - self.assertIsNotNone(nodes_b.get()) - nodes_b.V_m = vm - self.assertEqual(nodes_b.V_m, n * (vm,)) - - -def suite(): - suite = unittest.makeSuite(TestNodeCollection, "test") - return suite - - -def run(): - runner = unittest.TextTestRunner(verbosity=2) - runner.run(suite()) - - -if __name__ == "__main__": - run() From 245b72cfdf79d9bce2e15016569aee8011252ff7 Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Tue, 5 Sep 2023 23:32:53 +0200 Subject: [PATCH 19/20] Add tests for new flexibility to NodeCollection addition operations --- .../test_node_collection_operations.py | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) diff --git a/testsuite/pytests/test_node_collection_operations.py b/testsuite/pytests/test_node_collection_operations.py index ac09b7cd20..d8269c13e0 100644 --- a/testsuite/pytests/test_node_collection_operations.py +++ b/testsuite/pytests/test_node_collection_operations.py @@ -134,6 +134,57 @@ def test_empty_node_collection_add(): assert nest.num_connections == n_nrns * n_nrns +def test_node_collection_add_zero(): + """Test that adding zero and ``NodeCollection`` results in same ``NodeCollection``.""" + + nc = nest.Create("iaf_psc_alpha") + + assert nc + 0 == nc + assert 0 + nc == nc + + +def test_single_node_collection_summation(): + """Test that sum over single ``NodeCollection`` results in the same ``NodeCollection``.""" + + nc = nest.Create("iaf_psc_alpha") + assert sum(nc) == nc + + +def test_primitive_node_collection_summation(): + """Test primitive ``NodeCollection`` summation.""" + + nc_a = nest.Create("iaf_psc_alpha", 2) + nc_b = nest.Create("iaf_psc_alpha", 4) + + expected_prim_nc = nc_a + nc_b + assert sum([nc_a, nc_b]) == expected_prim_nc + + +def test_composite_node_collection_summation(): + """Test composite ``NodeCollection`` summation.""" + + nc_a = nest.Create("iaf_psc_alpha", 2) + nc_b = nest.Create("iaf_psc_exp", 4) + + expected_comp_nc = nc_a + nc_b + assert sum([nc_a, nc_b]) == expected_comp_nc + + +@pytest.mark.parametrize("invalid_type", [1, [], [0], (0,)]) +def test_node_collection_add_invalid_type_raises(invalid_type): + """Test that adding invalid type to ``NodeCollection`` raises error.""" + + nc = nest.Create("iaf_psc_alpha") + + # test right add + with pytest.raises(TypeError): + nc + invalid_type + + # test left add + with pytest.raises(TypeError): + invalid_type + nc + + def test_node_collection_iteration(): """Test ``NodeCollection`` iteration.""" From ce57badbb0b37b0e8d388693217b4b65570c253c Mon Sep 17 00:00:00 2001 From: Nicolai Haug Date: Wed, 6 Sep 2023 11:17:33 +0200 Subject: [PATCH 20/20] Remove debug print statement --- testsuite/pytests/test_get_connections.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/testsuite/pytests/test_get_connections.py b/testsuite/pytests/test_get_connections.py index df2005730f..0e9c35641b 100644 --- a/testsuite/pytests/test_get_connections.py +++ b/testsuite/pytests/test_get_connections.py @@ -126,8 +126,6 @@ def test_get_connections_correct_table_with_node_collection_index(): nodes = nest.Create("iaf_psc_alpha", 3) nest.Connect(nodes, nodes) - print(nodes[0], type(nodes[0])) - actual_conns = pd.DataFrame( nest.GetConnections(nodes[0]).get(["source", "target", "target_thread", "synapse_id", "port"]) )