Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ca_visual_sinks_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
18#include <atomic>
19#include <chrono>
20#include <cstdint>
21#include <filesystem>
22#include <fstream>
23#include <sstream>
24#include <string>
25#include <vector>
26#if defined(_WIN32)
27# include <process.h>
28#else
29# include <unistd.h>
30#endif
31
32#include <gtest/gtest.h>
33
34#include <ca-ascii.H>
35#include <ca-dot.H>
36#include <ca-npy.H>
37#include <ca-png.H>
38#include <ca-svg.H>
39#include <ca-vtk.H>
41#include <tpl_ca_hex_lattice.H>
42#include <tpl_ca_lattice.H>
43#include <tpl_ca_storage.H>
45
46using namespace Aleph::CA;
47
48namespace
49{
54
55 struct DecodedPNG
56 {
57 std::uint32_t width = 0;
58 std::uint32_t height = 0;
59 std::vector<std::uint8_t> raw;
60 };
61
62 Grid make_frame()
63 {
64 Grid g({2, 3}, 0);
65 g.set({0, 1}, 1);
66 g.set({1, 2}, 1);
67 return g;
68 }
69
71 long long process_id() noexcept
72 {
73#if defined(_WIN32)
74 return static_cast<long long>(_getpid());
75#else
76 return static_cast<long long>(getpid());
77#endif
78 }
79
80 std::filesystem::path unique_tmp_dir(const std::string &name)
81 {
82 // A steady_clock tick alone is unique *within* a process, but not
83 // across the several processes that actually run this suite (each
84 // TEST() is its own ctest process, and CI runs ctest with
85 // --parallel) -- two processes' calls landing in the same clock
86 // tick produce the identical name and race on the same directory.
87 // Mixing in the process id and a per-process counter closes that
88 // gap regardless of clock resolution or call count.
89 static std::atomic<unsigned long long> counter{0};
90 const auto tick = std::chrono::steady_clock::now().time_since_epoch().count();
91 return std::filesystem::temp_directory_path()
92 / (name + "_" + std::to_string(static_cast<long long>(tick)) +
93 "_" + std::to_string(process_id()) +
94 "_" + std::to_string(counter++));
95 }
96
97 std::string read_file(const std::filesystem::path &path, const bool binary = false)
98 {
99 std::ifstream in(path, binary ? std::ios::binary : std::ios::in);
100 std::ostringstream out;
101 out << in.rdbuf();
102 return out.str();
103 }
104
105 std::uint32_t be32(const std::string &s, std::size_t pos)
106 {
107 return (static_cast<std::uint32_t>(static_cast<unsigned char>(s[pos])) << 24)
108 | (static_cast<std::uint32_t>(static_cast<unsigned char>(s[pos + 1])) << 16)
109 | (static_cast<std::uint32_t>(static_cast<unsigned char>(s[pos + 2])) << 8)
110 | static_cast<std::uint32_t>(static_cast<unsigned char>(s[pos + 3]));
111 }
112
113 DecodedPNG decode_native_png(const std::string &png)
114 {
115 if (png.size() < 8u)
116 {
117 ADD_FAILURE() << "PNG too short";
118 return {};
119 }
120 EXPECT_EQ(png.substr(0, 8), std::string("\x89PNG\r\n\x1a\n", 8));
121
122 DecodedPNG decoded;
123 std::string idat;
124 std::size_t pos = 8;
125 while (pos + 12 <= png.size())
126 {
127 const std::uint32_t len = be32(png, pos);
128 const std::string type = png.substr(pos + 4, 4);
129 const std::string payload = png.substr(pos + 8, len);
130 if (type == "IHDR")
131 {
132 decoded.width = be32(payload, 0);
133 decoded.height = be32(payload, 4);
134 EXPECT_EQ(static_cast<unsigned char>(payload[8]), 8u);
135 EXPECT_EQ(static_cast<unsigned char>(payload[9]), 2u);
136 }
137 else if (type == "IDAT")
138 {
139 idat += payload;
140 }
141 else if (type == "IEND")
142 {
143 break;
144 }
145 pos += 12 + len;
146 }
147
148 if (idat.size() < 6u)
149 {
150 ADD_FAILURE() << "IDAT too short";
151 return decoded;
152 }
153 std::size_t z = 2;
154 while (z + 5 <= idat.size() - 4)
155 {
156 const bool final = (static_cast<unsigned char>(idat[z]) & 1u) != 0;
157 if ((static_cast<unsigned char>(idat[z]) & 0x06u) != 0u)
158 {
159 ADD_FAILURE() << "compressed PNG block was not stored";
160 return decoded;
161 }
162 ++z;
163 const std::uint16_t len = static_cast<std::uint16_t>(
164 static_cast<unsigned char>(idat[z])
165 | (static_cast<unsigned char>(idat[z + 1]) << 8));
166 const std::uint16_t nlen = static_cast<std::uint16_t>(
167 static_cast<unsigned char>(idat[z + 2])
168 | (static_cast<unsigned char>(idat[z + 3]) << 8));
169 EXPECT_EQ(static_cast<std::uint16_t>(~len), nlen);
170 z += 4;
171 if (z + len > idat.size())
172 {
173 ADD_FAILURE() << "stored block length exceeds IDAT";
174 return decoded;
175 }
176 decoded.raw.insert(decoded.raw.end(), idat.begin() + static_cast<std::ptrdiff_t>(z),
177 idat.begin() + static_cast<std::ptrdiff_t>(z + len));
178 z += len;
179 if (final)
180 break;
181 }
182 return decoded;
183 }
184
185 std::size_t count_substr(const std::string &s, const std::string &needle)
186 {
187 std::size_t count = 0;
188 std::size_t pos = 0;
189 while ((pos = s.find(needle, pos)) != std::string::npos)
190 {
191 ++count;
192 pos += needle.size();
193 }
194 return count;
195 }
196}
197
199{
200 std::ostringstream out;
201 write_png(out, make_frame(), Binary_RGB_Mapper<int>{0, {255, 255, 255}, {0, 0, 0}});
202
203 const DecodedPNG png = decode_native_png(out.str());
204 EXPECT_EQ(png.width, 3u);
205 EXPECT_EQ(png.height, 2u);
206 ASSERT_EQ(png.raw.size(), 2u * (1u + 3u * 3u));
207 EXPECT_EQ(png.raw[0], 0u);
208 EXPECT_EQ(png.raw[1], 255u);
209 EXPECT_EQ(png.raw[4], 0u);
210 EXPECT_EQ(png.raw[10], 0u);
211}
212
214{
215 const std::string rect = render_lattice_svg_string(
216 make_frame(), Binary_RGB_Mapper<int>{0, {250, 250, 250}, {10, 40, 200}});
217 EXPECT_NE(rect.find("<svg"), std::string::npos);
218 EXPECT_EQ(count_substr(rect, "<rect "), 6u);
219 EXPECT_NE(rect.find("fill=\"#0a28c8\""), std::string::npos);
220
221 HexGrid hex({2, 2}, 0);
222 hex.set_axial({1, 0}, 1);
223 const std::string hs = render_lattice_svg_string(
224 hex, Binary_RGB_Mapper<int>{0, {255, 255, 255}, {255, 120, 0}});
225 EXPECT_EQ(count_substr(hs, "<polygon "), 4u);
226
227 TriGrid tri({2, 2}, 0);
228 tri.set_tri({1, 1}, 1);
229 const std::string ts = render_lattice_svg_string(
230 tri, Binary_RGB_Mapper<int>{0, {255, 255, 255}, {0, 160, 190}});
231 EXPECT_EQ(count_substr(ts, "<polygon "), 4u);
232}
233
235{
236 std::ostringstream out;
238 const std::string npy = out.str();
239
240 ASSERT_GE(npy.size(), 10u);
241 EXPECT_EQ(npy.substr(0, 6), std::string("\x93NUMPY", 6));
242 EXPECT_EQ(static_cast<unsigned char>(npy[6]), 1u);
243 const std::uint16_t header_len = static_cast<std::uint16_t>(
244 static_cast<unsigned char>(npy[8]) | (static_cast<unsigned char>(npy[9]) << 8));
245 ASSERT_GE(npy.size(), 10u + header_len);
246 const std::string header = npy.substr(10, header_len);
247 EXPECT_NE(header.find("'shape': (2, 3)"), std::string::npos);
248 EXPECT_NE(header.find("'fortran_order': False"), std::string::npos);
249 EXPECT_EQ(npy.size(), 10u + header_len + 6u * sizeof(int));
250}
251
253{
254 Grid3 g({2, 2, 2}, 0);
255 g.set({1, 0, 1}, 7);
256 std::ostringstream out;
258 opts.scalar_name = "alive";
260 const std::string vtk = out.str();
261 EXPECT_NE(vtk.find("DATASET STRUCTURED_POINTS"), std::string::npos);
262 EXPECT_NE(vtk.find("DIMENSIONS 2 2 2"), std::string::npos);
263 EXPECT_NE(vtk.find("POINT_DATA 8"), std::string::npos);
264 EXPECT_NE(vtk.find("SCALARS alive int 1"), std::string::npos);
265 EXPECT_NE(vtk.find("\n7\n"), std::string::npos);
266}
267
269{
270 const Grid frame = make_frame();
271 EXPECT_EQ(render_ascii_string(frame), ".#.\n..#\n");
272
274 opts.include_border = true;
275 EXPECT_EQ(render_ascii_string(frame, opts), "+---+\n|.#.|\n|..#|\n+---+\n");
276}
277
279{
281 graph.set_node(1, 1);
282 const std::string dot = render_graph_lattice_dot_string(
283 graph, Binary_DOT_Palette<int>{0, "white", "black"});
284 EXPECT_NE(dot.find("graph CA"), std::string::npos);
285 EXPECT_NE(dot.find("n1 [fillcolor=\"black\""), std::string::npos);
286 EXPECT_NE(dot.find("n0 -- n1"), std::string::npos);
287 EXPECT_NE(dot.find("n1 -- n2"), std::string::npos);
288 EXPECT_EQ(count_substr(dot, "--"), 2u);
289}
290
292{
293 const auto dir = unique_tmp_dir("aleph_ca_visual_sinks");
294 std::filesystem::remove_all(dir);
295 const Grid frame = make_frame();
296
297 Png_Frame_Sink png(dir / "png_{step}.png",
298 Binary_RGB_Mapper<int>{0, {255, 255, 255}, {0, 0, 0}},
299 3);
300 Npy_Frame_Sink npy(dir / "npy_{step}.npy", 3);
301 VTK_Frame_Sink vtk(dir / "vtk_{step}.vtk", {}, 3);
302 Svg_Frame_Sink svg(dir / "svg_{step}.svg",
303 Binary_RGB_Mapper<int>{0, {255, 255, 255}, {0, 0, 0}},
304 {},
305 3);
306
307 png.accept(4, frame);
308 npy.accept(4, frame);
309 vtk.accept(4, frame);
310 svg.accept(4, frame);
311
312 EXPECT_EQ(read_file(dir / "png_004.png", true).substr(1, 3), "PNG");
313 EXPECT_EQ(read_file(dir / "npy_004.npy", true).substr(1, 5), "NUMPY");
314 EXPECT_NE(read_file(dir / "vtk_004.vtk").find("STRUCTURED_POINTS"), std::string::npos);
315 EXPECT_NE(read_file(dir / "svg_004.svg").find("<svg"), std::string::npos);
316
317 std::filesystem::remove_all(dir);
318}
Terminal and golden-test renderers for CA frames.
size_t size_t int32_t * out
Definition ca-c-api.h:120
GraphViz DOT renderer for graph cellular automata.
NumPy .npy writer for CA frames.
Dependency-free PNG frame sink for cellular automata.
Native SVG renderer for rectangular, hexagonal and triangular CAs.
VTK legacy writer for scientific CA visualisation.
Graph lattice: one cell per node + precomputed adjacency.
void set_node(std::size_t n, const T &v)
Direct write at node id n.
Hexagonal lattice with axial accessors over a 2D storage.
Lattice that adds boundary-aware access on top of a storage.
Path-pattern sink that writes one .npy file per frame.
Definition ca-npy.H:184
Path-pattern sink that writes one PNG file per accepted frame.
Definition ca-png.H:241
Path-pattern sink that writes one SVG file per accepted frame.
Definition ca-svg.H:365
void accept(const std::size_t step, const Lattice &frame)
Write one SVG frame using the default dead state.
Definition ca-svg.H:401
Triangular lattice with parity helpers over a 2D storage.
Path-pattern sink that writes VTK legacy frames.
Definition ca-vtk.H:174
#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
Native_Png decode_native_png(std::istream &in)
Decode a PNG emitted by Aleph::CA::write_png.
void write_vtk_legacy(std::ostream &out, const Lattice &frame, const VTK_Write_Options &opts={})
Write a 2D or 3D lattice as VTK legacy STRUCTURED_POINTS.
Definition ca-vtk.H:119
std::string render_ascii_string(const Lattice &frame, Palette &&palette, const Ascii_Render_Options &opts={})
Return the text rendering of a rank-2 frame.
Definition ca-ascii.H:237
Array< Array< std::size_t > > make_path_graph_adjacency(std::size_t n, bool cycle=false)
Build the adjacency of a path graph with n nodes.
std::string render_graph_lattice_dot_string(const Graph &graph, Palette &&palette, const DOT_Render_Options &opts={})
Return a graph frame rendered as DOT.
Definition ca-dot.H:207
void write_png(std::ostream &out, const Lattice &frame, Mapper &&mapper)
Write a rank-2 frame as an 8-bit RGB PNG image.
Definition ca-png.H:171
std::string render_lattice_svg_string(const Lattice &frame, Palette &&palette, const SVG_Render_Options &opts={}, const typename Lattice::state_type &dead_state=typename Lattice::state_type{})
Return a frame rendered as SVG.
Definition ca-svg.H:344
void write_npy(std::ostream &out, const Lattice &frame)
Write a lattice frame as a NumPy .npy array.
Definition ca-npy.H:151
Itor find(const Itor &beg, const Itor &end, const T &value)
Find the first element equal to a value.
Definition ahAlgo.H:230
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
STL namespace.
Options for textual CA rendering.
Definition ca-ascii.H:67
bool include_border
draw a simple +---+ border
Definition ca-ascii.H:68
Default binary DOT fill-colour palette.
Definition ca-dot.H:75
Default binary RGB mapper.
Definition ca-io.H:245
Out-of-range neighbours behave as if the lattice ended.
Definition ca-traits.H:119
Options for VTK legacy output.
Definition ca-vtk.H:62
std::string scalar_name
scalar field name
Definition ca-vtk.H:64
static long counter
Definition test-splice.C:40
CA whose underlying topology is an arbitrary undirected graph.
Hexagonal lattice with axial / offset / cube coordinate conversions and a TikZ-compatible pixel mappi...
Cellular automata lattice with pluggable boundary policies.
Dense, contiguous storage for cellular automata cells (1D/2D/3D).
Triangular lattice with up/down parity helpers and pixel mapping suitable for visualisation.