6 #include <catch2/catch_test_macros.hpp>
18 TEST_CASE(
"SOA-backed Mechanism data structure",
"[Neuron][data_structures][mechanism]") {
19 GIVEN(
"A mechanism with two copies of the same tagged variable") {
21 constexpr std::array bar_values{7.0, 0.7, 0.9};
22 std::vector<Variable> field_info{{
"foo", 1}, {
"bar", bar_values.size()}};
23 auto const num_fields = field_info.size();
24 auto const foo_index = 0;
25 auto const bar_index = 1;
32 THEN(
"The mechanism data structure can be pretty-printed") {
33 std::ostringstream oss;
35 REQUIRE(oss.str() ==
"test_mechanism::storage{type=0, 2 fields}");
37 REQUIRE(mech_data.get_tag<field::FloatingPoint>().num_variables() == num_fields);
38 WHEN(
"A row is added") {
40 THEN(
"We cannot delete the mechanism type") {
43 THEN(
"Values can be read and written") {
44 constexpr
auto field0_value = 42.0;
45 mech_instance.fpfield(foo_index) = field0_value;
46 for (
auto i = 0;
i < bar_values.size(); ++
i) {
47 mech_instance.fpfield(bar_index,
i) = bar_values[
i];
49 REQUIRE_THROWS(mech_instance.fpfield(num_fields));
50 REQUIRE_THROWS(mech_instance.fpfield(bar_index, bar_values.size()));
51 REQUIRE(mech_instance.fpfield(foo_index) == field0_value);
52 for (
auto i = 0;
i < bar_values.size(); ++
i) {
53 REQUIRE(mech_instance.fpfield(bar_index,
i) == bar_values[
i]);
55 REQUIRE_THROWS(mech_instance.fpfield(num_fields));
57 REQUIRE(mech_instance.fpfield_dimension(foo_index) == 1);
58 REQUIRE(mech_instance.fpfield_dimension(bar_index) == bar_values.size());
59 REQUIRE_THROWS(mech_instance.fpfield_dimension(num_fields));
61 auto bar_handle = mech_instance.fpfield_handle(bar_index);
62 REQUIRE(*bar_handle.next_array_element(2) == 0.9);
63 REQUIRE_THROWS(*bar_handle.next_array_element(bar_values.size()));
66 AND_THEN(
"Data handles give useful information when printed") {
67 auto const require_str = [&mech_instance](std::string_view
ref,
auto... args) {
68 auto const dh = mech_instance.fpfield_handle(args...);
69 std::ostringstream oss;
71 REQUIRE(oss.str() ==
ref);
75 require_str(
"data_handle<double>{cont=test_mechanism foo row=0/1 val=42}",
77 require_str(
"data_handle<double>{cont=test_mechanism bar[0/3] row=0/1 val=7}",
80 require_str(
"data_handle<double>{cont=test_mechanism bar[1/3] row=0/1 val=0.7}",
83 require_str(
"data_handle<double>{cont=test_mechanism bar[2/3] row=0/1 val=0.9}",
88 mech_instance.fpfield_handle(foo_index)};
89 std::ostringstream oss;
92 "generic_data_handle{cont=test_mechanism foo row=0/1 type=double*}");
94 std::ostringstream actual;
95 actual << mech_instance;
96 REQUIRE(actual.str() ==
97 "test_mechanism{row=0/1 fpfield[0]{ 42 } fpfield[1]{ 7 0.7 0.9 }}");
100 WHEN(
"Many rows are added") {
101 std::vector<double> reference_field0, reference_field1_0, reference_field1_1;
102 constexpr
auto num_instances = 10;
103 std::generate_n(std::back_inserter(reference_field0), num_instances, [
i = 0]()
mutable {
107 std::generate_n(std::back_inserter(reference_field1_0),
113 std::generate_n(std::back_inserter(reference_field1_1),
117 return x * x * x * x;
119 std::vector<owning_handle> mech_instances{};
120 for (
auto i = 0;
i < num_instances; ++
i) {
121 auto& mech = mech_instances.emplace_back(mech_data);
122 mech.fpfield(0) = reference_field0[
i];
123 mech.fpfield(1, 0) = reference_field1_0[
i];
124 mech.fpfield(1, 1) = reference_field1_1[
i];
126 REQUIRE(mech_data.empty() == mech_instances.empty());
127 REQUIRE(mech_data.size() == mech_instances.size());
128 enum struct StorageCheck { Skip, Match, NotMatch };
129 const auto check_field_values = [&](StorageCheck storage_should_match) {
130 THEN(
"The correct values can be read back") {
131 std::vector<double> current_field0, current_field1_0, current_field1_1;
133 mech_instances.end(),
134 std::back_inserter(current_field0),
135 [](
auto const& mech) { return mech.fpfield(0); });
137 mech_instances.end(),
138 std::back_inserter(current_field1_0),
139 [](
auto const& mech) { return mech.fpfield(1, 0); });
141 mech_instances.end(),
142 std::back_inserter(current_field1_1),
143 [](
auto const& mech) { return mech.fpfield(1, 1); });
144 REQUIRE(current_field0 == reference_field0);
145 REQUIRE(current_field1_0 == reference_field1_0);
146 REQUIRE(current_field1_1 == reference_field1_1);
147 auto const field_matches =
148 [&](
auto const& reference,
int index,
int array_index = 0) {
149 for (
auto i = 0;
i < mech_data.size(); ++
i) {
150 if (mech_data.fpfield(
i,
index, array_index) != reference[
i]) {
156 if (storage_should_match == StorageCheck::Match) {
157 AND_THEN(
"The underlying storage matches the reference values") {
158 REQUIRE(field_matches(reference_field0, 0));
159 REQUIRE(field_matches(reference_field1_0, 1, 0));
160 REQUIRE(field_matches(reference_field1_1, 1, 1));
162 }
else if (storage_should_match == StorageCheck::NotMatch) {
163 AND_THEN(
"The underlying storage no longer matches the reference values") {
164 REQUIRE_FALSE(field_matches(reference_field0, 0));
165 REQUIRE_FALSE(field_matches(reference_field1_0, 1, 0));
166 REQUIRE_FALSE(field_matches(reference_field1_1, 1, 1));
171 check_field_values(StorageCheck::Match);
172 AND_WHEN(
"The underlying storage is permuted") {
173 std::vector<std::size_t> perm_vector(mech_instances.size());
174 std::iota(perm_vector.begin(), perm_vector.end(), 0);
176 std::shuffle(perm_vector.begin(), perm_vector.end(), g);
177 mech_data.apply_reverse_permutation(
std::move(perm_vector));
178 check_field_values(StorageCheck::Skip);
180 auto const apply_to_all = [&](
auto callable) {
181 auto const impl = [](
auto callable,
auto&... vecs) { (callable(vecs), ...); };
188 AND_WHEN(
"An element is deleted") {
189 apply_to_all([](
auto& vec) { vec.erase(vec.begin()); });
190 check_field_values(StorageCheck::Skip);
193 WHEN(
"We want to manipulate other mechanism data") {
194 THEN(
"We cannot readd the same type") {
198 THEN(
"We cannot access a non-existing mechanism") {
199 auto const non_existing_mech_type = 313;
200 REQUIRE_THROWS(
neuron::model().mechanism_data(non_existing_mech_type));
202 THEN(
"We cannot get data for a null mechanism") {
203 auto const null_mech_type = 345;
206 REQUIRE_THROWS(
neuron::model().mechanism_data(null_mech_type));
210 GIVEN(
"A mechanism type that gets deleted") {
211 std::vector<Variable> field_info{{
"foo", 1}};
212 constexpr
auto foo_index = 0;
213 constexpr
auto foo_value = 29.0;
219 REQUIRE(mech_data.get_tag<field::FloatingPoint>().num_variables() == 1);
220 WHEN(
"A row is added and we take a handle to its value") {
221 std::optional<owning_handle> instance{std::in_place, mech_data};
222 instance->fpfield(foo_index) = foo_value;
223 auto foo_handle = instance->fpfield_handle(foo_index);
225 THEN(
"The handle yields the expected value") {
226 REQUIRE(*foo_handle == foo_value);
228 AND_WHEN(
"The row is deleted again") {
230 THEN(
"We can still print the handle") {
231 std::ostringstream oss;
233 REQUIRE(oss.str() ==
"data_handle<double>{cont=test_mechanism foo died/0}");
235 AND_WHEN(
"The mechanism type is also deleted") {
237 THEN(
"We can still print the handle, following an unusual codepath") {
239 std::ostringstream oss;
241 REQUIRE(oss.str() ==
"data_handle<double>{cont=unknown died/unknown}");
244 std::ostringstream oss;
247 "generic_data_handle{cont=unknown died/unknown type=double*}");
256 TEST_CASE(
"Model::is_valid_mechanism",
"[Neuron][data_structures]") {
262 std::vector<Variable> field_info{{
"foo", 1}};
TEST_CASE("SOA-backed Mechanism data structure", "[Neuron][data_structures][mechanism]")
void move(Item *q1, Item *q2, Item *q3)
Model & model()
Access the global Model instance.
data_handle< T > transform(data_handle< T > handle, Transform type)
static double ref(void *v)
void delete_mechanism(int type)
Destroy the structure holding the data of a particular mechanism.
container::Mechanism::storage & add_mechanism(int type, Args &&... args)
Create a structure to hold the data of a new Mechanism.
bool is_valid_mechanism(int type) const
Base class defining the public API of Mechanism handles.
Non-template stable handle to a generic value.