70#ifndef TPL_CA_HASHLIFE_H
71#define TPL_CA_HASHLIFE_H
84#include <unordered_map>
113 [[
nodiscard]]
constexpr bool apply(
const bool current,
const std::uint8_t n)
const noexcept
124 static_cast<std::uint16_t
>(1u << 3),
static_cast<std::uint16_t
>((1u << 2) | (1u << 3))};
128 static_cast<std::uint16_t
>((1u << 3) | (1u << 6)),
129 static_cast<std::uint16_t
>((1u << 2) | (1u << 3))};
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))};
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));
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));
172 while (i < s.size()
and std::isspace(
static_cast<unsigned char>(s[i])))
174 if (i < s.size()
and (s[i] ==
'B' or s[i] ==
'b'))
177 while (i < s.size()
and std::isdigit(
static_cast<unsigned char>(s[i])))
179 birth |=
static_cast<std::uint16_t
>(1u << (s[i] -
'0'));
182 if (i < s.size()
and s[i] ==
'/')
184 if (i < s.size()
and (s[i] ==
'S' or s[i] ==
's'))
186 while (i < s.size()
and std::isdigit(
static_cast<unsigned char>(s[i])))
188 survival |=
static_cast<std::uint16_t
>(1u << (s[i] -
'0'));
194 while (i < s.size()
and std::isdigit(
static_cast<unsigned char>(s[i])))
196 survival |=
static_cast<std::uint16_t
>(1u << (s[i] -
'0'));
199 if (i >= s.size()
or s[i] !=
'/')
202 while (i < s.size()
and std::isdigit(
static_cast<unsigned char>(s[i])))
204 birth |=
static_cast<std::uint16_t
>(1u << (s[i] -
'0'));
211namespace ca_hashlife_detail {
248 auto mix = [](std::size_t a, std::size_t b)
noexcept -> std::size_t
250 return a ^ (b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2));
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));
354 <<
"Hashlife_Engine: cache_capacity (" <<
cache_capacity_ <<
") below minimum 1024";
382 void set_alive(
const std::int64_t x,
const std::int64_t
y,
const bool alive =
true)
421 std::max<std::uint8_t>(
static_cast<std::uint8_t
>(
k + 2), 4);
429 const std::uint64_t
steps = std::uint64_t{1} << (
L - 2);
452 const unsigned k =
static_cast<unsigned>(63 - std::countl_zero(
generations));
453 const std::uint64_t step =
advance(
k);
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()};
522 template <
typename F>
549 std::int64_t
cur_x = 0;
550 std::int64_t
cur_y = 0;
551 std::int64_t
run = 0;
556 if (line.empty()
or line[0] ==
'#')
571 for (
const char c : line)
573 if (std::isdigit(
static_cast<unsigned char>(c)))
574 run =
run * 10 + (c -
'0');
575 else if (c ==
'b' or c ==
'.')
580 else if (c ==
'o' or c ==
'A')
582 const std::int64_t
r = (
run == 0 ? 1 :
run);
583 for (std::int64_t i = 0; i <
r; ++i)
606 std::istringstream
iss(s);
618 void save_rle(std::ostream &
out,
const std::string &comment = {})
const
620 if (
not comment.empty())
622 std::istringstream
cs(comment);
624 while (std::getline(
cs,
l))
625 out <<
"#C " <<
l <<
'\n';
641 if (cells.
size() > 1)
647 std::ostringstream body;
648 auto emit_run = [&body](
const std::int64_t n,
const char glyph)
660 while (i < cells.
size())
662 const std::int64_t
y = cells(i).first;
663 const std::int64_t x = cells(i).second;
689 const std::string
body_str = body.str();
693 std::size_t end = std::min<std::size_t>(pos + 70,
body_str.size());
706 std::ostringstream
oss;
721 std::unordered_map<Node_Key, Node *, ca_hashlife_detail::Node_Key_Hash>
table_;
735 const Node_Key key{1, bits,
nullptr,
nullptr,
nullptr,
nullptr};
741 n.population =
static_cast<std::uint64_t
>(std::popcount(bits));
750 const Node_Key key{
static_cast<std::uint8_t
>(nw->
level + 1), 0, nw, ne, sw, se};
787 for (
auto &n :
pool_)
798 const std::uint8_t
L = n->
level;
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));
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;
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;
860 g |=
static_cast<std::uint16_t
>((a & 0x3u)) << 0;
861 g |=
static_cast<std::uint16_t
>((b & 0x3u)) << 2;
862 g |=
static_cast<std::uint16_t
>((a >> 2) & 0x3u) << 4;
863 g |=
static_cast<std::uint16_t
>((b >> 2) & 0x3u) << 6;
864 g |=
static_cast<std::uint16_t
>((c & 0x3u)) << 8;
865 g |=
static_cast<std::uint16_t
>((d & 0x3u)) << 10;
866 g |=
static_cast<std::uint16_t
>((c >> 2) & 0x3u) << 12;
867 g |=
static_cast<std::uint16_t
>((d >> 2) & 0x3u) << 14;
869 auto get = [g](
const int r,
const int c)
noexcept ->
bool
871 return ((g >> (
r * 4 + c)) & 1u) != 0;
873 auto count = [&get](
const int r,
const int c)
noexcept -> std::uint8_t
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));
880 std::uint8_t result = 0;
900 Node *result =
nullptr;
954 const int bit_idx =
static_cast<int>(
y) * 2 +
static_cast<int>(x);
962 const std::int64_t
half = std::int64_t{1} << (n->
level - 1);
988 const int bit_idx =
static_cast<int>(
y) * 2 +
static_cast<int>(x);
989 return ((n->bits >>
bit_idx) & 1u) != 0;
991 const std::int64_t
half = std::int64_t{1} << (n->level - 1);
997 template <
typename F>
1014 const std::int64_t
half = std::int64_t{1} << (n->
level - 1);
1025 auto pos = line.find(key);
1026 if (pos == std::string::npos)
1028 pos = line.find(
'=', pos);
1029 if (pos == std::string::npos)
1032 while (pos < line.size()
and std::isspace(
static_cast<unsigned char>(line[pos])))
1035 if (pos < line.size()
and line[pos] ==
'-')
1041 while (pos < line.size()
and std::isdigit(
static_cast<unsigned char>(line[pos])))
1043 v = v * 10 + (line[pos] -
'0');
1046 return neg ? -v : v;
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;
1060 while (pos < line.size()
and std::isspace(
static_cast<unsigned char>(line[pos])))
1063 while (pos < line.size()
and not std::isspace(
static_cast<unsigned char>(line[pos]))
1064 and line[pos] !=
',')
1066 token.push_back(line[pos]);
1070 return std::nullopt;
Exception handling system with formatted messages for Aleph-w.
#define ah_domain_error_if(C)
Throws std::domain_error if condition holds.
size_t size_t int32_t * out
Simple dynamic array with automatic resizing and functional operations.
constexpr size_t size() const noexcept
Return the number of elements stored in the stack.
T & base()
Return a reference to the first element of array.
T & append(const T &data)
Append a copy of data
void reserve(size_t cap)
Reserves cap cells into the array.
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)
void clear_result_cache()
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)
std::size_t result_misses_
Node * horizontal_centered(Node *w, Node *e)
std::unordered_map< Node_Key, Node *, ca_hashlife_detail::Node_Key_Hash > table_
Node * evolve_level_2(Node *n)
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)
std::size_t result_cache_clears_
std::size_t cache_capacity_
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().
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.
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.
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
Dynamic array container with automatic resizing.
Comprehensive sorting algorithms and search utilities for Aleph-w.