Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_ca_ghost_lattice_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
50#include <array>
51#include <cstdint>
52#include <stdexcept>
53
54#include <gtest/gtest.h>
55
56#include <ca-traits.H>
57#include <tpl_ca_concepts.H>
58#include <tpl_ca_storage.H>
59#include <tpl_ca_lattice.H>
61#include <tpl_ca_neighborhood.H>
62#include <tpl_ca_rule.H>
63#include <tpl_ca_engine.H>
64
65using namespace Aleph;
66using namespace Aleph::CA;
67
68// ---------------------------------------------------------------------------
69// Concept conformance.
70// ---------------------------------------------------------------------------
71
76static_assert(
78
79// ---------------------------------------------------------------------------
80// Small helpers used throughout this file.
81// ---------------------------------------------------------------------------
82
83namespace
84{
86 template <typename Boundary, std::size_t H = 1>
89 {
90 Ghost_Lattice<Dense_Cell_Storage<int, 2>, Boundary, H> g({ 3, 3 }, 0);
91 for (ca_index_t i = 0; i < 3; ++i)
92 for (ca_index_t j = 0; j < 3; ++j)
93 g.set({ i, j }, static_cast<int>(i * 10 + j));
94 g.refresh_halo();
95 return g;
96 }
97
99 template <typename Boundary>
102 {
103 Lattice<Dense_Cell_Storage<int, 2>, Boundary> lat({ 3, 3 }, 0);
104 for (ca_index_t i = 0; i < 3; ++i)
105 for (ca_index_t j = 0; j < 3; ++j)
106 lat.set({ i, j }, static_cast<int>(i * 10 + j));
107 return lat;
108 }
109}
110
111// ---------------------------------------------------------------------------
112// Strict access.
113// ---------------------------------------------------------------------------
114
116{
118 for (ca_index_t i = 0; i < 3; ++i)
119 for (ca_index_t j = 0; j < 3; ++j)
120 EXPECT_EQ(g.at({ i, j }), i * 10 + j);
121}
122
124{
126 EXPECT_THROW(g.at({ -1, 0 }), std::out_of_range);
127 EXPECT_THROW(g.at({ 3, 0 }), std::out_of_range);
128 EXPECT_THROW(g.set({ -1, 0 }, 1), std::out_of_range);
129 EXPECT_THROW(g.set({ 0, 3 }, 1), std::out_of_range);
130}
131
133{
135 EXPECT_EQ(g.dimension(), 2u);
136 EXPECT_EQ(g.size(), 4u * 5u);
137 EXPECT_EQ(g.size(0), 4u);
138 EXPECT_EQ(g.size(1), 5u);
139 EXPECT_EQ(g.extents()[0], 4u);
140 EXPECT_EQ(g.extents()[1], 5u);
141 EXPECT_EQ(g.store_extents()[0], 4u + 4u); // +2*halo
142 EXPECT_EQ(g.store_extents()[1], 5u + 4u);
143 EXPECT_EQ(g.halo_radius(), 2u);
144}
145
147{
149 EXPECT_THROW(g.size(2), std::out_of_range);
150}
151
153{
155
156 EXPECT_EQ(g.dimension(), 1u);
157 EXPECT_EQ(g.size(), 0u);
158 EXPECT_EQ(g.size(0), 0u);
159 EXPECT_EQ(g.extents()[0], 0u);
160 EXPECT_EQ(g.store_extents()[0], 0u);
161 EXPECT_EQ(g.halo_radius(), 2u);
162 EXPECT_EQ(g.storage().size(), 0u);
163
164 EXPECT_EQ(g.at_safe({0}), 0);
165 EXPECT_EQ(g.at_safe({-1}), 0);
166 EXPECT_NO_THROW(g.refresh_halo());
167 EXPECT_NO_THROW(g.fill(3));
168 EXPECT_EQ(g.size(), 0u);
169 EXPECT_EQ(g.storage().size(), 0u);
170}
171
173{
175 Ghost row_empty({0, 5}, 9);
176 Ghost col_empty({5, 0}, 9);
177
178 for (Ghost *g : {&row_empty, &col_empty})
179 {
180 EXPECT_EQ(g->dimension(), 2u);
181 EXPECT_EQ(g->size(), 0u);
182 EXPECT_EQ(g->storage().size(), 0u);
183 EXPECT_EQ(g->halo_radius(), 1u);
184
185 EXPECT_EQ(g->at_safe({0, 0}), 0);
186 EXPECT_EQ(g->at_safe({-1, 0}), 0);
187 EXPECT_EQ(g->at_safe({0, -1}), 0);
188 EXPECT_NO_THROW(g->refresh_halo());
189 EXPECT_NO_THROW(g->fill(4));
190 EXPECT_EQ(g->size(), 0u);
191 EXPECT_EQ(g->storage().size(), 0u);
192 }
193
194 EXPECT_EQ(row_empty.extents()[0], 0u);
195 EXPECT_EQ(row_empty.extents()[1], 5u);
196 EXPECT_EQ(row_empty.store_extents()[0], 0u);
197 EXPECT_EQ(row_empty.store_extents()[1], 7u);
198
199 EXPECT_EQ(col_empty.extents()[0], 5u);
200 EXPECT_EQ(col_empty.extents()[1], 0u);
201 EXPECT_EQ(col_empty.store_extents()[0], 7u);
202 EXPECT_EQ(col_empty.store_extents()[1], 0u);
203}
204
205// ---------------------------------------------------------------------------
206// fill(): the user-visible region is set, the halo remains untouched
207// until refresh_halo is called again.
208// ---------------------------------------------------------------------------
209
211{
213 g.refresh_halo();
214 // Halo is 9s after the initial refresh.
215 EXPECT_EQ(g.at_safe({ -1, 0 }), 9);
216 EXPECT_EQ(g.at_safe({ 3, 0 }), 9);
217
218 g.fill(4);
219 // Interior is now 4. Halo is still 9 (fill leaves halo untouched).
220 EXPECT_EQ(g.at({ 1, 1 }), 4);
221 EXPECT_EQ(g.at_safe({ -1, 1 }), 9);
222
223 g.refresh_halo();
224 // After refresh the halo still keeps its constant 9 (constant policy).
225 EXPECT_EQ(g.at_safe({ -1, 1 }), 9);
226}
227
228// ---------------------------------------------------------------------------
229// refresh_halo for every policy produces the right cells.
230// ---------------------------------------------------------------------------
231
233{
235 // Every halo cell must be zero.
236 EXPECT_EQ(g.at_safe({ -1, 0 }), 0);
237 EXPECT_EQ(g.at_safe({ -1, -1 }), 0);
238 EXPECT_EQ(g.at_safe({ 3, 3 }), 0);
239 EXPECT_EQ(g.at_safe({ 0, 3 }), 0);
240 // Interior still intact.
241 EXPECT_EQ(g.at_safe({ 1, 1 }), 11);
242}
243
245{
246 using Boundary = ConstantBoundary<int, 42>;
247 Ghost_Lattice<Dense_Cell_Storage<int, 2>, Boundary, 1> g({ 3, 3 }, 0);
248 for (ca_index_t i = 0; i < 3; ++i)
249 for (ca_index_t j = 0; j < 3; ++j)
250 g.set({ i, j }, static_cast<int>(i * 10 + j));
251 g.refresh_halo();
252
253 EXPECT_EQ(g.at_safe({ -1, 0 }), 42);
254 EXPECT_EQ(g.at_safe({ 3, 0 }), 42);
255 EXPECT_EQ(g.at_safe({ -1, -1 }), 42);
256 EXPECT_EQ(g.at_safe({ 3, 3 }), 42);
257 EXPECT_EQ(g.at_safe({ 1, 1 }), 11);
258}
259
261{
264
265 // Single-axis wrap on each side.
266 EXPECT_EQ(g.at_safe({ -1, 1 }), lat.at_safe({ -1, 1 }));
267 EXPECT_EQ(g.at_safe({ 3, 1 }), lat.at_safe({ 3, 1 }));
268 EXPECT_EQ(g.at_safe({ 1, -1 }), lat.at_safe({ 1, -1 }));
269 EXPECT_EQ(g.at_safe({ 1, 3 }), lat.at_safe({ 1, 3 }));
270
271 // Corners.
272 EXPECT_EQ(g.at_safe({ -1, -1 }), lat.at_safe({ -1, -1 }));
273 EXPECT_EQ(g.at_safe({ 3, 3 }), lat.at_safe({ 3, 3 }));
274}
275
277{
280
281 // First halo cell on each side mirrors the closest interior cell.
282 EXPECT_EQ(g.at_safe({ -1, 1 }), lat.at({ 0, 1 }));
283 EXPECT_EQ(g.at_safe({ 3, 1 }), lat.at({ 2, 1 }));
284 EXPECT_EQ(g.at_safe({ 1, -1 }), lat.at({ 1, 0 }));
285 EXPECT_EQ(g.at_safe({ 1, 3 }), lat.at({ 1, 2 }));
286
287 // Corners.
288 EXPECT_EQ(g.at_safe({ -1, -1 }), lat.at({ 0, 0 }));
289 EXPECT_EQ(g.at_safe({ 3, 3 }), lat.at({ 2, 2 }));
290}
291
293{
295
296 // Clamp: -1 -> 0, 3 -> 2.
297 EXPECT_EQ(g.at_safe({ -1, 1 }), g.at({ 0, 1 }));
298 EXPECT_EQ(g.at_safe({ 3, 1 }), g.at({ 2, 1 }));
299 EXPECT_EQ(g.at_safe({ 1, -1 }), g.at({ 1, 0 }));
300 EXPECT_EQ(g.at_safe({ 1, 3 }), g.at({ 1, 2 }));
301 // Corners.
302 EXPECT_EQ(g.at_safe({ -1, -1 }), g.at({ 0, 0 }));
303 EXPECT_EQ(g.at_safe({ 3, 3 }), g.at({ 2, 2 }));
304}
305
306// ---------------------------------------------------------------------------
307// at_safe equivalence: Ghost_Lattice<B> and Lattice<B> must agree for
308// every coordinate inside the halo AND every user-visible coordinate.
309// ---------------------------------------------------------------------------
310
311template <typename Boundary>
313{
316
317 // Walk every coordinate from -2 to 4 inclusive (covers interior +
318 // halo + slow-path territory).
319 for (ca_index_t i = -2; i <= 4; ++i)
320 for (ca_index_t j = -2; j <= 4; ++j)
321 EXPECT_EQ(g.at_safe({ i, j }), l.at_safe({ i, j }))
322 << "mismatch at (" << i << ", " << j << ")";
323}
324
341
343{
344 using Boundary = ConstantBoundary<int, 77>;
345 Ghost_Lattice<Dense_Cell_Storage<int, 2>, Boundary, 1> g({ 3, 3 }, 0);
346 Lattice<Dense_Cell_Storage<int, 2>, Boundary> l({ 3, 3 }, 0);
347 for (ca_index_t i = 0; i < 3; ++i)
348 for (ca_index_t j = 0; j < 3; ++j)
349 {
350 g.set({ i, j }, static_cast<int>(i * 10 + j));
351 l.set({ i, j }, static_cast<int>(i * 10 + j));
352 }
353 g.refresh_halo();
354 for (ca_index_t i = -2; i <= 4; ++i)
355 for (ca_index_t j = -2; j <= 4; ++j)
356 EXPECT_EQ(g.at_safe({ i, j }), l.at_safe({ i, j }))
357 << "mismatch at (" << i << ", " << j << ")";
358}
359
360// ---------------------------------------------------------------------------
361// Halo radius > 1: the halo has more than one ring.
362// ---------------------------------------------------------------------------
363
365{
366 // Halo = 2 so -2 is valid as a no-branch fast-path read.
369
370 // Inner halo ring (|offset| == 1).
371 for (ca_index_t i = -1; i <= 3; ++i)
372 for (ca_index_t j = -1; j <= 3; ++j)
373 EXPECT_EQ(g.at_safe({ i, j }), l.at_safe({ i, j }));
374
375 // Outer halo ring (|offset| == 2).
376 EXPECT_EQ(g.at_safe({ -2, 1 }), l.at_safe({ -2, 1 }));
377 EXPECT_EQ(g.at_safe({ 4, 1 }), l.at_safe({ 4, 1 }));
378 EXPECT_EQ(g.at_safe({ 1, -2 }), l.at_safe({ 1, -2 }));
379 EXPECT_EQ(g.at_safe({ 1, 4 }), l.at_safe({ 1, 4 }));
380 EXPECT_EQ(g.at_safe({ -2, -2 }), l.at_safe({ -2, -2 }));
381 EXPECT_EQ(g.at_safe({ 4, 4 }), l.at_safe({ 4, 4 }));
382
383 // Beyond halo (|offset| >= 3) we hit the slow policy path, which
384 // must still agree with the classic lattice.
385 EXPECT_EQ(g.at_safe({ -3, 1 }), l.at_safe({ -3, 1 }));
386 EXPECT_EQ(g.at_safe({ 5, 1 }), l.at_safe({ 5, 1 }));
387}
388
389// ---------------------------------------------------------------------------
390// 1D lattice sanity: the same machinery scales down.
391// ---------------------------------------------------------------------------
392
394{
397 for (ca_index_t i = 0; i < 5; ++i)
398 {
399 g.set({ i }, static_cast<int>(i + 1));
400 l.set({ i }, static_cast<int>(i + 1));
401 }
402 g.refresh_halo();
403 for (ca_index_t i = -3; i <= 7; ++i)
404 EXPECT_EQ(g.at_safe({ i }), l.at_safe({ i }));
405}
406
407// ---------------------------------------------------------------------------
408// 3D lattice sanity: every corner of the halo is populated.
409// ---------------------------------------------------------------------------
410
412{
415 int v = 1;
416 for (ca_index_t i = 0; i < 2; ++i)
417 for (ca_index_t j = 0; j < 2; ++j)
418 for (ca_index_t k = 0; k < 2; ++k, ++v)
419 {
420 g.set({ i, j, k }, v);
421 l.set({ i, j, k }, v);
422 }
423 g.refresh_halo();
424
425 for (ca_index_t i = -1; i <= 2; ++i)
426 for (ca_index_t j = -1; j <= 2; ++j)
427 for (ca_index_t k = -1; k <= 2; ++k)
428 EXPECT_EQ(g.at_safe({ i, j, k }), l.at_safe({ i, j, k }));
429}
430
431// ---------------------------------------------------------------------------
432// swap and copy semantics.
433// ---------------------------------------------------------------------------
434
436{
439 a.swap(b);
440 EXPECT_EQ(a.size(0), 3u);
441 EXPECT_EQ(b.size(0), 2u);
442 EXPECT_EQ(a.at({ 0, 0 }), 9);
443 EXPECT_EQ(b.at({ 0, 0 }), 1);
444}
445
446// ---------------------------------------------------------------------------
447// Engine equivalence (the Phase 4 acceptance criterion).
448//
449// Running the classic synchronous engine for >= 100 steps on a regular
450// `Lattice` and on a `Ghost_Lattice` must produce identical frames,
451// for every supported boundary policy.
452// ---------------------------------------------------------------------------
453
454namespace
455{
457 inline void seed_rpentomino(auto &lat, ca_index_t rows, ca_index_t cols)
458 {
459 const ca_index_t ci = rows / 2, cj = cols / 2;
460 lat.set({ ci - 1, cj }, 1);
461 lat.set({ ci - 1, cj + 1 }, 1);
462 lat.set({ ci, cj - 1 }, 1);
463 lat.set({ ci, cj }, 1);
464 lat.set({ ci + 1, cj }, 1);
465 }
466
467 template <typename L>
468 bool frames_match(const L &a, const L &b)
469 {
470 if (a.size(0) != b.size(0) or a.size(1) != b.size(1))
471 return false;
472 for (ca_index_t i = 0; i < static_cast<ca_index_t>(a.size(0)); ++i)
473 for (ca_index_t j = 0; j < static_cast<ca_index_t>(a.size(1)); ++j)
474 if (a.at({ i, j }) != b.at({ i, j }))
475 return false;
476 return true;
477 }
478
479 template <typename A, typename B>
480 bool frames_match_cross(const A &a, const B &b)
481 {
482 if (a.size(0) != b.size(0) or a.size(1) != b.size(1))
483 return false;
484 for (ca_index_t i = 0; i < static_cast<ca_index_t>(a.size(0)); ++i)
485 for (ca_index_t j = 0; j < static_cast<ca_index_t>(a.size(1)); ++j)
486 if (a.at({ i, j }) != b.at({ i, j }))
487 return false;
488 return true;
489 }
490}
491
492template <typename Boundary>
493static void run_engine_equivalence(std::size_t steps)
494{
495 constexpr ca_size_t ROWS = 16;
496 constexpr ca_size_t COLS = 16;
497
501
502 ClassicL l_init({ ROWS, COLS }, 0);
503 GhostL g_init({ ROWS, COLS }, 0);
506
509
512
513 for (std::size_t s = 0; s < steps; ++s)
514 {
515 classic.step();
516 ghost.step();
518 << "frames diverge at step " << s + 1;
519 }
520}
521
526
531
536
541
543{
544 // With V = 0 the Dirichlet halo is indistinguishable from `Open`.
546}
547
549{
550 // A non-zero Dirichlet floods the border with alive neighbours, so
551 // the grid saturates quickly. We still verify frame equivalence.
553}
554
555// ---------------------------------------------------------------------------
556// Halo radius 2 with the engine: a Von_Neumann radius-2 rule must work
557// without the slow path kicking in (since halo radius >= neighbourhood).
558// ---------------------------------------------------------------------------
559
560// A trivial totalistic rule that counts alive neighbours mod 2.
561// Defined at namespace scope because local classes may not have member
562// templates, and `Outer_Totalistic_Rule::operator()` is templated.
563namespace
564{
565 struct R2_Parity_Functor
566 {
567 constexpr int operator()(const int &, std::size_t alive) const noexcept
568 {
569 return alive % 2 == 0 ? 0 : 1;
570 }
571 };
572}
573
575{
576 constexpr ca_size_t ROWS = 8;
577 constexpr ca_size_t COLS = 8;
578
580 using Boundary = ConstantBoundary<int, 0>;
582 using Nbh = Von_Neumann<2, 2>;
583
586
587 ClassicL l_init({ ROWS, COLS }, 0);
588 GhostL g_init({ ROWS, COLS }, 0);
589 // Seed a single alive cell in the middle.
590 l_init.set({ 4, 4 }, 1);
591 g_init.set({ 4, 4 }, 1);
592
594 std::move(l_init), ParityRule(R2_Parity_Functor{}), Nbh{});
596 std::move(g_init), ParityRule(R2_Parity_Functor{}), Nbh{});
597
598 for (std::size_t s = 0; s < 60; ++s)
599 {
600 classic.step();
601 ghost.step();
603 << "R=2 frames diverge at step " << s + 1;
604 }
605}
size_t steps
Definition ca-c-api.h:126
size_t * rows
Definition ca-c-api.h:112
size_t cols
Definition ca-c-api.h:105
Common typedefs and tag types for the Cellular Automata module.
Row-major dense storage for N-dimensional cellular automata.
Lattice with Halo ghost layers around the user-visible cells.
void set(const coord_type &c, const state_type &v)
Strict write through a user coordinate.
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
Synchronous double-buffered engine.
void step()
Apply the rule to every cell once and swap buffers.
Von Neumann (L1) neighborhood of radius R in N dimensions.
#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::ptrdiff_t ca_index_t
Signed coordinate component used by lattices and neighborhoods.
Definition ca-traits.H:60
std::size_t ca_size_t
Unsigned size component used for extents and counts.
Definition ca-traits.H:63
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
Constant-value boundary.
Definition ca-traits.H:138
Zero-gradient (Neumann) boundary.
Definition ca-traits.H:152
Out-of-range neighbours behave as if the lattice ended.
Definition ca-traits.H:119
Out-of-range coordinates mirror back into the lattice.
Definition ca-traits.H:129
The lattice wraps around on every axis.
Definition ca-traits.H:124
static int * k
C++20 concepts for the Cellular Automata module.
Synchronous double-buffered engine for cellular automata.
Ghost-layer lattice for high-performance boundary handling.
static void expect_equivalent_at_safe()
static void run_engine_equivalence(std::size_t steps)
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