Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_ca_rule_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 version 2.0.0b
6 https://github.com/lrleon/Aleph-w
7
8 This file is part of Aleph-w library
9
10 Copyright (c) 2002-2026 Leandro Rabindranath Leon
11
12 Permission is hereby granted, free of charge, to any person obtaining a copy
13 of this software and associated documentation files (the "Software"), to deal
14 in the Software without restriction, including without limitation the rights
15 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16 copies of the Software, and to permit persons to whom the Software is
17 furnished to do so, subject to the following conditions:
18
19 The above copyright notice and this permission notice shall be included in all
20 copies or substantial portions of the Software.
21
22 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28 SOFTWARE.
29*/
30
42# include <array>
43# include <cstdint>
44# include <span>
45# include <stdexcept>
46# include <utility>
47# include <vector>
48
49# include <gtest/gtest.h>
50
51# include <ca-traits.H>
52# include <tpl_ca_concepts.H>
53# include <tpl_ca_storage.H>
54# include <tpl_ca_lattice.H>
55# include <tpl_ca_neighborhood.H>
56# include <tpl_ca_rule.H>
57
58using namespace Aleph;
59using namespace Aleph::CA;
60
61namespace
62{
64 template <typename Rule, typename Boundary>
67 const Rule & rule)
68 {
69 Lattice<Dense_Cell_Storage<int, 2>, Boundary> nxt(cur.extents());
71 std::array<int, Moore<2, 1>::size_v> buf { };
72 for (ca_index_t i = 0; i < static_cast<ca_index_t>(cur.size(0)); ++i)
73 for (ca_index_t j = 0; j < static_cast<ca_index_t>(cur.size(1)); ++j)
74 {
76 std::span<int>(buf.data(), buf.size()));
77 nxt.set({ i, j },
78 rule(cur.at({ i, j }),
79 Neighbor_View<int>(buf.data(), buf.size())));
80 }
81 return nxt;
82 }
83
86 template <typename Rule, typename Boundary>
89 const Rule & rule)
90 {
91 Lattice<Dense_Cell_Storage<int, 1>, Boundary> nxt(cur.extents());
93 Offset_Vec<1>{ 1 } });
94 std::array<int, 2> buf { };
95 for (ca_index_t i = 0; i < static_cast<ca_index_t>(cur.size(0)); ++i)
96 {
98 std::span<int>(buf.data(), buf.size()));
99 nxt.set({ i },
100 rule(cur.at({ i }),
101 Neighbor_View<int>(buf.data(), buf.size())));
102 }
103 return nxt;
104 }
105
107 template <typename Boundary>
109 make_grid(std::array<ca_size_t, 2> ext, const std::vector<int> & data)
110 {
112 for (ca_size_t i = 0; i < ext[0]; ++i)
113 for (ca_size_t j = 0; j < ext[1]; ++j)
114 lat.set({ static_cast<ca_index_t>(i),
115 static_cast<ca_index_t>(j) },
116 data[i * ext[1] + j]);
117 return lat;
118 }
119
121 template <typename B1, typename B2>
122 bool same(const Lattice<Dense_Cell_Storage<int, 2>, B1> & a,
124 {
125 if (a.size(0) != b.size(0) or a.size(1) != b.size(1))
126 return false;
127 for (ca_index_t i = 0; i < static_cast<ca_index_t>(a.size(0)); ++i)
128 for (ca_index_t j = 0; j < static_cast<ca_index_t>(a.size(1)); ++j)
129 if (a.at({ i, j }) != b.at({ i, j }))
130 return false;
131 return true;
132 }
133}
134
135// ---------------------------------------------------------------------------
136// Game of Life — canonical patterns.
137// ---------------------------------------------------------------------------
138
140{
141 // 4x4 grid, 2x2 block at rows/cols [1..2].
143 { 4, 4 },
144 { 0, 0, 0, 0,
145 0, 1, 1, 0,
146 0, 1, 1, 0,
147 0, 0, 0, 0 });
148 auto rule = make_game_of_life_rule();
149 for (int t = 0; t < 5; ++t)
150 {
151 auto nxt = step_2d(lat, rule);
152 ASSERT_TRUE(same(lat, nxt)) << "Block must be stationary at t=" << t;
153 lat = std::move(nxt);
154 }
155}
156
158{
159 // 5x5 grid, horizontal blinker at row 2, cols 1..3.
161 { 5, 5 },
162 { 0, 0, 0, 0, 0,
163 0, 0, 0, 0, 0,
164 0, 1, 1, 1, 0,
165 0, 0, 0, 0, 0,
166 0, 0, 0, 0, 0 });
168 { 5, 5 },
169 { 0, 0, 0, 0, 0,
170 0, 0, 1, 0, 0,
171 0, 0, 1, 0, 0,
172 0, 0, 1, 0, 0,
173 0, 0, 0, 0, 0 });
174 auto rule = make_game_of_life_rule();
175
176 auto t1 = step_2d(horiz, rule);
178 auto t2 = step_2d(t1, rule);
180 auto t3 = step_2d(t2, rule);
182}
183
185{
186 // 8x8 toroidal so the glider can travel without falling off the edge.
188 { 8, 8 },
189 { 0, 1, 0, 0, 0, 0, 0, 0,
190 0, 0, 1, 0, 0, 0, 0, 0,
191 1, 1, 1, 0, 0, 0, 0, 0,
192 0, 0, 0, 0, 0, 0, 0, 0,
193 0, 0, 0, 0, 0, 0, 0, 0,
194 0, 0, 0, 0, 0, 0, 0, 0,
195 0, 0, 0, 0, 0, 0, 0, 0,
196 0, 0, 0, 0, 0, 0, 0, 0 });
197 // After 4 steps a glider translates by (1, 1).
199 { 8, 8 },
200 { 0, 0, 0, 0, 0, 0, 0, 0,
201 0, 0, 1, 0, 0, 0, 0, 0,
202 0, 0, 0, 1, 0, 0, 0, 0,
203 0, 1, 1, 1, 0, 0, 0, 0,
204 0, 0, 0, 0, 0, 0, 0, 0,
205 0, 0, 0, 0, 0, 0, 0, 0,
206 0, 0, 0, 0, 0, 0, 0, 0,
207 0, 0, 0, 0, 0, 0, 0, 0 });
208
209 auto rule = make_game_of_life_rule();
210 for (int t = 0; t < 4; ++t)
211 lat = step_2d(lat, rule);
212 EXPECT_TRUE(same(lat, expected)) << "Glider must shift by (1,1) in 4 steps";
213}
214
215// ---------------------------------------------------------------------------
216// Wolfram elementary rules — spot-checked outputs.
217// ---------------------------------------------------------------------------
218
219namespace
220{
224 int wolfram_next(std::uint8_t r, int l, int s, int rg)
225 {
226 const std::size_t idx = (l << 2) | (s << 1) | rg;
227 return (r >> idx) & 1;
228 }
229}
230
232{
233 // For every elementary rule and every 3-bit pattern, the precomputed
234 // table built by `make_wolfram_elementary_rule` must produce the
235 // same next-state as the textbook formula.
236 for (int r = 0; r < 256; ++r)
237 {
238 auto rule = make_wolfram_elementary_rule(static_cast<std::uint8_t>(r));
239 for (int l = 0; l < 2; ++l)
240 for (int s = 0; s < 2; ++s)
241 for (int rg = 0; rg < 2; ++rg)
242 {
243 std::array<int, 2> nb { l, rg };
244 const int got = rule(s, Neighbor_View<int>(nb.data(), 2));
245 const int want = wolfram_next(static_cast<std::uint8_t>(r),
246 l, s, rg);
248 << "rule=" << r << " (l,s,r)=(" << l << "," << s << ","
249 << rg << ")";
250 }
251 }
252}
253
255{
256 // Rule 90 from a single 1 at the centre produces the Sierpinski pattern.
257 // After one step, only the cells adjacent to the impulse become 1.
259 { 1, 7 },
260 { 0, 0, 0, 1, 0, 0, 0 });
261 auto rule = make_wolfram_elementary_rule(90);
262 // Adapt 1D step using the 2D infrastructure with size(0)=1:
263 // we just iterate j over the single row.
265 for (ca_index_t j = 0; j < 7; ++j) row.set({ j }, lat.at({ 0, j }));
266
267 auto nxt = step_1d(row, rule);
268 // Expected after rule 90 from impulse at index 3:
269 // (l,s,r)=(0,0,0)→0, (0,0,1)→1, (0,1,0)→0, (1,0,0)→1, others → 0
270 // Centred impulse at j=3:
271 // j=2: l=0,s=0,r=1 → 1
272 // j=3: l=0,s=1,r=0 → 0
273 // j=4: l=1,s=0,r=0 → 1
274 // others stay 0.
275 EXPECT_EQ(nxt.at({ 0 }), 0);
276 EXPECT_EQ(nxt.at({ 1 }), 0);
277 EXPECT_EQ(nxt.at({ 2 }), 1);
278 EXPECT_EQ(nxt.at({ 3 }), 0);
279 EXPECT_EQ(nxt.at({ 4 }), 1);
280 EXPECT_EQ(nxt.at({ 5 }), 0);
281 EXPECT_EQ(nxt.at({ 6 }), 0);
282}
283
285{
286 auto rule = make_wolfram_elementary_rule(90);
287 std::array<int, 1> short_nb { 0 };
288 EXPECT_THROW((void) rule(0, Neighbor_View<int>(short_nb.data(),
289 short_nb.size())),
290 std::length_error);
291
292 std::array<int, 2> nb { 0, 1 };
293 EXPECT_THROW((void) rule(2, Neighbor_View<int>(nb.data(), nb.size())),
294 std::domain_error);
295
296 std::array<int, 2> bad_nb { 0, -1 };
297 EXPECT_THROW((void) rule(0, Neighbor_View<int>(bad_nb.data(),
298 bad_nb.size())),
299 std::domain_error);
300}
301
303{
304 // Lattice large enough to avoid hitting OpenBoundary in 5 steps.
306 row.set({ 10 }, 1); // central impulse
307 auto rule = make_wolfram_elementary_rule(30);
308
309 // Reference rows (rule 30 is the canonical Wolfram example).
310 // Source: Wolfram MathWorld "Rule 30".
311 std::vector<std::vector<int>> expected = {
312 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // t=0
313 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, // t=1
314 { 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 }, // t=2
315 { 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0 }, // t=3
316 { 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 }, // t=4
317 { 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 0, 0 }, // t=5
318 };
319
320 for (std::size_t t = 0; t < expected.size(); ++t)
321 {
322 for (ca_index_t j = 0; j < 21; ++j)
323 ASSERT_EQ(row.at({ j }), expected[t][j])
324 << "rule 30 t=" << t << " j=" << j;
325 row = step_1d(row, rule);
326 }
327}
328
329// ---------------------------------------------------------------------------
330// Composite_Rule
331// ---------------------------------------------------------------------------
332
334{
335 // Build two rules over a tiny state machine. Rule A: x -> x + 1.
336 // Rule B: x -> x * 2. Composite(A, B): x -> 2(x+1).
337 struct Add_One {
338 int operator()(int x, Neighbor_View<int>) const { return x + 1; }
339 };
340 struct Times_Two {
341 int operator()(int x, Neighbor_View<int>) const { return x * 2; }
342 };
344 std::array<int, 0> nb { };
345 EXPECT_EQ(r(3, Neighbor_View<int>(nb.data(), 0)), 8);
346 EXPECT_EQ(r(0, Neighbor_View<int>(nb.data(), 0)), 2);
347}
348
349// ---------------------------------------------------------------------------
350// Totalistic_Rule
351// ---------------------------------------------------------------------------
352
354{
355 // Identity-on-sum: f(s) = s. The rule then returns
356 // current + sum(neighbours) for any state type.
357 auto identity = [](int s) { return s; };
358 Totalistic_Rule<decltype(identity)> r(identity);
359 std::array<int, 4> nb { 1, 2, 3, 4 };
360 EXPECT_EQ(r(10, Neighbor_View<int>(nb.data(), 4)), 20);
361}
362
363// ---------------------------------------------------------------------------
364// Outer_Totalistic_Rule with a non-bool state type.
365// ---------------------------------------------------------------------------
366
368{
369 // F: returns the number of non-zero neighbours regardless of `current`.
370 auto f = [](int /*current*/, std::size_t alive) -> int
371 { return static_cast<int>(alive); };
372 Outer_Totalistic_Rule<decltype(f)> r(f);
373
374 std::array<int, 5> nb { 0, 7, 0, -3, 0 };
375 EXPECT_EQ(r(99, Neighbor_View<int>(nb.data(), 5)), 2);
376}
size_t row
Definition ca-c-api.h:115
Common typedefs and tag types for the Cellular Automata module.
Sequential composition of one or more rules.
User-supplied list of offsets for arbitrary connectivity.
Row-major dense storage for N-dimensional cellular automata.
Lattice that adds boundary-aware access on top of a storage.
Moore (Chebyshev) neighborhood of radius R in N dimensions.
Rule whose next state depends on (current, alive_count).
Definition tpl_ca_rule.H:99
Rule whose next state depends on the sum of every cell in the neighbourhood (including the centre).
Minimal std::expected-style result type for C++20.
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
constexpr Game_Of_Life_Rule make_game_of_life_rule() noexcept
Build the canonical Game of Life rule.
std::span< const T > Neighbor_View
Read-only view over a contiguous range of neighbour values.
Definition ca-traits.H:90
std::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Definition ca-traits.H:60
std::array< ca_index_t, N > Coord_Vec
Default coordinate vector.
Definition ca-traits.H:69
void gather_neighbors(const Nbh &nh, const L &lat, const typename L::coord_type &center, std::span< T > out)
Populate out[0..nh.size()) with neighbour values of center.
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
Coord_Vec< N > Offset_Vec
Default offset vector (aliases Coord_Vec).
Definition ca-traits.H:79
constexpr Lookup_Rule< 2, 2 > make_wolfram_elementary_rule(std::uint8_t rule_no) noexcept
Build the elementary 1D Wolfram rule rule_no (0..255) as a Lookup_Rule<2, 2> over neighbourhood {-1,...
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Out-of-range neighbours behave as if the lattice ended.
Definition ca-traits.H:119
gsl_rng * r
C++20 concepts for the Cellular Automata module.
Cellular automata lattice with pluggable boundary policies.
Neighborhoods catalogue for Aleph::CA.
Rule mechanisms for Aleph::CA.
Dense, contiguous storage for cellular automata cells (1D/2D/3D).
DynList< int > l