Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
tpl_ca_hashlife.H
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
70#ifndef TPL_CA_HASHLIFE_H
71#define TPL_CA_HASHLIFE_H
72
73#include <algorithm>
74#include <bit>
75#include <cctype>
76#include <cstdint>
77#include <deque>
78#include <istream>
79#include <limits>
80#include <optional>
81#include <ostream>
82#include <sstream>
83#include <string>
84#include <unordered_map>
85#include <utility>
86
87#include <ah-errors.H>
88#include <tpl_array.H>
89#include <tpl_sort_utils.H>
90
91namespace Aleph {
92namespace CA {
93
108{
109 std::uint16_t birth_mask;
110 std::uint16_t survival_mask;
111
113 [[nodiscard]] constexpr bool apply(const bool current, const std::uint8_t n) const noexcept
114 {
115 return current ? (((survival_mask >> n) & 1u) != 0) : (((birth_mask >> n) & 1u) != 0);
116 }
117
118 [[nodiscard]] constexpr bool operator==(const Outer_Totalistic_Binary_Rule &) const noexcept
119 = default;
120};
121
124 static_cast<std::uint16_t>(1u << 3), static_cast<std::uint16_t>((1u << 2) | (1u << 3))};
125
128 static_cast<std::uint16_t>((1u << 3) | (1u << 6)),
129 static_cast<std::uint16_t>((1u << 2) | (1u << 3))};
130
133 static_cast<std::uint16_t>((1u << 3) | (1u << 6) | (1u << 7) | (1u << 8)),
134 static_cast<std::uint16_t>((1u << 3) | (1u << 4) | (1u << 6) | (1u << 7) | (1u << 8))};
135
137inline constexpr Outer_Totalistic_Binary_Rule Seeds{static_cast<std::uint16_t>(1u << 2),
138 std::uint16_t{0}};
139
146{
147 std::string s;
148 s.reserve(20);
149 s.push_back('B');
150 for (int i = 0; i <= 8; ++i)
151 if (((r.birth_mask >> i) & 1u) != 0)
152 s.push_back(static_cast<char>('0' + i));
153 s.push_back('/');
154 s.push_back('S');
155 for (int i = 0; i <= 8; ++i)
156 if (((r.survival_mask >> i) & 1u) != 0)
157 s.push_back(static_cast<char>('0' + i));
158 return s;
159}
160
168[[nodiscard]] inline std::optional<Outer_Totalistic_Binary_Rule> parse_rule(const std::string &s)
169{
170 std::uint16_t birth = 0, survival = 0;
171 std::size_t i = 0;
172 while (i < s.size() and std::isspace(static_cast<unsigned char>(s[i])))
173 ++i;
174 if (i < s.size() and (s[i] == 'B' or s[i] == 'b'))
175 {
176 ++i;
177 while (i < s.size() and std::isdigit(static_cast<unsigned char>(s[i])))
178 {
179 birth |= static_cast<std::uint16_t>(1u << (s[i] - '0'));
180 ++i;
181 }
182 if (i < s.size() and s[i] == '/')
183 ++i;
184 if (i < s.size() and (s[i] == 'S' or s[i] == 's'))
185 ++i;
186 while (i < s.size() and std::isdigit(static_cast<unsigned char>(s[i])))
187 {
188 survival |= static_cast<std::uint16_t>(1u << (s[i] - '0'));
189 ++i;
190 }
191 }
192 else
193 {
194 while (i < s.size() and std::isdigit(static_cast<unsigned char>(s[i])))
195 {
196 survival |= static_cast<std::uint16_t>(1u << (s[i] - '0'));
197 ++i;
198 }
199 if (i >= s.size() or s[i] != '/')
200 return std::nullopt;
201 ++i;
202 while (i < s.size() and std::isdigit(static_cast<unsigned char>(s[i])))
203 {
204 birth |= static_cast<std::uint16_t>(1u << (s[i] - '0'));
205 ++i;
206 }
207 }
209}
210
211namespace ca_hashlife_detail {
212
215struct Node
216{
217 std::uint8_t level;
218 std::uint8_t bits;
219 std::uint64_t population;
225};
226
229{
230 std::uint8_t level;
231 std::uint8_t bits;
236
237 [[nodiscard]] bool operator==(const Node_Key &o) const noexcept
238 {
239 return level == o.level and bits == o.bits and nw == o.nw and ne == o.ne and sw == o.sw
240 and se == o.se;
241 }
242};
243
245{
246 [[nodiscard]] std::size_t operator()(const Node_Key &k) const noexcept
247 {
248 auto mix = [](std::size_t a, std::size_t b) noexcept -> std::size_t
249 {
250 return a ^ (b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2));
251 };
252 std::size_t h = (static_cast<std::size_t>(k.level) << 8) | k.bits;
253 h = mix(h, reinterpret_cast<std::uintptr_t>(k.nw));
254 h = mix(h, reinterpret_cast<std::uintptr_t>(k.ne));
255 h = mix(h, reinterpret_cast<std::uintptr_t>(k.sw));
256 h = mix(h, reinterpret_cast<std::uintptr_t>(k.se));
257 return h;
258 }
259};
260
261} // namespace ca_hashlife_detail
262
301{
302public:
305
307 struct BBox
308 {
309 std::int64_t x_min;
310 std::int64_t y_min;
311 std::int64_t x_max;
312 std::int64_t y_max;
313
316 {
317 return x_min > x_max;
318 }
319
321 [[nodiscard]] std::int64_t width() const noexcept
322 {
323 return is_empty() ? std::int64_t{0} : (x_max - x_min + 1);
324 }
325
327 [[nodiscard]] std::int64_t height() const noexcept
328 {
329 return is_empty() ? std::int64_t{0} : (y_max - y_min + 1);
330 }
331 };
332
334 struct Stats
335 {
336 std::size_t canonical_nodes;
337 std::size_t result_hits;
338 std::size_t result_misses;
340 std::uint8_t root_level;
341 };
342
350 explicit Hashlife_Engine(Rule rule = Conway_Life, std::size_t cache_capacity = std::size_t{1} << 22)
352 {
354 << "Hashlife_Engine: cache_capacity (" << cache_capacity_ << ") below minimum 1024";
356 }
357
359 void set_rule(const Rule &r)
360 {
361 if (r == rule_)
362 return;
363 rule_ = r;
365 }
366
369 {
370 return rule_;
371 }
372
382 void set_alive(const std::int64_t x, const std::int64_t y, const bool alive = true)
383 {
384 while (true)
385 {
386 const std::int64_t half = std::int64_t{1} << (root_->level - 1);
387 if (x >= -half and x < half and y >= -half and y < half)
388 break;
389 root_ = expand(root_);
390 }
391 const std::int64_t half = std::int64_t{1} << (root_->level - 1);
392 root_ = set_in_node(root_, x + half, y + half, alive);
393 }
394
397 [[nodiscard]] bool alive_at(const std::int64_t x, const std::int64_t y) const
398 {
399 const std::int64_t half = std::int64_t{1} << (root_->level - 1);
401 return false;
402 return get_in_node(root_, x + half, y + half);
403 }
404
417 std::uint64_t advance(const unsigned k)
418 {
419 ah_domain_error_if(k > 62) << "Hashlife_Engine::advance: k=" << k << " exceeds 62";
420 const std::uint8_t min_level =
421 std::max<std::uint8_t>(static_cast<std::uint8_t>(k + 2), 4);
422 while (root_->level < min_level)
423 root_ = expand(root_);
424 while (not is_centered_well(root_))
425 root_ = expand(root_);
426
427 const std::uint8_t L = root_->level;
428 Node *const new_root = evolve(root_);
429 const std::uint64_t steps = std::uint64_t{1} << (L - 2);
430 generation_ += static_cast<std::int64_t>(steps);
431 root_ = new_root;
432
433 if (table_.size() > cache_capacity_)
435 return steps;
436 }
437
447 std::uint64_t run(std::uint64_t generations)
448 {
449 std::uint64_t advanced = 0;
450 while (generations > 0)
451 {
452 const unsigned k = static_cast<unsigned>(63 - std::countl_zero(generations));
453 const std::uint64_t step = advance(k);
454 advanced += step;
455 if (step >= generations)
456 break;
457 generations -= step;
458 }
459 return advanced;
460 }
461
463 [[nodiscard]] std::uint64_t population() const noexcept
464 {
465 return root_->population;
466 }
467
470 {
471 return generation_;
472 }
473
475 [[nodiscard]] BBox bbox() const
476 {
477 BBox bb{std::numeric_limits<std::int64_t>::max(), std::numeric_limits<std::int64_t>::max(),
478 std::numeric_limits<std::int64_t>::min(), std::numeric_limits<std::int64_t>::min()};
479 if (root_->population == 0)
480 return bb;
481 const std::int64_t half = std::int64_t{1} << (root_->level - 1);
482 visit_alive(root_, -half, -half, [&bb](std::int64_t x, std::int64_t y) noexcept
483 {
484 if (x < bb.x_min)
485 bb.x_min = x;
486 if (x > bb.x_max)
487 bb.x_max = x;
488 if (y < bb.y_min)
489 bb.y_min = y;
490 if (y > bb.y_max)
491 bb.y_max = y;
492 });
493 return bb;
494 }
495
497 void clear()
498 {
499 generation_ = 0;
501 }
502
508
511 {
512 return cache_capacity_;
513 }
514
522 template <typename F>
523 void for_each_alive(F &&f) const
524 {
525 if (root_->population == 0)
526 return;
527 const std::int64_t half = std::int64_t{1} << (root_->level - 1);
528 visit_alive(root_, -half, -half, std::forward<F>(f));
529 }
530
531 // ------------------------------------------------------------------
532 // RLE (Run-Length-Encoded) Conway-format I/O.
533 // ------------------------------------------------------------------
534
543 void load_rle(std::istream &in)
544 {
545 std::string line;
546 bool header_seen = false;
547 std::int64_t origin_x = 0;
548 std::int64_t origin_y = 0;
549 std::int64_t cur_x = 0;
550 std::int64_t cur_y = 0;
551 std::int64_t run = 0;
552 bool ended = false;
553
554 while (not ended and std::getline(in, line))
555 {
556 if (line.empty() or line[0] == '#')
557 continue;
558 if (not header_seen)
559 {
560 const std::int64_t x_size = parse_header_value(line, 'x');
561 const std::int64_t y_size = parse_header_value(line, 'y');
562 if (auto rule_opt = parse_rule_field(line); rule_opt)
563 rule_ = *rule_opt;
564 origin_x = -(x_size / 2);
565 origin_y = -(y_size / 2);
566 cur_x = origin_x;
567 cur_y = origin_y;
568 header_seen = true;
569 continue;
570 }
571 for (const char c : line)
572 {
573 if (std::isdigit(static_cast<unsigned char>(c)))
574 run = run * 10 + (c - '0');
575 else if (c == 'b' or c == '.')
576 {
577 cur_x += (run == 0 ? 1 : run);
578 run = 0;
579 }
580 else if (c == 'o' or c == 'A')
581 {
582 const std::int64_t r = (run == 0 ? 1 : run);
583 for (std::int64_t i = 0; i < r; ++i)
584 set_alive(cur_x + i, cur_y, true);
585 cur_x += r;
586 run = 0;
587 }
588 else if (c == '$')
589 {
590 cur_y += (run == 0 ? 1 : run);
591 cur_x = origin_x;
592 run = 0;
593 }
594 else if (c == '!')
595 {
596 ended = true;
597 break;
598 }
599 }
600 }
601 }
602
604 void load_rle_string(const std::string &s)
605 {
606 std::istringstream iss(s);
607 load_rle(iss);
608 }
609
618 void save_rle(std::ostream &out, const std::string &comment = {}) const
619 {
620 if (not comment.empty())
621 {
622 std::istringstream cs(comment);
623 std::string l;
624 while (std::getline(cs, l))
625 out << "#C " << l << '\n';
626 }
627 const BBox bb = bbox();
628 if (bb.is_empty())
629 {
630 out << "x = 0, y = 0, rule = " << format_rule(rule_) << "\n!\n";
631 return;
632 }
633
634 // Collect alive cells sorted by (y, x).
636 cells.reserve(static_cast<std::size_t>(root_->population));
637 for_each_alive([&cells](std::int64_t x, std::int64_t y)
638 {
639 cells.append(std::pair{y, x});
640 });
641 if (cells.size() > 1)
642 quicksort(&cells.base(), 0, static_cast<long>(cells.size()) - 1);
643
644 out << "x = " << bb.width() << ", y = " << bb.height() << ", rule = " << format_rule(rule_)
645 << '\n';
646
647 std::ostringstream body;
648 auto emit_run = [&body](const std::int64_t n, const char glyph)
649 {
650 if (n <= 0)
651 return;
652 if (n != 1)
653 body << n;
654 body << glyph;
655 };
656
657 std::int64_t prev_y = bb.y_min;
658 std::int64_t cur_x = bb.x_min;
659 std::size_t i = 0;
660 while (i < cells.size())
661 {
662 const std::int64_t y = cells(i).first;
663 const std::int64_t x = cells(i).second;
664 if (y != prev_y)
665 {
666 emit_run(y - prev_y, '$');
667 prev_y = y;
668 cur_x = bb.x_min;
669 }
670 if (x > cur_x)
671 {
672 emit_run(x - cur_x, 'b');
673 cur_x = x;
674 }
675 std::int64_t alive_run = 0;
676 std::size_t j = i;
677 while (j < cells.size() and cells(j).first == y and cells(j).second == cur_x + alive_run)
678 {
679 ++alive_run;
680 ++j;
681 }
682 emit_run(alive_run, 'o');
683 cur_x += alive_run;
684 i = j;
685 }
686 body << "!\n";
687
688 // Wrap body at ~70 columns, never breaking inside a `<digits><glyph>` token.
689 const std::string body_str = body.str();
690 std::size_t pos = 0;
691 while (pos < body_str.size())
692 {
693 std::size_t end = std::min<std::size_t>(pos + 70, body_str.size());
694 while (end < body_str.size() and std::isdigit(static_cast<unsigned char>(body_str[end])))
695 ++end;
696 if (end < body_str.size() and (body_str[end] == 'b' or body_str[end] == 'o'))
697 ++end;
698 out << body_str.substr(pos, end - pos) << '\n';
699 pos = end;
700 }
701 }
702
704 [[nodiscard]] std::string save_rle_string(const std::string &comment = {}) const
705 {
706 std::ostringstream oss;
707 save_rle(oss, comment);
708 return oss.str();
709 }
710
711private:
714
715 static constexpr std::uint8_t initial_level_ = 3;
716
718 std::size_t cache_capacity_;
719
720 std::deque<Node> pool_;
721 std::unordered_map<Node_Key, Node *, ca_hashlife_detail::Node_Key_Hash> table_;
723
724 Node *root_ = nullptr;
725 std::int64_t generation_ = 0;
726
727 mutable std::size_t result_hits_ = 0;
728 mutable std::size_t result_misses_ = 0;
729 std::size_t result_cache_clears_ = 0;
730
731 // -------------------- Canonical node creation --------------------
732
733 Node *make_leaf(const std::uint8_t bits)
734 {
735 const Node_Key key{1, bits, nullptr, nullptr, nullptr, nullptr};
736 if (auto it = table_.find(key); it != table_.end())
737 return it->second;
738 Node n{};
739 n.level = 1;
740 n.bits = bits;
741 n.population = static_cast<std::uint64_t>(std::popcount(bits));
742 pool_.push_back(n);
743 Node *const p = &pool_.back();
744 table_.emplace(key, p);
745 return p;
746 }
747
748 Node *make_node(Node *nw, Node *ne, Node *sw, Node *se)
749 {
750 const Node_Key key{static_cast<std::uint8_t>(nw->level + 1), 0, nw, ne, sw, se};
751 if (auto it = table_.find(key); it != table_.end())
752 return it->second;
753 Node n{};
754 n.level = key.level;
755 n.bits = 0;
756 n.population = nw->population + ne->population + sw->population + se->population;
757 n.nw = nw;
758 n.ne = ne;
759 n.sw = sw;
760 n.se = se;
761 pool_.push_back(n);
762 Node *const p = &pool_.back();
763 table_.emplace(key, p);
764 return p;
765 }
766
767 Node *empty_node(const std::uint8_t level)
768 {
769 while (empty_by_level_.size() <= level)
770 {
771 const std::uint8_t L = static_cast<std::uint8_t>(empty_by_level_.size());
772 if (L == 0)
773 {
774 empty_by_level_.append(nullptr);
775 continue;
776 }
777 Node *n = (L == 1) ? make_leaf(0)
781 }
782 return empty_by_level_[level];
783 }
784
786 {
787 for (auto &n : pool_)
788 n.result = nullptr;
789 result_hits_ = 0;
790 result_misses_ = 0;
792 }
793
794 // -------------------- Geometry helpers --------------------
795
796 Node *expand(const Node *n)
797 {
798 const std::uint8_t L = n->level;
799 if (L == 1)
800 {
801 Node *nw = make_leaf(static_cast<std::uint8_t>((n->bits & 1u) ? (1u << 3) : 0u));
802 Node *ne = make_leaf(static_cast<std::uint8_t>((n->bits & 2u) ? (1u << 2) : 0u));
803 Node *sw = make_leaf(static_cast<std::uint8_t>((n->bits & 4u) ? (1u << 1) : 0u));
804 Node *se = make_leaf(static_cast<std::uint8_t>((n->bits & 8u) ? (1u << 0) : 0u));
805 return make_node(nw, ne, sw, se);
806 }
807 Node *const e = empty_node(static_cast<std::uint8_t>(L - 1));
808 Node *new_nw = make_node(e, e, e, n->nw);
809 Node *new_ne = make_node(e, e, n->ne, e);
810 Node *new_sw = make_node(e, n->sw, e, e);
811 Node *new_se = make_node(n->se, e, e, e);
813 }
814
822 [[nodiscard]] static bool is_centered_well(const Node *n) noexcept
823 {
824 if (n->level < 4)
825 return false;
826 const std::uint64_t central =
827 n->nw->se->se->population
828 + n->ne->sw->sw->population
829 + n->sw->ne->ne->population
830 + n->se->nw->nw->population;
831 return central == n->population;
832 }
833
835 {
836 return make_node(w->ne, e->nw, w->se, e->sw);
837 }
838
840 {
841 return make_node(n->sw, n->se, s->nw, s->ne);
842 }
843
844 Node *center_of_4(Node *nw, Node *ne, Node *sw, Node *se)
845 {
846 return make_node(nw->se, ne->sw, sw->ne, se->nw);
847 }
848
849 // -------------------- Evolution --------------------
850
852 {
853 const std::uint8_t a = n->nw->bits;
854 const std::uint8_t b = n->ne->bits;
855 const std::uint8_t c = n->sw->bits;
856 const std::uint8_t d = n->se->bits;
857
858 // Pack 4x4 cells row-major into a 16-bit bitmap, bit `(r*4 + c)`.
859 std::uint16_t g = 0;
860 g |= static_cast<std::uint16_t>((a & 0x3u)) << 0; // row 0 cols 0-1
861 g |= static_cast<std::uint16_t>((b & 0x3u)) << 2; // row 0 cols 2-3
862 g |= static_cast<std::uint16_t>((a >> 2) & 0x3u) << 4; // row 1 cols 0-1
863 g |= static_cast<std::uint16_t>((b >> 2) & 0x3u) << 6; // row 1 cols 2-3
864 g |= static_cast<std::uint16_t>((c & 0x3u)) << 8; // row 2 cols 0-1
865 g |= static_cast<std::uint16_t>((d & 0x3u)) << 10; // row 2 cols 2-3
866 g |= static_cast<std::uint16_t>((c >> 2) & 0x3u) << 12; // row 3 cols 0-1
867 g |= static_cast<std::uint16_t>((d >> 2) & 0x3u) << 14; // row 3 cols 2-3
868
869 auto get = [g](const int r, const int c) noexcept -> bool
870 {
871 return ((g >> (r * 4 + c)) & 1u) != 0;
872 };
873 auto count = [&get](const int r, const int c) noexcept -> std::uint8_t
874 {
875 return static_cast<std::uint8_t>(get(r - 1, c - 1) + get(r - 1, c) + get(r - 1, c + 1)
876 + get(r, c - 1) + get(r, c + 1) + get(r + 1, c - 1)
877 + get(r + 1, c) + get(r + 1, c + 1));
878 };
879
880 std::uint8_t result = 0;
881 if (rule_.apply(get(1, 1), count(1, 1)))
882 result |= 1u << 0;
883 if (rule_.apply(get(1, 2), count(1, 2)))
884 result |= 1u << 1;
885 if (rule_.apply(get(2, 1), count(2, 1)))
886 result |= 1u << 2;
887 if (rule_.apply(get(2, 2), count(2, 2)))
888 result |= 1u << 3;
889 return make_leaf(result);
890 }
891
893 {
894 if (n->result != nullptr)
895 {
896 ++result_hits_;
897 return n->result;
898 }
900 Node *result = nullptr;
901 if (n->level == 2)
902 {
903 result = evolve_level_2(n);
904 }
905 else
906 {
907 Node *const nw = n->nw;
908 Node *const ne = n->ne;
909 Node *const sw = n->sw;
910 Node *const se = n->se;
911
912 Node *n00 = nw;
913 Node *n01 = horizontal_centered(nw, ne);
914 Node *n02 = ne;
915 Node *n10 = vertical_centered(nw, sw);
916 Node *n11 = center_of_4(nw, ne, sw, se);
917 Node *n12 = vertical_centered(ne, se);
918 Node *n20 = sw;
919 Node *n21 = horizontal_centered(sw, se);
920 Node *n22 = se;
921
922 Node *r00 = evolve(n00);
923 Node *r01 = evolve(n01);
924 Node *r02 = evolve(n02);
925 Node *r10 = evolve(n10);
926 Node *r11 = evolve(n11);
927 Node *r12 = evolve(n12);
928 Node *r20 = evolve(n20);
929 Node *r21 = evolve(n21);
930 Node *r22 = evolve(n22);
931
936
937 Node *q_nw = evolve(m_nw);
938 Node *q_ne = evolve(m_ne);
939 Node *q_sw = evolve(m_sw);
940 Node *q_se = evolve(m_se);
941
942 result = make_node(q_nw, q_ne, q_sw, q_se);
943 }
944 n->result = result;
945 return result;
946 }
947
948 // -------------------- Cell-level set / get / iterate --------------------
949
950 Node *set_in_node(const Node *n, const std::int64_t x, const std::int64_t y, const bool alive)
951 {
952 if (n->level == 1)
953 {
954 const int bit_idx = static_cast<int>(y) * 2 + static_cast<int>(x);
955 std::uint8_t new_bits = n->bits;
956 if (alive)
957 new_bits |= static_cast<std::uint8_t>(1u << bit_idx);
958 else
959 new_bits &= static_cast<std::uint8_t>(~(1u << bit_idx));
960 return make_leaf(new_bits);
961 }
962 const std::int64_t half = std::int64_t{1} << (n->level - 1);
963 Node *nw = n->nw;
964 Node *ne = n->ne;
965 Node *sw = n->sw;
966 Node *se = n->se;
967 if (y < half)
968 {
969 if (x < half)
970 nw = set_in_node(nw, x, y, alive);
971 else
972 ne = set_in_node(ne, x - half, y, alive);
973 }
974 else
975 {
976 if (x < half)
977 sw = set_in_node(sw, x, y - half, alive);
978 else
979 se = set_in_node(se, x - half, y - half, alive);
980 }
981 return make_node(nw, ne, sw, se);
982 }
983
984 [[nodiscard]] static bool get_in_node(const Node *n, const std::int64_t x, const std::int64_t y) noexcept
985 {
986 if (n->level == 1)
987 {
988 const int bit_idx = static_cast<int>(y) * 2 + static_cast<int>(x);
989 return ((n->bits >> bit_idx) & 1u) != 0;
990 }
991 const std::int64_t half = std::int64_t{1} << (n->level - 1);
992 if (y < half)
993 return (x < half) ? get_in_node(n->nw, x, y) : get_in_node(n->ne, x - half, y);
994 return (x < half) ? get_in_node(n->sw, x, y - half) : get_in_node(n->se, x - half, y - half);
995 }
996
997 template <typename F>
998 void visit_alive(Node *n, std::int64_t x, std::int64_t y, F &&f) const
999 {
1000 if (n->population == 0)
1001 return;
1002 if (n->level == 1)
1003 {
1004 if (n->bits & 0x1u)
1005 f(x, y);
1006 if (n->bits & 0x2u)
1007 f(x + 1, y);
1008 if (n->bits & 0x4u)
1009 f(x, y + 1);
1010 if (n->bits & 0x8u)
1011 f(x + 1, y + 1);
1012 return;
1013 }
1014 const std::int64_t half = std::int64_t{1} << (n->level - 1);
1015 visit_alive(n->nw, x, y, f);
1016 visit_alive(n->ne, x + half, y, f);
1017 visit_alive(n->sw, x, y + half, f);
1018 visit_alive(n->se, x + half, y + half, f);
1019 }
1020
1021 // -------------------- RLE header parsing --------------------
1022
1023 static std::int64_t parse_header_value(const std::string &line, const char key)
1024 {
1025 auto pos = line.find(key);
1026 if (pos == std::string::npos)
1027 return 0;
1028 pos = line.find('=', pos);
1029 if (pos == std::string::npos)
1030 return 0;
1031 ++pos;
1032 while (pos < line.size() and std::isspace(static_cast<unsigned char>(line[pos])))
1033 ++pos;
1034 bool neg = false;
1035 if (pos < line.size() and line[pos] == '-')
1036 {
1037 neg = true;
1038 ++pos;
1039 }
1040 std::int64_t v = 0;
1041 while (pos < line.size() and std::isdigit(static_cast<unsigned char>(line[pos])))
1042 {
1043 v = v * 10 + (line[pos] - '0');
1044 ++pos;
1045 }
1046 return neg ? -v : v;
1047 }
1048
1049 static std::optional<Outer_Totalistic_Binary_Rule> parse_rule_field(const std::string &line)
1050 {
1051 auto pos = line.find("rule");
1052 if (pos == std::string::npos)
1053 pos = line.find("Rule");
1054 if (pos == std::string::npos)
1055 return std::nullopt;
1056 pos = line.find('=', pos);
1057 if (pos == std::string::npos)
1058 return std::nullopt;
1059 ++pos;
1060 while (pos < line.size() and std::isspace(static_cast<unsigned char>(line[pos])))
1061 ++pos;
1062 std::string token;
1063 while (pos < line.size() and not std::isspace(static_cast<unsigned char>(line[pos]))
1064 and line[pos] != ',')
1065 {
1066 token.push_back(line[pos]);
1067 ++pos;
1068 }
1069 if (token.empty())
1070 return std::nullopt;
1071 return parse_rule(token);
1072 }
1073};
1074
1075} // namespace CA
1076} // namespace Aleph
1077
1078#endif // TPL_CA_HASHLIFE_H
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
Definition ah-errors.H:527
long double h
Definition btreepic.C:154
long double w
Definition btreepic.C:153
size_t steps
Definition ca-c-api.h:126
size_t size_t int32_t * out
Definition ca-c-api.h:120
Simple dynamic array with automatic resizing and functional operations.
Definition tpl_array.H:138
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
Definition tpl_array.H:365
T & base()
Return a reference to the first element of array.
Definition tpl_array.H:326
T & append(const T &data)
Append a copy of data
Definition tpl_array.H:250
void reserve(size_t cap)
Reserves cap cells into the array.
Definition tpl_array.H:320
Hashlife engine for outer-totalistic binary cellular automata.
Rule rule() const noexcept
Current rule.
void load_rle_string(const std::string &s)
Convenience overload that reads the pattern from a string.
BBox bbox() const
Tight bounding box of the alive cells (empty if population() == 0).
std::size_t cache_capacity() const noexcept
Maximum allowed canonical-node count before the result cache is dropped.
Hashlife_Engine(Rule rule=Conway_Life, std::size_t cache_capacity=std::size_t{1}<< 22)
Build an engine with the given rule and result-cache capacity.
static constexpr std::uint8_t initial_level_
8 × 8 starting universe
static bool is_centered_well(const Node *n) noexcept
Standard Hashlife "centered well" predicate: every quadrant of n has its population concentrated in t...
void set_rule(const Rule &r)
Replace the rule. Invalidates the result cache.
static std::optional< Outer_Totalistic_Binary_Rule > parse_rule_field(const std::string &line)
Node * set_in_node(const Node *n, const std::int64_t x, const std::int64_t y, const bool alive)
Node * vertical_centered(Node *n, Node *s)
static bool get_in_node(const Node *n, const std::int64_t x, const std::int64_t y) noexcept
void save_rle(std::ostream &out, const std::string &comment={}) const
Write the alive cells in RLE format.
std::uint64_t run(std::uint64_t generations)
Advance by exactly generations steps.
void for_each_alive(F &&f) const
Iterate over all alive cells, calling f(x, y) for each.
Stats stats() const noexcept
Diagnostic counters and current root level.
void set_alive(const std::int64_t x, const std::int64_t y, const bool alive=true)
Set or clear the cell at world coordinates (x, y).
std::uint64_t advance(const unsigned k)
Advance the universe by 2^k generations.
Node * expand(const Node *n)
void visit_alive(Node *n, std::int64_t x, std::int64_t y, F &&f) const
Node * empty_node(const std::uint8_t level)
Node * horizontal_centered(Node *w, Node *e)
std::unordered_map< Node_Key, Node *, ca_hashlife_detail::Node_Key_Hash > table_
static std::int64_t parse_header_value(const std::string &line, const char key)
std::string save_rle_string(const std::string &comment={}) const
Convenience overload returning the RLE serialisation as a string.
Node * make_node(Node *nw, Node *ne, Node *sw, Node *se)
Array< Node * > empty_by_level_
Aleph::Array of canonical empties per level.
void clear()
Reset the universe to empty (preserves rule, capacity and node cache).
Node * make_leaf(const std::uint8_t bits)
void load_rle(std::istream &in)
Load an RLE pattern from in (centred at the origin).
std::int64_t generation() const noexcept
Number of generations elapsed since construction (or the last clear).
bool alive_at(const std::int64_t x, const std::int64_t y) const
Read the cell at world coordinates (x, y).
std::uint64_t population() const noexcept
Number of alive cells.
Node * center_of_4(Node *nw, Node *ne, Node *sw, Node *se)
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
static mpfr_t y
Definition mpfr_mul_d.c:3
std::optional< Outer_Totalistic_Binary_Rule > parse_rule(const std::string &s)
Parse a B.../S... (or Wolfram S/B) rule string.
std::string format_rule(const Outer_Totalistic_Binary_Rule &r)
Format a rule as a Conway-style Bxxx/Sxxx string.
constexpr Outer_Totalistic_Binary_Rule Conway_Life
Conway's Game of Life: B3/S23.
constexpr Outer_Totalistic_Binary_Rule HighLife
Nathan Thompson's HighLife: B36/S23 (replicators).
constexpr Outer_Totalistic_Binary_Rule Day_And_Night
Bays' Day & Night: B3678/S34678 (self-complementary).
constexpr Outer_Totalistic_Binary_Rule Seeds
Seeds: B2/S (every live cell dies, two neighbours produce a birth).
Main namespace for Aleph-w library functions.
Definition ah-arena.H:89
and
Check uniqueness with explicit hash + equality functors.
void quicksort(T *a, const long l, const long r, const Compare &cmp=Compare())
Sort an array using iterative quicksort with optimizations.
Itor::difference_type count(const Itor &beg, const Itor &end, const T &value)
Count elements equal to a value.
Definition ahAlgo.H:127
Inclusive bounding box (returned by bbox).
std::int64_t height() const noexcept
Height of the box (0 if empty).
bool is_empty() const noexcept
True iff there are no alive cells.
std::int64_t width() const noexcept
Width of the box (0 if empty).
Diagnostics returned by stats.
std::size_t result_cache_clears
times the result cache was invalidated
std::size_t result_misses
misses on the per-node result cache
std::size_t result_hits
hits on the per-node result cache
std::size_t canonical_nodes
number of distinct canonical nodes
std::uint8_t root_level
current universe level (side 2^level)
Outer-totalistic binary rule encoded as two 9-bit bitmasks.
std::uint16_t birth_mask
bit i ⇒ dead cell with i live neighbours becomes alive
constexpr bool apply(const bool current, const std::uint8_t n) const noexcept
True iff a cell with current state and n live neighbours is alive next.
constexpr bool operator==(const Outer_Totalistic_Binary_Rule &) const noexcept=default
std::uint16_t survival_mask
bit i ⇒ live cell with i live neighbours stays alive
std::size_t operator()(const Node_Key &k) const noexcept
Hash key used to canonicalise nodes via std::unordered_map.
bool operator==(const Node_Key &o) const noexcept
Node * result
memoised next-generation result (level level - 1)
std::uint8_t level
1 ⇒ 2x2 leaf; k ⇒ 2^k × 2^k node
std::uint64_t population
number of alive cells covered by this node
std::uint8_t bits
for level-1 leaves: NW NE SW SE bits
static int * k
gsl_rng * r
Dynamic array container with automatic resizing.
Comprehensive sorting algorithms and search utilities for Aleph-w.
DynList< int > l