5 #include <catch2/generators/catch_generators.hpp>
6 #include <catch2/catch_test_macros.hpp>
14 #include <unordered_map>
16 static_assert(std::is_default_constructible_v<Node>);
17 static_assert(!std::is_copy_constructible_v<Node>);
18 static_assert(std::is_move_constructible_v<Node>);
19 static_assert(!std::is_copy_assignable_v<Node>);
20 static_assert(std::is_move_assignable_v<Node>);
21 static_assert(std::is_destructible_v<Node>);
46 static std::string
to_str(T
const& x) {
47 std::ostringstream oss;
52 TEST_CASE(
"data_handle<double>",
"[Neuron][data_structures][data_handle]") {
53 GIVEN(
"A null handle") {
59 THEN(
"Check it is really null") {
62 THEN(
"Check it compares equal to a different null pointer") {
64 REQUIRE(
handle == other_handle);
66 THEN(
"Check it prints the right value") {
69 THEN(
"Check it doesn't claim to be modern") {
70 REQUIRE_FALSE(
handle.refers_to_a_modern_data_structure());
72 THEN(
"Check it decays to a null pointer") {
73 auto* foo_ptr =
static_cast<double*
>(
handle);
74 REQUIRE(foo_ptr ==
nullptr);
77 GIVEN(
"A handle wrapping a raw pointer (compatibility mode)") {
78 std::vector<double> foo(10);
86 THEN(
"Check it is not null") {
89 THEN(
"Check it does not compare equal to a null handle") {
91 REQUIRE(
handle != null_handle);
93 THEN(
"Check it compares equal to a different handle wrapping the same raw pointer") {
95 REQUIRE(
handle == other_handle);
97 THEN(
"Check it yields the right value") {
100 THEN(
"Check it doesn't claim to be modern") {
101 REQUIRE_FALSE(
handle.refers_to_a_modern_data_structure());
105 THEN(
"Check it decays to the right raw pointer") {
106 auto* foo_ptr =
static_cast<double*
>(
handle);
107 REQUIRE(foo_ptr == foo.data());
109 THEN(
"Check it prints the right value") {
110 std::ostringstream expected;
111 expected <<
"data_handle<double>{raw=" << foo.data() <<
'}';
114 THEN(
"Check that we can store/retrieve in/from unordered_map") {
115 std::unordered_map<data_handle<double>, std::string>
map;
119 THEN(
"Check that next_array_element works") {
125 GIVEN(
"A handle to a void pointer") {
133 THEN(
"Check it is not null") {
136 THEN(
"Check it doesn't claim to be modern") {
137 REQUIRE_FALSE(
handle.refers_to_a_modern_data_structure());
139 THEN(
"Check it decays to the right raw pointer") {
140 auto* foo_ptr =
static_cast<void*
>(
handle);
141 REQUIRE(foo_ptr == foo.get());
143 THEN(
"Check it matches another data_handle<void> to same pointer") {
145 REQUIRE(
handle == other_handle);
147 THEN(
"Check it prints the right value") {
148 std::ostringstream expected;
149 expected <<
"data_handle<void>{raw=" << foo.get() <<
'}';
153 GIVEN(
"A handle referring to an entry in an SOA container") {
155 std::optional<::Node>
node{std::in_place};
158 const auto handle_id =
handle.identifier();
163 THEN(
"Check it is not null") {
166 THEN(
"Check it actually refers_to voltage and not something else") {
172 THEN(
"Check it does not compare equal to a null handle") {
174 REQUIRE(
handle != null_handle);
176 THEN(
"Check it does not compare equal to a handle in legacy mode") {
179 REQUIRE(
handle != foo_handle);
181 THEN(
"Check it yields the right value") {
183 const auto const_handle(
handle);
186 THEN(
"Check it claims to be modern") {
187 REQUIRE(
handle.refers_to_a_modern_data_structure());
189 THEN(
"Check it prints the right value") {
190 REQUIRE(
to_str(
handle) ==
"data_handle<double>{Node::field::Voltage row=0/1 val=42}");
192 THEN(
"Check that getting next_array_element throws, dimension is 1") {
193 REQUIRE_THROWS(
handle.next_array_element());
195 THEN(
"Check that we can store/retrieve in/from unordered_map") {
196 std::unordered_map<data_handle<double>, std::string>
map;
198 REQUIRE(
map[
handle] ==
"unordered_map_modern_dh");
200 THEN(
"Make sure we get the current logical row number") {
203 THEN(
"Check that deleting the (Node) object it refers to invalidates the handle") {
207 REQUIRE(
handle.refers_to_a_modern_data_structure());
208 REQUIRE(
to_str(
handle) ==
"data_handle<double>{Node::field::Voltage died/0}");
212 const auto const_handle(
handle);
213 REQUIRE_THROWS(*const_handle);
214 REQUIRE(
handle.identifier() == handle_id);
217 "Check that mutating the underlying container while holding a raw pointer has the "
219 auto* raw_ptr =
static_cast<double*
>(
handle);
228 REQUIRE(
handle != new_handle);
229 REQUIRE_FALSE(new_handle.refers_to_a_modern_data_structure());
237 std::vector<double> ret{};
238 std::transform(nodes.begin(), nodes.end(), std::back_inserter(ret), [](
auto const&
node) {
244 std::size_t num_nodes = 10) {
245 std::vector<double> reference_voltages{};
246 std::generate_n(std::back_inserter(reference_voltages), num_nodes, [
i = 0]()
mutable {
250 std::vector<::Node> nodes{};
252 reference_voltages.end(),
253 std::back_inserter(nodes),
263 TEST_CASE(
"SOA-backed Node structure",
"[Neuron][data_structures][node]") {
265 GIVEN(
"A default-constructed node") {
267 THEN(
"Check its SOA-backed members have their default values") {
268 REQUIRE(
node.
area() == field::Area{}.default_value());
269 REQUIRE(
node.
v() == field::Voltage{}.default_value());
271 THEN(
"Check we can get a non-owning handle to it") {
273 AND_THEN(
"Check the handle yields the corect values") {
274 REQUIRE(
handle.
area() == field::Area{}.default_value());
275 REQUIRE(
handle.
v() == field::Voltage{}.default_value());
279 GIVEN(
"A series of nodes with increasing integer voltages") {
282 auto& nodes = std::get<0>(nodes_and_voltages);
283 auto& reference_voltages = std::get<1>(nodes_and_voltages);
289 node_data.mark_as_sorted(write_token);
291 auto const require_logical_match = [&]() {
292 THEN(
"Check the logical voltages still match") {
296 auto const storage_match = [&]() {
297 for (
auto i = 0;
i < nodes.size(); ++
i) {
298 if (node_data.get<field::Voltage>(
i) != reference_voltages.at(
i)) {
304 auto const require_logical_and_storage_match = [&]() {
305 THEN(
"Check the logical voltages still match") {
307 AND_THEN(
"Check the underlying storage also matches") {
308 REQUIRE(storage_match());
312 auto const require_logical_match_and_storage_different = [&]() {
313 THEN(
"Check the logical voltages still match") {
315 AND_THEN(
"Check the underlying storage no longer matches") {
316 REQUIRE_FALSE(storage_match());
320 WHEN(
"Values are read back immediately") {
321 require_logical_and_storage_match();
323 std::vector<std::size_t> perm_vector(nodes.size());
324 std::iota(perm_vector.begin(), perm_vector.end(), 0);
325 WHEN(
"The underlying storage is rotated") {
326 auto rotated = perm_vector;
327 std::rotate(rotated.begin(),
std::next(rotated.begin()), rotated.end());
328 auto const sorted_token = node_data.apply_reverse_permutation(
std::move(rotated));
329 require_logical_match_and_storage_different();
331 WHEN(
"A unit reverse permutation is applied to the underlying storage") {
332 node_data.apply_reverse_permutation(
std::move(perm_vector));
333 require_logical_and_storage_match();
338 WHEN(
"A random permutation is applied to the underlying storage") {
340 std::shuffle(perm_vector.begin(), perm_vector.end(), g);
341 auto const sorted_token = node_data.apply_reverse_permutation(
std::move(perm_vector));
344 require_logical_match();
346 auto const require_exception = [&](
auto perm) {
347 THEN(
"An exception is thrown") {
348 REQUIRE_THROWS(node_data.apply_reverse_permutation(
std::move(perm)));
349 AND_THEN(
"The container is still flagged as sorted") {
350 REQUIRE(node_data.is_sorted());
354 WHEN(
"A too-short permutation is applied to the underlying storage") {
355 std::vector<std::size_t> bad_perm(nodes.size() - 1);
356 std::iota(bad_perm.begin(), bad_perm.end(), 0);
359 WHEN(
"A permutation with a repeated entry is applied to the underlying storage") {
360 std::vector<std::size_t> bad_perm(nodes.size());
361 std::iota(bad_perm.begin(), bad_perm.end(), 0);
365 WHEN(
"A permutation with an invalid value is applied to the underlying storage") {
366 std::vector<std::size_t> bad_perm(nodes.size());
367 std::iota(bad_perm.begin(), bad_perm.end(), 0);
368 bad_perm[0] = std::numeric_limits<std::size_t>::max();
371 WHEN(
"The last Node is removed") {
373 reference_voltages.pop_back();
374 require_logical_and_storage_match();
376 WHEN(
"The first Node is removed") {
377 nodes.erase(nodes.begin());
378 reference_voltages.erase(reference_voltages.begin());
379 require_logical_match_and_storage_different();
381 WHEN(
"The middle Node is removed") {
382 auto const index_to_remove = nodes.size() / 2;
383 nodes.erase(
std::next(nodes.begin(), index_to_remove));
384 reference_voltages.erase(
std::next(reference_voltages.begin(), index_to_remove));
385 require_logical_match_and_storage_different();
387 WHEN(
"The dense storage is sorted and marked read-only") {
400 auto frozen_token = node_data.issue_frozen_token();
401 node_data.mark_as_sorted(frozen_token);
402 REQUIRE(node_data.is_sorted());
403 THEN(
"New nodes cannot be created") {
405 REQUIRE_THROWS(::
Node{});
409 THEN(
"Values in existing nodes can be modified") {
410 auto&
node = nodes.front();
411 REQUIRE_NOTHROW(
node.
v());
412 REQUIRE_NOTHROW(
node.
v() += 42.0);
414 THEN(
"The sorted-ness flag cannot be modified") {
415 REQUIRE_THROWS(node_data.mark_as_unsorted());
416 AND_THEN(
"Attempts to do so fail") {
417 REQUIRE(node_data.is_sorted());
421 "The storage *can* be permuted if the sorted token is transferred back to the "
423 node_data.apply_reverse_permutation(
std::move(perm_vector), frozen_token);
425 THEN(
"The storage cannot be permuted when a 2nd sorted token is used") {
427 REQUIRE_THROWS(node_data.apply_reverse_permutation(
std::move(perm_vector)));
435 THEN(
"After the token is discarded, new Nodes can be allocated") {
436 REQUIRE_NOTHROW(::
Node{});
443 TEST_CASE(
"Fast membrane current storage",
"[Neuron][data_structures][node][fast_imem]") {
446 auto const set_fast_imem = [](
bool new_value) {
450 auto const check_throws = [](
auto&
node) {
451 THEN(
"fast_imem fields cannot be accessed") {
457 auto const check_default = [](
auto&
node) {
458 THEN(
"fast_imem fields have their default values") {
464 GIVEN(
"fast_imem calculation is disabled") {
465 set_fast_imem(
false);
466 WHEN(
"A node is default-constructed") {
474 "generic_data_handle{raw=nullptr type=double*}");
475 AND_WHEN(
"fast_imem calculation is enabled with a Node active") {
484 GIVEN(
"fast_imem calculation is enabled") {
486 WHEN(
"A node is default-constructed") {
495 "data_handle<double>{Node::field::FastIMemSavRHS row=0/1 val=42}");
497 "generic_data_handle{Node::field::FastIMemSavRHS row=0/1 type=double*}");
498 AND_WHEN(
"fast_imem calculation is disabled with a Node active") {
500 set_fast_imem(
false);
507 "generic_data_handle{cont=deleted row=0/unknown type=double*}");
508 AND_WHEN(
"fast_imem calculation is re-enabled") {
516 "generic_data_handle{cont=deleted row=0/unknown type=double*}");
520 WHEN(
"A series of Nodes are created with non-trivial fast_imem values") {
521 constexpr
auto num_nodes = 10;
522 std::vector<::Node> nodes(num_nodes);
523 std::vector<std::size_t> perm_vector(num_nodes);
524 for (
auto i = 0;
i < num_nodes; ++
i) {
526 nodes[
i].sav_d() =
i *
i;
527 nodes[
i].sav_rhs() =
i *
i *
i;
529 AND_WHEN(
"A random permutation is applied") {
531 std::shuffle(perm_vector.begin(), perm_vector.end(), g);
534 THEN(
"The logical values should still match") {
535 for (
auto i = 0;
i < num_nodes; ++
i) {
536 REQUIRE(nodes[
i].sav_d() ==
i *
i);
537 REQUIRE(nodes[
i].sav_rhs() ==
i *
i *
i);
547 TEST_CASE(
"Deleting a row from a frozen SoA container causes a fatal error",
548 "[.][tests_that_abort]") {
550 std::optional<::Node>
node{std::in_place};
551 REQUIRE(node_data.size() == 1);
552 auto const frozen_token = node_data.issue_frozen_token();
static double map(void *v)
void move(Item *q1, Item *q2, Item *q3)
void nrn_fast_imem_alloc()
handle_interface< non_owning_identifier< storage > > handle
Non-owning handle to a Node.
std::tuple< std::vector<::Node >, std::vector< double > > get_nodes_and_reference_voltages(std::size_t num_nodes=10)
std::vector< double > get_node_voltages(std::vector<::Node > const &nodes)
Model & model()
Access the global Model instance.
static std::string to_str(T const &x)
data_handle< T > transform(data_handle< T > handle, Transform type)
constexpr static double magic_voltage_value
TEST_CASE("data_handle<double>", "[Neuron][data_structures][data_handle]")
static Node * node(Object *)
container::Node::storage & node_data()
Access the structure containing the data of all Nodes.
Area in um^2 but see treeset.cpp.
Base class defining the public API of Node handles.
field::Area::type & area()
Return the area.
field::Voltage::type & v()
Return the membrane potential.
Explicit specialisation data_handle<void>.
Stable handle to a generic value.
Non-template stable handle to a generic value.
std::size_t current_row() const
Return current offset in the underlying storage where this object lives.
frozen_token_type issue_frozen_token()
Create a token guaranteeing the container is in "frozen" state.
frozen_token_type apply_reverse_permutation(Arg &&permutation)
Permute the SoA-format data using an arbitrary range of integers.