- PVSM.RU - https://www.pvsm.ru -
Мы привыкли думать о вычислениях как о битах, регистрах и арифметике. А что, если базовой единицей вычисления сделать не бит, а локальную геометрическую конфигурацию тетраэдров? В этой статье я покажу дискретный тетраэдрический движок состояний, симметрийную канонизацию, аттракторы, иерархические jump-таблицы и реальные замеры на RTX 3090 — с измеренным exact-ускорением в 554.92 раза на одной и той же задаче.
Обычная вычислительная логика устроена очень просто: есть биты 0/1, есть операции над ними, есть длинные цепочки преобразований.
Но можно посмотреть на вычисление иначе: не как на манипуляцию отдельными битами, а как на эволюцию локальных геометрических конфигураций.
Вместо «ячейка памяти хранит 0 или 1» можно взять более богатую сущность:
положение объекта,
ориентацию,
фазу,
допустимые симметрии,
правила локального преобразования.
В моей модели такой сущностью стал локальный кластер тетраэдров.
Бит — это лампочка: либо включена, либо выключена.
Тетраэдрический мотив — это уже маленькая механическая конструкция, у которой есть:
как она стоит,
куда повернута,
в каком состоянии находится,
как она может перестроиться дальше.
То есть одна «единица состояния» становится намного богаче обычного бита.
Тетраэдр — это простейшее трёхмерное тело с богатой симметрией:
4 вершины,
4 грани,
6 рёбер.
У него достаточно сложная геометрия, чтобы порождать интересные классы состояний, но при этом он ещё достаточно прост, чтобы всё это можно было эффективно кодировать.
В вычислительном смысле тетраэдр удобен по трём причинам:
у него компактная группа симметрий;
локальное состояние можно дискретизировать;
последовательности преобразований можно предвычислять.
Тетраэдр — это «минимальная интересная 3D-фигура». Куб слишком привычный. Треугольник — уже не 3D. А тетраэдр — это маленькая 3D-ячейка, на которой уже можно строить нетривиальную «механику состояний».
В моей модели локальный кластер устроен так:
есть 4 слота;
в них находятся 3 тетраэдра;
1 слот пустой.
Каждый занятый слот хранит локальное состояние тетраэдра:
ориентация: 0..11,
фаза: 0..2.
Итого одно локальное состояние тетраэдра:
[ 12 × 3 = 36 ] вариантов.
Пустой слот кодируется отдельно.
То есть полный raw-state кластера — это конфигурация вида:
4 позиции,
из них 3 заняты,
в каждой занятой позиции — один из 36 вариантов.
Представьте 4 ячейки, как будто 4 места вокруг маленькой геометрической конструкции. На трёх местах стоят тетраэдры, одно место пустое. Каждый тетраэдр может:
стоять в одной из 12 ориентаций,
быть в одной из 3 фаз.
Из этого собирается локальная «сцена».
Если не учитывать симметрии, то количество raw-state равно:
выбор пустого слота: 4,
три заполненных слота, в каждом по 36 вариантов.
[ 4 · 36^3 = 186,624 ]
Именно это и показал код:
Raw states: 186 624
Но это ещё не настоящие рабочие состояния, потому что многие из них эквивалентны с точностью до вращения тетраэдрической схемы.
Если просто «в лоб» перечислить все варианты, их почти 187 тысяч. Но среди них много одинаковых по сути, просто повернутых в пространстве. Как если бы вы посмотрели на один и тот же предмет с другой стороны — сам предмет не изменился, изменился только ракурс.
Это один из ключевых шагов.
Мы берём группу вращений тетраэдра. Для неё я использовал 12 вращений — это ориентационно-сохраняющая группа симметрий тетраэдра, изоморфная A₄.
Для каждого raw-state мы:
применяем все допустимые симметрии;
получаем множество эквивалентных конфигураций;
выбираем канонический представитель, например, лексикографически минимальный packed-state.
Так raw-state схлопываются в классы эквивалентности.
Результат:
Canonical motifs: 15 576
Symmetry count: 12
То есть:[ 186,624 → 15,576 ]
Это уже серьёзное сжатие пространства состояний.
Мы перестаём хранить «одну и ту же фигуру, повёрнутую по-разному» как разные состояния. Мы говорим: у этой конфигурации есть «канонический вид», и всё приводим к нему. Это примерно как вместо «кошка лицом влево» и «кошка лицом вправо» хранить просто «кошка», если для задачи поворот не важен.
После канонизации базовой единицей вычисления становится motif — канонический локальный класс конфигурации.
То есть дальше движок работает уже не с произвольными «сырыми» состояниями, а с компактным идентификатором мотива:
[ text{motifId} in [0,15575] ]
Именно motif становится базовой «единицей вычисления».
После всей уборки и удаления дубликатов остаётся не 186 тысяч хаотичных вариантов, а 15 576 «типовых конструкций». Вот эти типовые конструкции и есть «слова» нашего геометрического языка.
Дальше нужен механизм вычисления — то есть правила, как одна конфигурация переходит в другую.
Для этого вводятся 4 генератора:
[ G_0, G_1, G_2, G_3 ]
Каждый генератор делает три вещи:
переставляет слоты;
двигает один тетраэдр в пустую позицию;
изменяет его ориентацию и фазу;
слегка обновляет состояния остальных тетраэдров.
Фактически это локальная геометрическая операция переписывания.
Генератор — это как «одна маленькая команда»: что-то повернуть, что-то сдвинуть, пустое место поменять, обновить локальные состояния. То есть не арифметика, а маленькая геометрическая перестройка.
Одна команда — это хорошо, но вычисление обычно состоит из последовательности команд.
Поэтому вводится путь — короткое слово над генераторами.
Например, базовый путь у меня был такой:
[ (2,2,1,2,2) ]
То есть:[ F = G_2 circ G_2 circ G_1 circ G_2 circ G_2 ]
Это уже не единичное движение, а маленькая программа.
Во второй и третьей версиях движка я использовал уже 8 разных путей:
pathId=0 [2,2,1,2,2]
pathId=1 [0,1,2,3,0]
pathId=2 [3,2,1,0,3]
pathId=3 [1,1,1,1,1]
pathId=4 [0,2,0,2,1]
pathId=5 [3,3,0,1,2]
pathId=6 [1,3,2,1,0]
pathId=7 [2,0,3,1,2]
Если генератор — это одна команда, то путь — это короткий скрипт из 5 команд. То есть мы уже не просто «щёлкаем одной операцией», а задаём маленькую программу над геометрической системой.
Если делать всё напрямую, то для каждого мотива и каждого пути нужно последовательно применить все генераторы в слове.
Если путь повторяется много раз, например F⁶⁴, то приходится выполнять длинную цепочку переходов.
Именно так работает direct-режим.
Для случая F⁶⁴ и 4 млн элементов результаты такие:
Direct random time: 17,28 ms
Direct grouped time: 10,10 ms
Это «честный тупой способ»: берём состояние, 5 раз применяем команды пути, потом ещё 64 раза повторяем весь путь, и так для миллионов элементов. Понятно, что это работает, но дорого.
Вот здесь начинается самое интересное.
Если путь фиксирован, то можно заранее вычислить, куда он переводит каждый мотив:
[ text{motif} to F(text{motif}) ]
Это таблица J0 = F¹.
Но можно пойти дальше и заранее вычислить:
J1 = F⁶⁴
J2 = F⁴⁰⁹⁶
J3 = F²⁶²¹⁴⁴
J4 = F¹⁶⁷⁷⁷²¹⁶
То есть очень длинная программа превращается в один lookup. Именно это и есть jump-таблицы.
Вместо того чтобы каждый раз снова и снова выполнять длинную последовательность команд, мы делаем как в математике с таблицей степеней: заранее знаем, что получится после 64 шагов, после 4096 шагов, после 262 тысяч шагов, после 16.7 миллионов шагов. Тогда вычисление превращается в «сразу взять готовый ответ из таблицы».
В base-64 варианте иерархия такая:
J0: F^1
J1: F^64
J2: F^4 096
J3: F^262 144
J4: F^16 777 216
Это означает, что:
J1 эквивалентен 64 применениям J0,
J2 эквивалентен 64 применениям J1,
и так далее.
Результат — очень глубокая композиция кодируется одной табличной операцией.
Это как матрёшка уровней ускорения: один уровень знает 1 шаг, следующий — 64 шага, следующий — уже 4096 шагов, и так дальше. Чем выше уровень, тем длиннее программа, спрятанная в одном lookup.
Base-64 отлично работает для степеней 64, но плохо подходит для произвольного числа повторов.
Для этого была построена ещё и бинарная иерархия:
B0: F^1
B1: F^2
B2: F^4
B3: F^8
...
B24: F^16 777 216
Тогда любое число повторов N можно разложить по двоичным степеням и применить нужные jump-уровни. Это даёт лучший arbitrary-режим.
И код подтвердил это на практике:
Base64 arbitrary time: 3,86 ms
Binary arbitrary time: 1,84 ms
Binary arbitrary: 2,10x vs base64-arbitrary
Если нужно сделать не «ровно 64 в степени k», а какое-то произвольное число шагов, например 12 345 678, удобнее раскладывать не по 64, а по двоичным кускам: 1, 2, 4, 8, 16... Это стандартная идея «быстрого возведения», только применённая к геометрическим переходам.
Если взять отображение
[ text{motif} to F(text{motif}) ]
то оно задаёт функциональный граф.
У такого графа каждая вершина имеет ровно одно исходящее ребро, а значит вся система распадается на:
хвосты,
циклы,
бассейны притяжения этих циклов.
Именно это и даёт аттракторную структуру.
Для разных путей картина оказалась очень разной. Например:
pathId=0 [2,2,1,2,2] | cycles=2, maxCycle=20, avgSteps=66,52, dominantBasin=15 486 (99,42%)
pathId=5 [3,3,0,1,2] | cycles=3, maxCycle=145, avgSteps=26,21, dominantBasin=15 504 (99,54%)
pathId=3 [1,1,1,1,1] | cycles=17, maxCycle=74, avgSteps=20,13, dominantBasin=7 913 (50,80%)
Если много раз применять один и тот же путь, система обычно не гуляет бесконечно куда попало. Она либо приходит в цикл, либо в маленькое семейство устойчивых режимов. Это как шарик, катящийся по поверхности: почти всегда он попадает в какую-то «ямку» или начинает крутиться по устойчивой траектории.
Пути оказались не просто разными, а семантически разными.
Сильно сжимающие пути
Например, path 0 и path 5: почти все состояния затягиваются в один доминирующий basin. Это похоже на нормализацию или приведение к канонической форме.
Более выразительные пути
Например, path 3, path 2, path 6, path 7: больше циклов, больше разнообразия, меньше «схлопывание». Такие пути ближе к богатой логике состояний.
Какие-то пути работают как «пылесос» — почти всё засасывают в один режим. А какие-то пути больше похожи на «разветвитель» — сохраняют разнообразие и дают системе больше вариантов поведения. То есть пути начинают вести себя как разные инструкции процессора.
Для каждого (pathId, motifId) можно знать:
представителя цикла,
длину цикла,
число шагов до цикла.
Чтобы не читать несколько буферов, я упаковал это в один 32-битный integer:
representative: 14 бит,
cycleLength: 8 бит,
stepsToCycle: 10 бит.
Это уменьшает память и сокращает число чтений.
Вместо того чтобы хранить 3 разных числа в 3 разных местах, мы складываем их в одну компактную коробку из 32 бит. Меньше памяти — быстрее lookup.
Отдельные проходы по памяти — дорого.
Поэтому был сделан fused-режим, который за один dispatch делает:
jump;
lookup packed attractor metadata.
То есть pipeline стал таким:
[ (text{motifId},text{pathId}) to text{newMotifId} + text{attractorMeta} ]
Практически это оказалось очень удачным решением.
Вместо двух походов в память мы идём один раз: сразу прыгаем в итоговое состояние, сразу забираем всё, что нужно знать о его аттракторе. Это как сходить в магазин один раз и купить всё сразу, а не идти сначала за хлебом, потом отдельно за молоком.
В direct-режиме путь для каждого элемента выбирается по pathId.
Если pathId случайный, потоки GPU идут по более «рваным» паттернам доступа. Если pathId сгруппирован, потоки становятся более согласованными.
Результат:
Direct random time: 17,28 ms
Direct grouped time: 10,10 ms
То есть grouped scheduling даёт ускорение в 1.71x.
Для fused-режима эффект гораздо слабее:
Fused J1 random time: 1,85 ms
Fused J1 grouped time: 1,74 ms
Это значит, что jump/fused-режим значительно устойчивее к path divergence.
Если соседние потоки делают похожую работу, GPU это любит. Если каждый поток живёт своей жизнью, начинаются потери. Поэтому «сортировать задачи по типу пути» — это хорошая идея, особенно для direct-режима.
Вот ключевые результаты замеров.
Базовые тесты
Standard compute: 3,60 ms
Direct F^64 random: 17,28 ms
Direct F^64 grouped: 10,10 ms
Jump J1 random: 1,62 ms
Fused J1 random: 1,85 ms
Fused J1 grouped: 1,74 ms
Base64 arbitrary: 3,86 ms
Binary arbitrary: 1,84 ms
Base64 exact deep: 1,31 ms
Binary exact deep: 1,66 ms
Простыми словами
Что это значит:
прямой пересчёт длинных путей — медленный;
jump-таблицы — очень быстрые;
fused-режим почти не уступает чистому jump;
binary лучше для произвольной глубины;
base64 лучше для точных степеней 64.
Для F⁶⁴
Лучший режим — jump или fused jump.
Jump J1 random: 1,62 ms
Fused J1 random: 1,85 ms
Против direct random:
17,28 ms
Для arbitrary repeat
Лучший режим — binary decomposition.
Binary arbitrary: 1,84 ms
vs
Base64 arbitrary: 3,86 ms
Для exact powers of 64
Лучший режим — base64 exact jump.
Base64 exact deep: 1,31 ms
vs
Binary exact deep: 1,66 ms
Если сильно упростить:
ровные большие степени → бери base64;
любое произвольное число шагов → бери binary;
нужна ещё информация про аттрактор → бери fused.
Чтобы уйти от «оценок по линейной экстраполяции», я сделал реальный exact benchmark на 30 секунд.
Задача была одна и та же:
[ F^{4096} ]
Считалась двумя способами:
Direct exact: 64 последовательных запуска F⁶⁴.
Jump exact: 1 запуск J2 = F⁴⁰⁹⁶.
Обе реализации вычисляют одну и ту же точную функцию.
Результаты:
DIRECT EXACT F^4096
Time: 30,69 s
Batches completed: 41
Exact depth per batch: F^4 096
Batches/sec: 1,3359
Path applications/sec: 21 887 824 042
Generator applications/sec: 109 439 120 208
JUMP EXACT F^4096
Time: 30,00 s
Batches completed: 22 240
Exact depth per batch: F^4 096
Batches/sec: 741,3308
Path applications/sec: 12 145 964 037 056
Generator applications/sec: 60 729 820 185 278
Measured exact throughput speedup: 554,92x
Мы не сравниваем «примерно похожие штуки». Мы сравниваем одну и ту же задачу: либо выполнять её длинной прямой цепочкой, либо взять уже сжатый jump. И вот тут ускорение получилось 554.92x. То есть не «в теории», а реально, по секундомеру.
Ранее по более глубоким jump-уровням можно было получить очень большие оценочные факторы, например миллионы раз, если линейно экстраполировать direct на F¹⁶·⁷⁷⁷·²¹⁶. Но это всё равно остаётся оценкой.
А вот ускорение в 554.92x на F⁴⁰⁹⁶ — это уже измеренный exact speedup.
Теперь главный вопрос: почему это всё так быстро работает?
Причина №1. Мы считаем не арифметику, а переходы между классами состояний
Вместо длинной последовательности операций над числами мы работаем с компактными motif-id.
Простыми словами: Мы не считаем формулы в лоб, а ходим по карте уже известных состояний.
Причина №2. Симметрии сильно уменьшают пространство
Raw-state: 186,624 → Canonical motifs: 15,576.
Простыми словами: Мы избавились от огромного количества дубликатов.
Причина №3. Длинные программы предвычислены
F⁴⁰⁹⁶ превращается не в 4096 повторов, а в один lookup.
Простыми словами: Мы не повторяем длинную работу, а достаём уже готовый итог.
Причина №4. GPU хорошо любит табличные переходы малого размера
Все ключевые таблицы маленькие по современным меркам:
Generator table: ~0.24 MB
Base64 jump tables: ~2.38 MB
Packed attractor metadata: ~0.48 MB
Binary jump tables: ~11.88 MB
Простыми словами: Таблицы достаточно компактные, чтобы GPU быстро с ними работал.
Причина №5. Fused kernel уменьшает число проходов по памяти
Jump + attractor metadata делаются за один проход.
Простыми словами: Меньше бегаем туда-сюда по памяти — быстрее работаем.
Причина №6. Для arbitrary repeat используется правильная иерархия
Binary decomposition дала ускорение в 2.10x относительно base64 decomposition для произвольной глубины.
Простыми словами: Для разных типов задач нужен разный «способ прыжка». Мы это учли.
Чтобы остаться честными, нужно сказать и о границах метода.
Это не «ускорение любых вычислений»
Метод ускоряет длинные композиции в дискретной геометрической системе, а не произвольные задачи мира.
Простыми словами: Это не «новый суперкомпьютер для всего подряд», а очень сильный ускоритель для определённого класса вычислений.
Это не замена криптографии
Такой движок не заменяет SHA-256 и не делает валидный Bitcoin mining.
Простыми словами: Биткоин всё равно требует свой хеш, и сеть не примет «тетраэдрический ответ».
Это не магия
Основной выигрыш возникает потому, что вычисление имеет структуру, а структура позволяет сделать jump-таблицы.
Простыми словами: Если задача не имеет повторяемых переходов и симметрий, такого ускорения не будет.
Зато вот что действительно означает:
У нас есть рабочий дискретный геометрический вычислитель:
богатая единица состояния;
симметрийная канонизация;
множество путей;
attractor-структура;
jump-компрессия.
У нас есть зачаток instruction set. Разные пути ведут себя по-разному: одни сильно сжимают пространство, другие дают богатую динамику, третьи создают длинные циклы.
У нас есть runtime-стратегия:
exact powers of 64 → base64 jump;
arbitrary repeats → binary jump;
metadata → fused;
path grouping → для лучшего scheduler behavior.
Если формулировать вывод максимально честно и точно, то он такой:
В этой работе я построил дискретный тетраэдрический вычислительный движок, в котором базовой единицей является не бит, а канонический геометрический мотив.
За счёт:
симметрийной канонизации,
предвычисления переходов,
иерархических jump-таблиц,
packed attractor metadata,
fused GPU-kernel,
и правильного выбора стратегии исполнения,
удалось получить измеренное exact-ускорение в 554.92x на одной и той же задаче F⁴⁰⁹⁶ на RTX 3090.
А также показать, что:
base64 jump лучше для точных степеней,
binary jump лучше для произвольной глубины,
разные пути формируют разные attractor-профили, а значит у такой системы уже появляется вычислительная семантика.
Главная мысль статьи очень простая: если считать не битами, а геометрическими состояниями, и заранее знать, как длинные цепочки преобразований схлопываются, то можно получить очень серьёзное ускорение на точных задачах.
Код программы:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using ComputeSharp;
namespace TetraCombinationEngineV3
{
internal static class Config
{
// ----------------------------------------------------
// Дискретная тетра-кодировка
// ----------------------------------------------------
public const int SLOT_COUNT = 4;
public const int OCCUPIED_COUNT = 3;
public const int ORIENTATION_COUNT = 12;
public const int PHASE_COUNT = 3;
public const int LOCAL_STATE_COUNT = ORIENTATION_COUNT * PHASE_COUNT; // 36
public const int EMPTY = LOCAL_STATE_COUNT; // 36
public const int PACK_BASE = LOCAL_STATE_COUNT + 1; // 37
// ----------------------------------------------------
// Paths
// ----------------------------------------------------
public const int PATH_LENGTH = 5;
public static readonly int[][] PATHS =
{
new[] { 2,2,1,2,2 },
new[] { 0,1,2,3,0 },
new[] { 3,2,1,0,3 },
new[] { 1,1,1,1,1 },
new[] { 0,2,0,2,1 },
new[] { 3,3,0,1,2 },
new[] { 1,3,2,1,0 },
new[] { 2,0,3,1,2 }
};
public static readonly string[] PATH_NAMES =
{
"2,2,1,2,2",
"0,1,2,3,0",
"3,2,1,0,3",
"1,1,1,1,1",
"0,2,0,2,1",
"3,3,0,1,2",
"1,3,2,1,0",
"2,0,3,1,2"
};
public static int PATH_COUNT => PATHS.Length;
// ----------------------------------------------------
// Base-64 hierarchy
// ----------------------------------------------------
// J0 = F^1
// J1 = F^64
// J2 = F^4096
// J3 = F^262144
// J4 = F^16777216
public const int BASE64_JUMP_BASE = 64;
public const int BASE64_LEVELS = 5;
// ----------------------------------------------------
// Binary hierarchy
// ----------------------------------------------------
// B0 = F^1
// B1 = F^2
// ...
// B24 = F^16777216
public const int BINARY_LEVELS = 25;
// ----------------------------------------------------
// Packed attractor metadata
// rep:14 bits [0..16383]
// len:8 bits [0..255]
// step:10 bits [0..1023]
// total:32 bits
// ----------------------------------------------------
public const int REP_BITS = 14;
public const int LEN_BITS = 8;
public const int STEP_BITS = 10;
public const int REP_MASK = (1 << REP_BITS) - 1;
public const int LEN_MASK = (1 << LEN_BITS) - 1;
public const int STEP_MASK = (1 << STEP_BITS) - 1;
public const int LEN_SHIFT = REP_BITS;
public const int STEP_SHIFT = REP_BITS + LEN_BITS;
// ----------------------------------------------------
// Benchmark
// ----------------------------------------------------
public const int ELEMENTS = 4_000_000;
public const int CORRECTNESS_TEST_SIZE = 100_000;
public const int STANDARD_ITERATIONS = 80;
public const int DIRECT_GPU_WORD_REPEATS = 64;
public const int ARBITRARY_REPEAT_COUNT = 12_345_678;
// exact deep target
public const int EXACT_DEEP_REPEAT = 16_777_216;
// ----------------------------------------------------
// Real 30-second task
// exact semantic task:F^4096
// direct exact = 64 launches of F^64
// jump exact = one launch of J2
// ----------------------------------------------------
public const int REAL_TASK_REPEAT = 4_096;
public const int REAL_TASK_BASE64_LEVEL = 2; // J2 = F^4096
public const int REAL_TASK_SECONDS = 30;
}
internal sealed class PathSummary
{
public required int PathId { get; init; }
public required int CycleCount { get; init; }
public required int MaxCycleLength { get; init; }
public required float AverageSteps { get; init; }
public required int DominantBasinSize { get; init; }
public required int DominantCycleLength { get; init; }
public required int DominantRepresentative { get; init; }
}
internal sealed class EngineBuildResult
{
public required int MotifCount { get; init; }
public required int RawStateCount { get; init; }
public required int SymmetryCount { get; init; }
public required int[] CanonicalPackedByMotif { get; init; }
public required int[] PathWords { get; init; }
public required int[] GeneratorTransitions { get; init; }
public required int[] Base64JumpTables { get; init; } // [((path * levels)+level)*motifCount + motif]
public required long[] Base64LevelApplications { get; init; }
public required int[] BinaryJumpTables { get; init; } // [((path * levels)+level)*motifCount + motif]
public required long[] BinaryLevelApplications { get; init; }
public required int[] PackedAttractorMetadata { get; init; } // [path * motifCount + motif]
public required PathSummary[] PathSummaries { get; init; }
}
internal sealed class RealTaskResult
{
public required string Name { get; init; }
public required int Elements { get; init; }
public required int ExactRepeat { get; init; }
public required double Seconds { get; init; }
public required long BatchesCompleted { get; init; }
public long TotalElementEvaluations => BatchesCompleted * (long)Elements;
public long TotalPathApplications => BatchesCompleted * (long)Elements * ExactRepeat;
public long TotalGeneratorApplications => TotalPathApplications * Config.PATH_LENGTH;
public double BatchesPerSecond => BatchesCompleted / Seconds;
public double PathApplicationsPerSecond => TotalPathApplications / Seconds;
public double GeneratorApplicationsPerSecond => TotalGeneratorApplications / Seconds;
}
// ============================================================
// STANDARD GPU COMPUTE
// ============================================================
[ThreadGroupSize(256, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct StandardComputeKernel : IComputeShader
{
public readonly ReadWriteBuffer<float> Input;
public readonly ReadWriteBuffer<float> Output;
public readonly int Elements;
public readonly int Iterations;
public StandardComputeKernel(
ReadWriteBuffer<float> input,
ReadWriteBuffer<float> output,
int elements,
int iterations)
{
Input = input;
Output = output;
Elements = elements;
Iterations = iterations;
}
public void Execute()
{
int i = ThreadIds.X;
if (i >= Elements) return;
float x = Input[i];
for (int iter = 0; iter < Iterations; iter++)
{
x =
Hlsl.Sin(x * 1.13f) * Hlsl.Cos(x * 0.91f) +
0.31f * Hlsl.Exp(Hlsl.Abs(x) * 0.12f) +
0.27f * Hlsl.Log(1.0f + Hlsl.Abs(x) + 0.1f) +
0.19f * Hlsl.Sqrt(Hlsl.Abs(x) + 0.2f) -
0.08f * Hlsl.Tan(x * 0.15f);
x = x * 0.87f + 0.013f;
}
Output[i] = x;
}
}
// ============================================================
// DIRECT MULTI-PATH F^64
// ============================================================
[ThreadGroupSize(256, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct DirectMultiPathKernel : IComputeShader
{
public readonly ReadWriteBuffer<int> InputMotifs;
public readonly ReadWriteBuffer<int> InputPathIds;
public readonly ReadWriteBuffer<int> OutputMotifs;
public readonly ReadWriteBuffer<int> GeneratorTransitions;
public readonly ReadWriteBuffer<int> PathWords;
public readonly int Elements;
public readonly int MotifCount;
public readonly int PathLength;
public readonly int WordRepeats;
public DirectMultiPathKernel(
ReadWriteBuffer<int> inputMotifs,
ReadWriteBuffer<int> inputPathIds,
ReadWriteBuffer<int> outputMotifs,
ReadWriteBuffer<int> generatorTransitions,
ReadWriteBuffer<int> pathWords,
int elements,
int motifCount,
int pathLength,
int wordRepeats)
{
InputMotifs = inputMotifs;
InputPathIds = inputPathIds;
OutputMotifs = outputMotifs;
GeneratorTransitions = generatorTransitions;
PathWords = pathWords;
Elements = elements;
MotifCount = motifCount;
PathLength = pathLength;
WordRepeats = wordRepeats;
}
public void Execute()
{
int i = ThreadIds.X;
if (i >= Elements) return;
int motif = InputMotifs[i];
int pathId = InputPathIds[i];
int pathOffset = pathId * PathLength;
for (int r = 0; r < WordRepeats; r++)
{
for (int step = 0; step < PathLength; step++)
{
int g = PathWords[pathOffset + step];
motif = GeneratorTransitions[g * MotifCount + motif];
}
}
OutputMotifs[i] = motif;
}
}
// ============================================================
// SINGLE-LEVEL JUMP LOOKUP
// Works for both base64 and binary hierarchies
// ============================================================
[ThreadGroupSize(256, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct MultiPathJumpKernel : IComputeShader
{
public readonly ReadWriteBuffer<int> InputMotifs;
public readonly ReadWriteBuffer<int> InputPathIds;
public readonly ReadWriteBuffer<int> OutputMotifs;
public readonly ReadWriteBuffer<int> JumpTables;
public readonly int Elements;
public readonly int MotifCount;
public readonly int LevelCount;
public readonly int Level;
public MultiPathJumpKernel(
ReadWriteBuffer<int> inputMotifs,
ReadWriteBuffer<int> inputPathIds,
ReadWriteBuffer<int> outputMotifs,
ReadWriteBuffer<int> jumpTables,
int elements,
int motifCount,
int levelCount,
int level)
{
InputMotifs = inputMotifs;
InputPathIds = inputPathIds;
OutputMotifs = outputMotifs;
JumpTables = jumpTables;
Elements = elements;
MotifCount = motifCount;
LevelCount = levelCount;
Level = level;
}
public void Execute()
{
int i = ThreadIds.X;
if (i >= Elements) return;
int motif = InputMotifs[i];
int pathId = InputPathIds[i];
int offset = ((pathId * LevelCount) + Level) * MotifCount;
OutputMotifs[i] = JumpTables[offset + motif];
}
}
// ============================================================
// FUSED JUMP + PACKED ATTRACTOR LOOKUP
// Works for both base64 and binary hierarchies
// ============================================================
[ThreadGroupSize(256, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct FusedJumpAttractorKernel : IComputeShader
{
public readonly ReadWriteBuffer<int> InputMotifs;
public readonly ReadWriteBuffer<int> InputPathIds;
public readonly ReadWriteBuffer<int> OutputMotifs;
public readonly ReadWriteBuffer<int> OutputPackedMeta;
public readonly ReadWriteBuffer<int> JumpTables;
public readonly ReadWriteBuffer<int> PackedAttractorMetadata;
public readonly int Elements;
public readonly int MotifCount;
public readonly int LevelCount;
public readonly int Level;
public FusedJumpAttractorKernel(
ReadWriteBuffer<int> inputMotifs,
ReadWriteBuffer<int> inputPathIds,
ReadWriteBuffer<int> outputMotifs,
ReadWriteBuffer<int> outputPackedMeta,
ReadWriteBuffer<int> jumpTables,
ReadWriteBuffer<int> packedAttractorMetadata,
int elements,
int motifCount,
int levelCount,
int level)
{
InputMotifs = inputMotifs;
InputPathIds = inputPathIds;
OutputMotifs = outputMotifs;
OutputPackedMeta = outputPackedMeta;
JumpTables = jumpTables;
PackedAttractorMetadata = packedAttractorMetadata;
Elements = elements;
MotifCount = motifCount;
LevelCount = levelCount;
Level = level;
}
public void Execute()
{
int i = ThreadIds.X;
if (i >= Elements) return;
int motif = InputMotifs[i];
int pathId = InputPathIds[i];
int jumpOffset = ((pathId * LevelCount) + Level) * MotifCount;
motif = JumpTables[jumpOffset + motif];
int metaOffset = pathId * MotifCount;
int packed = PackedAttractorMetadata[metaOffset + motif];
OutputMotifs[i] = motif;
OutputPackedMeta[i] = packed;
}
}
// ============================================================
// BASE-64 ARBITRARY DECOMPOSITION
// ============================================================
[ThreadGroupSize(256, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct Base64DecomposedMultiPathKernel : IComputeShader
{
public readonly ReadWriteBuffer<int> InputMotifs;
public readonly ReadWriteBuffer<int> InputPathIds;
public readonly ReadWriteBuffer<int> OutputMotifs;
public readonly ReadWriteBuffer<int> JumpTables;
public readonly int Elements;
public readonly int MotifCount;
public readonly int LevelCount;
public readonly int RepeatCount;
public readonly int JumpBase;
public Base64DecomposedMultiPathKernel(
ReadWriteBuffer<int> inputMotifs,
ReadWriteBuffer<int> inputPathIds,
ReadWriteBuffer<int> outputMotifs,
ReadWriteBuffer<int> jumpTables,
int elements,
int motifCount,
int levelCount,
int repeatCount,
int jumpBase)
{
InputMotifs = inputMotifs;
InputPathIds = inputPathIds;
OutputMotifs = outputMotifs;
JumpTables = jumpTables;
Elements = elements;
MotifCount = motifCount;
LevelCount = levelCount;
RepeatCount = repeatCount;
JumpBase = jumpBase;
}
public void Execute()
{
int i = ThreadIds.X;
if (i >= Elements) return;
int motif = InputMotifs[i];
int pathId = InputPathIds[i];
int baseOffset = pathId * LevelCount * MotifCount;
int remaining = RepeatCount;
int d0 = remaining % JumpBase; remaining /= JumpBase;
int d1 = remaining % JumpBase; remaining /= JumpBase;
int d2 = remaining % JumpBase; remaining /= JumpBase;
int d3 = remaining % JumpBase; remaining /= JumpBase;
int d4 = remaining % JumpBase;
int o0 = baseOffset + 0 * MotifCount;
int o1 = baseOffset + 1 * MotifCount;
int o2 = baseOffset + 2 * MotifCount;
int o3 = baseOffset + 3 * MotifCount;
int o4 = baseOffset + 4 * MotifCount;
for (int k = 0; k < d0; k++) motif = JumpTables[o0 + motif];
for (int k = 0; k < d1; k++) motif = JumpTables[o1 + motif];
for (int k = 0; k < d2; k++) motif = JumpTables[o2 + motif];
for (int k = 0; k < d3; k++) motif = JumpTables[o3 + motif];
for (int k = 0; k < d4; k++) motif = JumpTables[o4 + motif];
OutputMotifs[i] = motif;
}
}
// ============================================================
// BINARY ARBITRARY DECOMPOSITION
// ============================================================
[ThreadGroupSize(256, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct BinaryDecomposedMultiPathKernel : IComputeShader
{
public readonly ReadWriteBuffer<int> InputMotifs;
public readonly ReadWriteBuffer<int> InputPathIds;
public readonly ReadWriteBuffer<int> OutputMotifs;
public readonly ReadWriteBuffer<int> JumpTables;
public readonly int Elements;
public readonly int MotifCount;
public readonly int LevelCount;
public readonly uint RepeatCount;
public BinaryDecomposedMultiPathKernel(
ReadWriteBuffer<int> inputMotifs,
ReadWriteBuffer<int> inputPathIds,
ReadWriteBuffer<int> outputMotifs,
ReadWriteBuffer<int> jumpTables,
int elements,
int motifCount,
int levelCount,
uint repeatCount)
{
InputMotifs = inputMotifs;
InputPathIds = inputPathIds;
OutputMotifs = outputMotifs;
JumpTables = jumpTables;
Elements = elements;
MotifCount = motifCount;
LevelCount = levelCount;
RepeatCount = repeatCount;
}
public void Execute()
{
int i = ThreadIds.X;
if (i >= Elements) return;
int motif = InputMotifs[i];
int pathId = InputPathIds[i];
int baseOffset = pathId * LevelCount * MotifCount;
uint mask = RepeatCount;
for (int level = 0; level < LevelCount; level++)
{
if ((mask & (1u << level)) != 0u)
{
motif = JumpTables[baseOffset + level * MotifCount + motif];
}
}
OutputMotifs[i] = motif;
}
}
// ============================================================
// FENCE
// ============================================================
[ThreadGroupSize(1, 1, 1)]
[GeneratedComputeShaderDescriptor]
public readonly partial struct FenceKernel : IComputeShader
{
public readonly ReadWriteBuffer<int> Fence;
public FenceKernel(ReadWriteBuffer<int> fence) => Fence = fence;
public void Execute() => Fence[0] = Fence[0] + 1;
}
internal static class Program
{
static void Main()
{
Console.OutputEncoding = Encoding.UTF8;
Console.WriteLine("================================================");
Console.WriteLine("TETRA COMBINATION ENGINE - V3 BINARY + REAL 30s");
Console.WriteLine("================================================");
Console.WriteLine();
var device = GraphicsDevice.GetDefault();
Console.WriteLine($"GPU:{device.Name}");
Console.WriteLine($"Elements:{Config.ELEMENTS:N0}");
Console.WriteLine($"Path count:{Config.PATH_COUNT}");
Console.WriteLine($"Path length:{Config.PATH_LENGTH}");
Console.WriteLine($"Base64 levels:{Config.BASE64_LEVELS}");
Console.WriteLine($"Binary levels:{Config.BINARY_LEVELS}");
Console.WriteLine();
Console.WriteLine("Building engine on CPU...");
var swBuild = Stopwatch.StartNew();
EngineBuildResult engine = BuildEngine();
swBuild.Stop();
Console.WriteLine($"Build time:{swBuild.Elapsed.TotalMilliseconds:F2} ms");
Console.WriteLine($"Raw states:{engine.RawStateCount:N0}");
Console.WriteLine($"Canonical motifs:{engine.MotifCount:N0}");
Console.WriteLine($"Symmetry count:{engine.SymmetryCount}");
Console.WriteLine($"Generator table:{engine.GeneratorTransitions.Length:N0} ints (~{engine.GeneratorTransitions.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
Console.WriteLine($"Path word table:{engine.PathWords.Length:N0} ints");
Console.WriteLine($"Base64 jump tables:{engine.Base64JumpTables.Length:N0} ints (~{engine.Base64JumpTables.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
Console.WriteLine($"Binary jump tables:{engine.BinaryJumpTables.Length:N0} ints (~{engine.BinaryJumpTables.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
Console.WriteLine($"Packed attractor metadata:{engine.PackedAttractorMetadata.Length:N0} ints (~{engine.PackedAttractorMetadata.Length * sizeof(int) / 1024.0 / 1024.0:F2} MB)");
Console.WriteLine();
PrintBase64LevelStats(engine);
Console.WriteLine();
PrintBinaryLevelStats(engine);
Console.WriteLine();
PrintPathSummaries(engine);
Console.WriteLine("nTesting standard GPU compute...");
var standardTime = TestStandardCompute(device);
Console.WriteLine($"Standard time:{standardTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"nTesting direct multi-path F^{Config.DIRECT_GPU_WORD_REPEATS:N0} (random pathId)...");
var directRandomTime = TestDirectMultiPath(device, engine, groupedPaths: false);
Console.WriteLine($"Direct random time:{directRandomTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"nTesting direct multi-path F^{Config.DIRECT_GPU_WORD_REPEATS:N0} (grouped pathId)...");
var directGroupedTime = TestDirectMultiPath(device, engine, groupedPaths: true);
Console.WriteLine($"Direct grouped time:{directGroupedTime.TotalMilliseconds:F2} ms");
Console.WriteLine("nTesting base64 jump J1 = F^64 (random pathId)...");
var jump64RandomTime = TestSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, level: 1, groupedPaths: false);
Console.WriteLine($"Jump J1 random time:{jump64RandomTime.TotalMilliseconds:F2} ms");
Console.WriteLine("nTesting base64 fused J1 = F^64 + attractor (random pathId)...");
var fused64RandomTime = TestFusedSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, level: 1, groupedPaths: false);
Console.WriteLine($"Fused J1 random time:{fused64RandomTime.TotalMilliseconds:F2} ms");
Console.WriteLine("nTesting base64 fused J1 = F^64 + attractor (grouped pathId)...");
var fused64GroupedTime = TestFusedSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, level: 1, groupedPaths: true);
Console.WriteLine($"Fused J1 grouped time:{fused64GroupedTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"nTesting base64 arbitrary F^{Config.ARBITRARY_REPEAT_COUNT:N0}...");
var base64ArbitraryTime = TestBase64Arbitrary(device, engine, Config.ARBITRARY_REPEAT_COUNT, groupedPaths: false);
Console.WriteLine($"Base64 arbitrary time:{base64ArbitraryTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"nTesting binary arbitrary F^{Config.ARBITRARY_REPEAT_COUNT:N0}...");
var binaryArbitraryTime = TestBinaryArbitrary(device, engine, Config.ARBITRARY_REPEAT_COUNT, groupedPaths: false);
Console.WriteLine($"Binary arbitrary time:{binaryArbitraryTime.TotalMilliseconds:F2} ms");
int exactBase64Level = TryGetExactLevel(Config.EXACT_DEEP_REPEAT, engine.Base64LevelApplications);
int exactBinaryLevel = TryGetExactLevel(Config.EXACT_DEEP_REPEAT, engine.BinaryLevelApplications);
Console.WriteLine($"nTesting exact deep base64 jump F^{Config.EXACT_DEEP_REPEAT:N0}...");
var base64ExactTime = TestFusedSingleJump(device, engine, engine.Base64JumpTables, Config.BASE64_LEVELS, exactBase64Level, groupedPaths: false);
Console.WriteLine($"Base64 exact deep time:{base64ExactTime.TotalMilliseconds:F2} ms");
Console.WriteLine($"nTesting exact deep binary jump F^{Config.EXACT_DEEP_REPEAT:N0}...");
var binaryExactTime = TestFusedSingleJump(device, engine, engine.BinaryJumpTables, Config.BINARY_LEVELS, exactBinaryLevel, groupedPaths: false);
Console.WriteLine($"Binary exact deep time:{binaryExactTime.TotalMilliseconds:F2} ms");
Console.WriteLine("n================================================");
Console.WriteLine("PERFORMANCE COMPARISON");
Console.WriteLine("================================================");
double standardMs = standardTime.TotalMilliseconds;
double directRandomMs = directRandomTime.TotalMilliseconds;
double directGroupedMs = directGroupedTime.TotalMilliseconds;
double jump64RandomMs = jump64RandomTime.TotalMilliseconds;
double fused64RandomMs = fused64RandomTime.TotalMilliseconds;
double fused64GroupedMs = fused64GroupedTime.TotalMilliseconds;
double base64ArbMs = base64ArbitraryTime.TotalMilliseconds;
double binaryArbMs = binaryArbitraryTime.TotalMilliseconds;
double base64ExactMs = base64ExactTime.TotalMilliseconds;
double binaryExactMs = binaryExactTime.TotalMilliseconds;
Console.WriteLine($"Standard compute:{standardMs:F2} ms (baseline)");
Console.WriteLine($"Direct F^64 random:{directRandomMs:F2} ms ({standardMs / directRandomMs:F2}x vs baseline)");
Console.WriteLine($"Direct F^64 grouped:{directGroupedMs:F2} ms ({standardMs / directGroupedMs:F2}x vs baseline,{directRandomMs / directGroupedMs:F2}x vs direct-random)");
Console.WriteLine($"Jump J1 random:{jump64RandomMs:F2} ms ({standardMs / jump64RandomMs:F2}x vs baseline,{directRandomMs / jump64RandomMs:F2}x vs direct-random)");
Console.WriteLine($"Fused J1 random:{fused64RandomMs:F2} ms ({standardMs / fused64RandomMs:F2}x vs baseline,{directRandomMs / fused64RandomMs:F2}x vs direct-random)");
Console.WriteLine($"Fused J1 grouped:{fused64GroupedMs:F2} ms ({standardMs / fused64GroupedMs:F2}x vs baseline,{fused64RandomMs / fused64GroupedMs:F2}x vs fused-random)");
Console.WriteLine($"Base64 arbitrary:{base64ArbMs:F2} ms");
Console.WriteLine($"Binary arbitrary:{binaryArbMs:F2} ms ({base64ArbMs / binaryArbMs:F2}x vs base64-arbitrary)");
Console.WriteLine($"Base64 exact deep:{base64ExactMs:F2} ms");
Console.WriteLine($"Binary exact deep:{binaryExactMs:F2} ms");
Console.WriteLine("n================================================");
Console.WriteLine("LOGICAL COMPRESSION");
Console.WriteLine("================================================");
double equivalentVsDirect64 = (double)Config.EXACT_DEEP_REPEAT / Config.DIRECT_GPU_WORD_REPEATS;
double estimatedDirectDeepMs = directRandomMs * equivalentVsDirect64;
Console.WriteLine($"Exact deep target:F^{Config.EXACT_DEEP_REPEAT:N0}");
Console.WriteLine($"Equivalent path applications vs direct F^64:{equivalentVsDirect64:N0}x");
Console.WriteLine($"Equivalent generator applications per element:{(long)Config.EXACT_DEEP_REPEAT * Config.PATH_LENGTH:N0}");
Console.WriteLine($"Estimated direct time for same depth (linear extrapolation):{estimatedDirectDeepMs / 1000.0:F2} s");
Console.WriteLine($"Estimated speedup of binary exact deep vs extrapolated direct:{estimatedDirectDeepMs / binaryExactMs:F2}x");
Console.WriteLine("n================================================");
Console.WriteLine("REAL 30-SECOND EXACT TASK");
Console.WriteLine("================================================");
Console.WriteLine($"Exact semantic task:F^{Config.REAL_TASK_REPEAT:N0}");
Console.WriteLine("Direct exact implementation:64 sequential launches of F^64");
Console.WriteLine("Jump exact implementation:one J2 = F^4096 lookup");
Console.WriteLine($"Duration per mode:{Config.REAL_TASK_SECONDS} seconds");
Console.WriteLine();
var realDirect = RunRealThirtySecondDirectExactTask(device, engine, groupedPaths: false);
var realJump = RunRealThirtySecondJumpExactTask(device, engine, groupedPaths: false);
PrintRealTaskResult(realDirect);
PrintRealTaskResult(realJump);
Console.WriteLine();
Console.WriteLine($"Measured exact throughput speedup (path applications/sec):{realJump.PathApplicationsPerSecond / realDirect.PathApplicationsPerSecond:F2}x");
Console.WriteLine($"Measured exact throughput speedup (generator applications/sec):{realJump.GeneratorApplicationsPerSecond / realDirect.GeneratorApplicationsPerSecond:F2}x");
Console.WriteLine($"Measured exact batch speedup:{realJump.BatchesPerSecond / realDirect.BatchesPerSecond:F2}x");
Console.WriteLine("n================================================");
Console.WriteLine("CORRECTNESS CHECK");
Console.WriteLine("================================================");
CheckCorrectness(device, engine);
Console.WriteLine("nDone. Press any key...");
Console.ReadKey();
}
// ============================================================
// BUILD ENGINE
// ============================================================
static EngineBuildResult BuildEngine()
{
var symmetryGroup = BuildA4SymmetryGroup();
var canonicalToId = new Dictionary<int, int>(capacity: 32768);
var motifPackedList = new List<int>(capacity: 32768);
int rawCount = 0;
for (int missing = 0; missing < 4; missing++)
{
for (int a = 0; a < Config.LOCAL_STATE_COUNT; a++)
{
for (int b = 0; b < Config.LOCAL_STATE_COUNT; b++)
{
for (int c = 0; c < Config.LOCAL_STATE_COUNT; c++)
{
rawCount++;
int[] slots = new int[4];
for (int i = 0; i < 4; i++) slots[i] = Config.EMPTY;
int idx = 0;
for (int s = 0; s < 4; s++)
{
if (s == missing) continue;
slots[s] = idx switch
{
0 => a,
1 => b,
_ => c
};
idx++;
}
int packed = PackSlots(slots[0], slots[1], slots[2], slots[3]);
int canonical = Canonicalize(packed, symmetryGroup);
if (!canonicalToId.ContainsKey(canonical))
{
canonicalToId[canonical] = motifPackedList.Count;
motifPackedList.Add(canonical);
}
}
}
}
}
int motifCount = motifPackedList.Count;
int[] pathWords = new int[Config.PATH_COUNT * Config.PATH_LENGTH];
for (int p = 0; p < Config.PATH_COUNT; p++)
{
for (int k = 0; k < Config.PATH_LENGTH; k++)
{
pathWords[p * Config.PATH_LENGTH + k] = Config.PATHS[p][k];
}
}
int[] generatorTransitions = new int[4 * motifCount];
for (int motif = 0; motif < motifCount; motif++)
{
int packed = motifPackedList[motif];
for (int g = 0; g < 4; g++)
{
int nextRaw = ApplyGenerator(packed, g);
int nextCanonical = Canonicalize(nextRaw, symmetryGroup);
int nextMotif = canonicalToId[nextCanonical];
generatorTransitions[g * motifCount + motif] = nextMotif;
}
}
// -------------------------
// Base64 jump tables
// -------------------------
int[] base64JumpTables = new int[Config.PATH_COUNT * Config.BASE64_LEVELS * motifCount];
long[] base64LevelApps = new long[Config.BASE64_LEVELS];
base64LevelApps[0] = 1;
for (int level = 1; level < Config.BASE64_LEVELS; level++)
base64LevelApps[level] = base64LevelApps[level - 1] * Config.BASE64_JUMP_BASE;
for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
{
int offset0 = GetTableOffset(pathId, 0, motifCount, Config.BASE64_LEVELS);
for (int motif = 0; motif < motifCount; motif++)
{
int current = motif;
for (int step = 0; step < Config.PATH_LENGTH; step++)
{
int g = pathWords[pathId * Config.PATH_LENGTH + step];
current = generatorTransitions[g * motifCount + current];
}
base64JumpTables[offset0 + motif] = current;
}
}
for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
{
for (int level = 1; level < Config.BASE64_LEVELS; level++)
{
int prevOffset = GetTableOffset(pathId, level - 1, motifCount, Config.BASE64_LEVELS);
int curOffset = GetTableOffset(pathId, level, motifCount, Config.BASE64_LEVELS);
for (int motif = 0; motif < motifCount; motif++)
{
int current = motif;
for (int r = 0; r < Config.BASE64_JUMP_BASE; r++)
{
current = base64JumpTables[prevOffset + current];
}
base64JumpTables[curOffset + motif] = current;
}
}
}
// -------------------------
// Binary jump tables
// -------------------------
int[] binaryJumpTables = new int[Config.PATH_COUNT * Config.BINARY_LEVELS * motifCount];
long[] binaryLevelApps = new long[Config.BINARY_LEVELS];
binaryLevelApps[0] = 1;
for (int level = 1; level < Config.BINARY_LEVELS; level++)
binaryLevelApps[level] = binaryLevelApps[level - 1] * 2;
for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
{
int base64Offset0 = GetTableOffset(pathId, 0, motifCount, Config.BASE64_LEVELS);
int binaryOffset0 = GetTableOffset(pathId, 0, motifCount, Config.BINARY_LEVELS);
Array.Copy(base64JumpTables, base64Offset0, binaryJumpTables, binaryOffset0, motifCount);
}
for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
{
for (int level = 1; level < Config.BINARY_LEVELS; level++)
{
int prevOffset = GetTableOffset(pathId, level - 1, motifCount, Config.BINARY_LEVELS);
int curOffset = GetTableOffset(pathId, level, motifCount, Config.BINARY_LEVELS);
for (int motif = 0; motif < motifCount; motif++)
{
int current = binaryJumpTables[prevOffset + motif];
current = binaryJumpTables[prevOffset + current];
binaryJumpTables[curOffset + motif] = current;
}
}
}
// -------------------------
// Attractor metadata from F^1
// -------------------------
int[] packedAttractorMetadata = new int[Config.PATH_COUNT * motifCount];
PathSummary[] pathSummaries = new PathSummary[Config.PATH_COUNT];
for (int pathId = 0; pathId < Config.PATH_COUNT; pathId++)
{
int offset0 = GetTableOffset(pathId, 0, motifCount, Config.BASE64_LEVELS);
int[] next = new int[motifCount];
Array.Copy(base64JumpTables, offset0, next, 0, motifCount);
AnalyzeFunctionalGraph(
next,
out int[] cycleId,
out int[] cycleRepresentative,
out int[] cycleLength,
out int[] stepsToCycle);
int maxRep = cycleRepresentative.Max();
int maxLen = cycleLength.Max();
int maxSteps = stepsToCycle.Max();
if (maxRep > Config.REP_MASK)
throw new InvalidOperationException($"Representative {maxRep} exceeds packed REP range.");
if (maxLen > Config.LEN_MASK)
throw new InvalidOperationException($"Cycle length {maxLen} exceeds packed LEN range.");
if (maxSteps > Config.STEP_MASK)
throw new InvalidOperationException($"Steps-to-cycle {maxSteps} exceeds packed STEP range.");
int metaOffset = pathId * motifCount;
for (int motif = 0; motif < motifCount; motif++)
{
packedAttractorMetadata[metaOffset + motif] =
PackMeta(cycleRepresentative[motif], cycleLength[motif], stepsToCycle[motif]);
}
int cycleCount = cycleId.Distinct().Count();
int maxCycleLength = cycleLength.Max();
float averageSteps = (float)stepsToCycle.Average();
var dominant = cycleId
.GroupBy(x => x)
.Select(g =>
{
int firstIndex = Array.IndexOf(cycleId, g.Key);
return new
{
BasinSize = g.Count(),
CycleLength = cycleLength[firstIndex],
Representative = cycleRepresentative[firstIndex]
};
})
.OrderByDescending(x => x.BasinSize)
.First();
pathSummaries[pathId] = new PathSummary
{
PathId = pathId,
CycleCount = cycleCount,
MaxCycleLength = maxCycleLength,
AverageSteps = averageSteps,
DominantBasinSize = dominant.BasinSize,
DominantCycleLength = dominant.CycleLength,
DominantRepresentative = dominant.Representative
};
}
return new EngineBuildResult
{
MotifCount = motifCount,
RawStateCount = rawCount,
SymmetryCount = symmetryGroup.Count,
CanonicalPackedByMotif = motifPackedList.ToArray(),
PathWords = pathWords,
GeneratorTransitions = generatorTransitions,
Base64JumpTables = base64JumpTables,
Base64LevelApplications = base64LevelApps,
BinaryJumpTables = binaryJumpTables,
BinaryLevelApplications = binaryLevelApps,
PackedAttractorMetadata = packedAttractorMetadata,
PathSummaries = pathSummaries
};
}
// ============================================================
// BENCHMARKS
// ============================================================
static TimeSpan TestStandardCompute(GraphicsDevice device)
{
int size = Config.ELEMENTS;
using var input = device.AllocateReadWriteBuffer<float>(size);
using var output = device.AllocateReadWriteBuffer<float>(size);
FillRandomFloatData(device, input, size, 12345);
device.For(size, new StandardComputeKernel(input, output, size, 8));
Synchronize(device);
var sw = Stopwatch.StartNew();
device.For(size, new StandardComputeKernel(input, output, size, Config.STANDARD_ITERATIONS));
Synchronize(device);
sw.Stop();
return sw.Elapsed;
}
static TimeSpan TestDirectMultiPath(GraphicsDevice device, EngineBuildResult engine, bool groupedPaths)
{
int size = Config.ELEMENTS;
using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var transitions = device.AllocateReadWriteBuffer<int>(engine.GeneratorTransitions.Length);
using var pathWords = device.AllocateReadWriteBuffer<int>(engine.PathWords.Length);
transitions.CopyFrom(engine.GeneratorTransitions);
pathWords.CopyFrom(engine.PathWords);
FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
if (groupedPaths)
FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);
device.For(size, new DirectMultiPathKernel(
inputMotifs, inputPathIds, outputMotifs,
transitions, pathWords,
size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
Synchronize(device);
var sw = Stopwatch.StartNew();
device.For(size, new DirectMultiPathKernel(
inputMotifs, inputPathIds, outputMotifs,
transitions, pathWords,
size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
Synchronize(device);
sw.Stop();
return sw.Elapsed;
}
static TimeSpan TestSingleJump(
GraphicsDevice device,
EngineBuildResult engine,
int[] jumpTables,
int levelCount,
int level,
bool groupedPaths)
{
int size = Config.ELEMENTS;
using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var jumps = device.AllocateReadWriteBuffer<int>(jumpTables.Length);
jumps.CopyFrom(jumpTables);
FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
if (groupedPaths)
FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);
device.For(size, new MultiPathJumpKernel(
inputMotifs, inputPathIds, outputMotifs,
jumps, size, engine.MotifCount, levelCount, level));
Synchronize(device);
var sw = Stopwatch.StartNew();
device.For(size, new MultiPathJumpKernel(
inputMotifs, inputPathIds, outputMotifs,
jumps, size, engine.MotifCount, levelCount, level));
Synchronize(device);
sw.Stop();
return sw.Elapsed;
}
static TimeSpan TestFusedSingleJump(
GraphicsDevice device,
EngineBuildResult engine,
int[] jumpTables,
int levelCount,
int level,
bool groupedPaths)
{
int size = Config.ELEMENTS;
using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var outputMeta = device.AllocateReadWriteBuffer<int>(size);
using var jumps = device.AllocateReadWriteBuffer<int>(jumpTables.Length);
using var meta = device.AllocateReadWriteBuffer<int>(engine.PackedAttractorMetadata.Length);
jumps.CopyFrom(jumpTables);
meta.CopyFrom(engine.PackedAttractorMetadata);
FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
if (groupedPaths)
FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);
device.For(size, new FusedJumpAttractorKernel(
inputMotifs, inputPathIds, outputMotifs, outputMeta,
jumps, meta,
size, engine.MotifCount, levelCount, level));
Synchronize(device);
var sw = Stopwatch.StartNew();
device.For(size, new FusedJumpAttractorKernel(
inputMotifs, inputPathIds, outputMotifs, outputMeta,
jumps, meta,
size, engine.MotifCount, levelCount, level));
Synchronize(device);
sw.Stop();
return sw.Elapsed;
}
static TimeSpan TestBase64Arbitrary(GraphicsDevice device, EngineBuildResult engine, int repeatCount, bool groupedPaths)
{
int size = Config.ELEMENTS;
using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var jumps = device.AllocateReadWriteBuffer<int>(engine.Base64JumpTables.Length);
jumps.CopyFrom(engine.Base64JumpTables);
FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
if (groupedPaths)
FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);
device.For(size, new Base64DecomposedMultiPathKernel(
inputMotifs, inputPathIds, outputMotifs, jumps,
size, engine.MotifCount, Config.BASE64_LEVELS, repeatCount, Config.BASE64_JUMP_BASE));
Synchronize(device);
var sw = Stopwatch.StartNew();
device.For(size, new Base64DecomposedMultiPathKernel(
inputMotifs, inputPathIds, outputMotifs, jumps,
size, engine.MotifCount, Config.BASE64_LEVELS, repeatCount, Config.BASE64_JUMP_BASE));
Synchronize(device);
sw.Stop();
return sw.Elapsed;
}
static TimeSpan TestBinaryArbitrary(GraphicsDevice device, EngineBuildResult engine, int repeatCount, bool groupedPaths)
{
int size = Config.ELEMENTS;
using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
using var outputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var jumps = device.AllocateReadWriteBuffer<int>(engine.BinaryJumpTables.Length);
jumps.CopyFrom(engine.BinaryJumpTables);
FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 12345);
if (groupedPaths)
FillGroupedPathIds(device, inputPathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 54321);
device.For(size, new BinaryDecomposedMultiPathKernel(
inputMotifs, inputPathIds, outputMotifs, jumps,
size, engine.MotifCount, Config.BINARY_LEVELS, (uint)repeatCount));
Synchronize(device);
var sw = Stopwatch.StartNew();
device.For(size, new BinaryDecomposedMultiPathKernel(
inputMotifs, inputPathIds, outputMotifs, jumps,
size, engine.MotifCount, Config.BINARY_LEVELS, (uint)repeatCount));
Synchronize(device);
sw.Stop();
return sw.Elapsed;
}
// ============================================================
// REAL 30-SECOND EXACT TASK
// ============================================================
static RealTaskResult RunRealThirtySecondDirectExactTask(GraphicsDevice device, EngineBuildResult engine, bool groupedPaths)
{
int size = Config.ELEMENTS;
using var motifsA = device.AllocateReadWriteBuffer<int>(size);
using var motifsB = device.AllocateReadWriteBuffer<int>(size);
using var pathIds = device.AllocateReadWriteBuffer<int>(size);
using var transitions = device.AllocateReadWriteBuffer<int>(engine.GeneratorTransitions.Length);
using var pathWords = device.AllocateReadWriteBuffer<int>(engine.PathWords.Length);
transitions.CopyFrom(engine.GeneratorTransitions);
pathWords.CopyFrom(engine.PathWords);
FillRandomMotifIds(device, motifsA, size, engine.MotifCount, 10101);
if (groupedPaths)
FillGroupedPathIds(device, pathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, pathIds, size, Config.PATH_COUNT, 20202);
// warmup
device.For(size, new DirectMultiPathKernel(
motifsA, pathIds, motifsB,
transitions, pathWords,
size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
Synchronize(device);
bool aToB = true;
long batches = 0;
var sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < Config.REAL_TASK_SECONDS)
{
// exact F^4096 = 64 dispatches of F^64
for (int i = 0; i < 64; i++)
{
if (aToB)
{
device.For(size, new DirectMultiPathKernel(
motifsA, pathIds, motifsB,
transitions, pathWords,
size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
}
else
{
device.For(size, new DirectMultiPathKernel(
motifsB, pathIds, motifsA,
transitions, pathWords,
size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
}
aToB = !aToB;
}
Synchronize(device);
batches++;
}
sw.Stop();
return new RealTaskResult
{
Name = "DIRECT EXACT F^4096",
Elements = size,
ExactRepeat = Config.REAL_TASK_REPEAT,
Seconds = sw.Elapsed.TotalSeconds,
BatchesCompleted = batches
};
}
static RealTaskResult RunRealThirtySecondJumpExactTask(GraphicsDevice device, EngineBuildResult engine, bool groupedPaths)
{
int size = Config.ELEMENTS;
using var motifsA = device.AllocateReadWriteBuffer<int>(size);
using var motifsB = device.AllocateReadWriteBuffer<int>(size);
using var pathIds = device.AllocateReadWriteBuffer<int>(size);
using var metaOut = device.AllocateReadWriteBuffer<int>(size);
using var jumps = device.AllocateReadWriteBuffer<int>(engine.Base64JumpTables.Length);
using var meta = device.AllocateReadWriteBuffer<int>(engine.PackedAttractorMetadata.Length);
jumps.CopyFrom(engine.Base64JumpTables);
meta.CopyFrom(engine.PackedAttractorMetadata);
FillRandomMotifIds(device, motifsA, size, engine.MotifCount, 10101);
if (groupedPaths)
FillGroupedPathIds(device, pathIds, size, Config.PATH_COUNT);
else
FillRandomPathIds(device, pathIds, size, Config.PATH_COUNT, 20202);
// warmup
device.For(size, new FusedJumpAttractorKernel(
motifsA, pathIds, motifsB, metaOut,
jumps, meta,
size, engine.MotifCount, Config.BASE64_LEVELS, Config.REAL_TASK_BASE64_LEVEL));
Synchronize(device);
bool aToB = true;
long batches = 0;
var sw = Stopwatch.StartNew();
while (sw.Elapsed.TotalSeconds < Config.REAL_TASK_SECONDS)
{
if (aToB)
{
device.For(size, new FusedJumpAttractorKernel(
motifsA, pathIds, motifsB, metaOut,
jumps, meta,
size, engine.MotifCount, Config.BASE64_LEVELS, Config.REAL_TASK_BASE64_LEVEL));
}
else
{
device.For(size, new FusedJumpAttractorKernel(
motifsB, pathIds, motifsA, metaOut,
jumps, meta,
size, engine.MotifCount, Config.BASE64_LEVELS, Config.REAL_TASK_BASE64_LEVEL));
}
aToB = !aToB;
Synchronize(device);
batches++;
}
sw.Stop();
return new RealTaskResult
{
Name = "JUMP EXACT F^4096",
Elements = size,
ExactRepeat = Config.REAL_TASK_REPEAT,
Seconds = sw.Elapsed.TotalSeconds,
BatchesCompleted = batches
};
}
// ============================================================
// CORRECTNESS
// ============================================================
static void CheckCorrectness(GraphicsDevice device, EngineBuildResult engine)
{
int size = Config.CORRECTNESS_TEST_SIZE;
using var inputMotifs = device.AllocateReadWriteBuffer<int>(size);
using var inputPathIds = device.AllocateReadWriteBuffer<int>(size);
using var directOut = device.AllocateReadWriteBuffer<int>(size);
using var jump64Out = device.AllocateReadWriteBuffer<int>(size);
using var fused64Out = device.AllocateReadWriteBuffer<int>(size);
using var fused64Meta = device.AllocateReadWriteBuffer<int>(size);
using var base64ArbOut = device.AllocateReadWriteBuffer<int>(size);
using var binaryArbOut = device.AllocateReadWriteBuffer<int>(size);
using var transitions = device.AllocateReadWriteBuffer<int>(engine.GeneratorTransitions.Length);
using var pathWords = device.AllocateReadWriteBuffer<int>(engine.PathWords.Length);
using var base64Jumps = device.AllocateReadWriteBuffer<int>(engine.Base64JumpTables.Length);
using var binaryJumps = device.AllocateReadWriteBuffer<int>(engine.BinaryJumpTables.Length);
using var meta = device.AllocateReadWriteBuffer<int>(engine.PackedAttractorMetadata.Length);
transitions.CopyFrom(engine.GeneratorTransitions);
pathWords.CopyFrom(engine.PathWords);
base64Jumps.CopyFrom(engine.Base64JumpTables);
binaryJumps.CopyFrom(engine.BinaryJumpTables);
meta.CopyFrom(engine.PackedAttractorMetadata);
FillRandomMotifIds(device, inputMotifs, size, engine.MotifCount, 777);
FillRandomPathIds(device, inputPathIds, size, Config.PATH_COUNT, 888);
device.For(size, new DirectMultiPathKernel(
inputMotifs, inputPathIds, directOut,
transitions, pathWords,
size, engine.MotifCount, Config.PATH_LENGTH, Config.DIRECT_GPU_WORD_REPEATS));
device.For(size, new MultiPathJumpKernel(
inputMotifs, inputPathIds, jump64Out,
base64Jumps,
size, engine.MotifCount, Config.BASE64_LEVELS, 1));
device.For(size, new FusedJumpAttractorKernel(
inputMotifs, inputPathIds, fused64Out, fused64Meta,
base64Jumps, meta,
size, engine.MotifCount, Config.BASE64_LEVELS, 1));
device.For(size, new Base64DecomposedMultiPathKernel(
inputMotifs, inputPathIds, base64ArbOut, base64Jumps,
size, engine.MotifCount, Config.BASE64_LEVELS, Config.ARBITRARY_REPEAT_COUNT, Config.BASE64_JUMP_BASE));
device.For(size, new BinaryDecomposedMultiPathKernel(
inputMotifs, inputPathIds, binaryArbOut, binaryJumps,
size, engine.MotifCount, Config.BINARY_LEVELS, Config.ARBITRARY_REPEAT_COUNT));
Synchronize(device);
int[] direct = new int[size];
int[] jump64 = new int[size];
int[] fused = new int[size];
int[] metaOut = new int[size];
int[] base64Arb = new int[size];
int[] binaryArb = new int[size];
int[] pathIds = new int[size];
directOut.CopyTo(direct, 0, 0, size);
jump64Out.CopyTo(jump64, 0, 0, size);
fused64Out.CopyTo(fused, 0, 0, size);
fused64Meta.CopyTo(metaOut, 0, 0, size);
base64ArbOut.CopyTo(base64Arb, 0, 0, size);
binaryArbOut.CopyTo(binaryArb, 0, 0, size);
inputPathIds.CopyTo(pathIds, 0, 0, size);
int directVsJump = 0;
int jumpVsFused = 0;
int metaMatches = 0;
int base64VsBinaryArb = 0;
for (int i = 0; i < size; i++)
{
if (direct[i] == jump64[i]) directVsJump++;
if (jump64[i] == fused[i]) jumpVsFused++;
int expectedMeta = engine.PackedAttractorMetadata[pathIds[i] * engine.MotifCount + fused[i]];
if (expectedMeta == metaOut[i]) metaMatches++;
if (base64Arb[i] == binaryArb[i]) base64VsBinaryArb++;
}
Console.WriteLine($"Direct F^64 vs jump J1 match rate:{directVsJump * 100.0 / size:F2}%");
Console.WriteLine($"Jump J1 vs fused J1 motif match rate:{jumpVsFused * 100.0 / size:F2}%");
Console.WriteLine($"Fused packed metadata match rate:{metaMatches * 100.0 / size:F2}%");
Console.WriteLine($"Base64 arbitrary vs binary arbitrary match rate:{base64VsBinaryArb * 100.0 / size:F2}%");
// Exact deep equality:base64 J4 vs binary B24
{
int sanity = 2000;
int ok = 0;
var rand = new Random(999);
for (int i = 0; i < sanity; i++)
{
int pathId = rand.Next(Config.PATH_COUNT);
int motif = rand.Next(engine.MotifCount);
int j4 = engine.Base64JumpTables[GetTableOffset(pathId, 4, engine.MotifCount, Config.BASE64_LEVELS) + motif];
int b24 = engine.BinaryJumpTables[GetTableOffset(pathId, 24, engine.MotifCount, Config.BINARY_LEVELS) + motif];
if (j4 == b24) ok++;
}
Console.WriteLine($"Base64 J4 vs binary B24 sanity:{ok * 100.0 / sanity:F2}%");
}
// Exact F^4096 equality:
// direct exact by 64x J1 vs one J2
{
int sanity = 2000;
int ok = 0;
var rand = new Random(1234);
for (int i = 0; i < sanity; i++)
{
int pathId = rand.Next(Config.PATH_COUNT);
int motif = rand.Next(engine.MotifCount);
int current = motif;
int j1Offset = GetTableOffset(pathId, 1, engine.MotifCount, Config.BASE64_LEVELS);
for (int k = 0; k < 64; k++)
current = engine.Base64JumpTables[j1Offset + current];
int j2 = engine.Base64JumpTables[GetTableOffset(pathId, 2, engine.MotifCount, Config.BASE64_LEVELS) + motif];
if (current == j2) ok++;
}
Console.WriteLine($"Exact F^4096 sanity (64x J1 == J2):{ok * 100.0 / sanity:F2}%");
}
Console.WriteLine("Sample decoded fused metadata:");
for (int i = 0; i < 5; i++)
{
int rep = UnpackRep(metaOut[i]);
int len = UnpackLen(metaOut[i]);
int steps = UnpackSteps(metaOut[i]);
Console.WriteLine(
$" sample {i}:pathId={pathIds[i]},finalMotif={fused[i]},rep={rep},cycleLen={len},stepsToCycle={steps}");
}
}
// ============================================================
// REPORTING
// ============================================================
static void PrintBase64LevelStats(EngineBuildResult engine)
{
Console.WriteLine("BASE64 JUMP HIERARCHY");
Console.WriteLine("------------------------------------------------");
for (int level = 0; level < engine.Base64LevelApplications.Length; level++)
Console.WriteLine($" J{level}:F^{engine.Base64LevelApplications[level]:N0}");
}
static void PrintBinaryLevelStats(EngineBuildResult engine)
{
Console.WriteLine("BINARY JUMP HIERARCHY");
Console.WriteLine("------------------------------------------------");
for (int level = 0; level < engine.BinaryLevelApplications.Length; level += 4)
{
var parts = new List<string>();
for (int k = 0; k < 4 && level + k < engine.BinaryLevelApplications.Length; k++)
{
int l = level + k;
parts.Add($"B{l}:F^{engine.BinaryLevelApplications[l]:N0}");
}
Console.WriteLine(" " + string.Join(" | ", parts));
}
}
static void PrintPathSummaries(EngineBuildResult engine)
{
Console.WriteLine("PATH ATTRACTOR SUMMARIES");
Console.WriteLine("------------------------------------------------");
for (int p = 0; p < engine.PathSummaries.Length; p++)
{
var s = engine.PathSummaries[p];
Console.WriteLine(
$" pathId={p} [{Config.PATH_NAMES[p]}] | cycles={s.CycleCount},maxCycle={s.MaxCycleLength},avgSteps={s.AverageSteps:F2},dominantBasin={s.DominantBasinSize:N0} ({s.DominantBasinSize * 100.0 / engine.MotifCount:F2}%),dominantLen={s.DominantCycleLength},rep={s.DominantRepresentative}");
}
}
static void PrintRealTaskResult(RealTaskResult result)
{
Console.WriteLine(result.Name);
Console.WriteLine($" Time:{result.Seconds:F2} s");
Console.WriteLine($" Batches completed:{result.BatchesCompleted:N0}");
Console.WriteLine($" Exact depth per batch:F^{result.ExactRepeat:N0}");
Console.WriteLine($" Batches/sec:{result.BatchesPerSecond:N4}");
Console.WriteLine($" Path applications/sec:{result.PathApplicationsPerSecond:N0}");
Console.WriteLine($" Generator applications/sec:{result.GeneratorApplicationsPerSecond:N0}");
}
// ============================================================
// AUTO / LEVEL HELPERS
// ============================================================
static int TryGetExactLevel(int repeatCount, long[] levelApplications)
{
for (int level = 0; level < levelApplications.Length; level++)
{
if (levelApplications[level] == repeatCount)
return level;
}
return -1;
}
// ============================================================
// PACKED META
// ============================================================
static int PackMeta(int representative, int cycleLength, int stepsToCycle)
{
return
(representative & Config.REP_MASK) |
((cycleLength & Config.LEN_MASK) << Config.LEN_SHIFT) |
((stepsToCycle & Config.STEP_MASK) << Config.STEP_SHIFT);
}
static int UnpackRep(int packed) => packed & Config.REP_MASK;
static int UnpackLen(int packed) => (packed >> Config.LEN_SHIFT) & Config.LEN_MASK;
static int UnpackSteps(int packed) => (packed >> Config.STEP_SHIFT) & Config.STEP_MASK;
// ============================================================
// GEOMETRIC / COMBINATORIAL CORE
// ============================================================
static int ApplyGenerator(int packed, int generator)
{
int[] slots = UnpackSlots(packed);
int[] perm = GetGeneratorPermutation(generator);
int[] permuted = ApplyPermutation(slots, perm);
int missing = FindEmpty(permuted);
int movedTarget;
if (missing != generator)
{
permuted[missing] = TransformMove(permuted[generator], generator, generator, missing);
permuted[generator] = Config.EMPTY;
movedTarget = missing;
}
else
{
int donor = FindFirstOccupied(permuted, generator);
permuted[generator] = TransformMove(permuted[donor], generator, donor, generator);
permuted[donor] = Config.EMPTY;
movedTarget = generator;
}
for (int s = 0; s < 4; s++)
{
if (permuted[s] == Config.EMPTY) continue;
if (s == movedTarget) continue;
permuted[s] = TransformStay(permuted[s], generator, s);
}
return PackSlots(permuted[0], permuted[1], permuted[2], permuted[3]);
}
static int TransformMove(int localState, int generator, int sourceSlot, int targetSlot)
{
if (localState == Config.EMPTY) return Config.EMPTY;
int orientation = localState % Config.ORIENTATION_COUNT;
int phase = localState / Config.ORIENTATION_COUNT;
orientation = (orientation + 1 + 3 * generator + 2 * sourceSlot + targetSlot) % Config.ORIENTATION_COUNT;
phase = (phase + 1 + generator + sourceSlot + 2 * targetSlot) % Config.PHASE_COUNT;
return phase * Config.ORIENTATION_COUNT + orientation;
}
static int TransformStay(int localState, int generator, int slot)
{
if (localState == Config.EMPTY) return Config.EMPTY;
int orientation = localState % Config.ORIENTATION_COUNT;
int phase = localState / Config.ORIENTATION_COUNT;
orientation = (orientation + GeneratorOrientationDelta(generator, slot)) % Config.ORIENTATION_COUNT;
phase = (phase + GeneratorPhaseDelta(generator, slot) + ((orientation & 3) == 0 ? 1 : 0)) % Config.PHASE_COUNT;
return phase * Config.ORIENTATION_COUNT + orientation;
}
static int GeneratorOrientationDelta(int generator, int slot)
{
return generator switch
{
0 => slot + 1,
1 => 2 * slot + 3,
2 => 3 * slot + 2,
_ => 5 + slot
};
}
static int GeneratorPhaseDelta(int generator, int slot)
{
return (generator + 2 * slot + 1) % Config.PHASE_COUNT;
}
static int[] GetGeneratorPermutation(int generator)
{
return generator switch
{
0 => new[] { 0, 2, 3, 1 },
1 => new[] { 3, 1, 0, 2 },
2 => new[] { 1, 3, 2, 0 },
_ => new[] { 2, 0, 1, 3 }
};
}
static int Canonicalize(int packed, List<int[]> symmetryGroup)
{
int best = int.MaxValue;
foreach (var perm in symmetryGroup)
{
int transformed = ApplyPackedPermutation(packed, perm);
if (transformed < best) best = transformed;
}
return best;
}
static List<int[]> BuildA4SymmetryGroup()
{
int[] a = { 0, 2, 3, 1 };
int[] b = { 1, 0, 3, 2 };
var result = new List<int[]>();
var seen = new HashSet<int>();
var queue = new Queue<int[]>();
int[] identity = { 0, 1, 2, 3 };
queue.Enqueue(identity);
seen.Add(PermutationCode(identity));
while (queue.Count > 0)
{
int[] p = queue.Dequeue();
result.Add((int[])p.Clone());
int[] pa = ComposePermutations(p, a);
int[] pb = ComposePermutations(p, b);
int codeA = PermutationCode(pa);
int codeB = PermutationCode(pb);
if (seen.Add(codeA)) queue.Enqueue(pa);
if (seen.Add(codeB)) queue.Enqueue(pb);
}
result.Sort((x, y) => PermutationCode(x).CompareTo(PermutationCode(y)));
return result;
}
static int[] ComposePermutations(int[] first, int[] second)
{
int[] result = new int[4];
for (int old = 0; old < 4; old++)
result[old] = second[first[old]];
return result;
}
static int PermutationCode(int[] perm)
{
return perm[0] + 4 * perm[1] + 16 * perm[2] + 64 * perm[3];
}
static int[] ApplyPermutation(int[] slots, int[] oldToNew)
{
int[] result = new int[4];
for (int old = 0; old < 4; old++)
result[oldToNew[old]] = slots[old];
return result;
}
static int ApplyPackedPermutation(int packed, int[] oldToNew)
{
int[] slots = UnpackSlots(packed);
int[] transformed = ApplyPermutation(slots, oldToNew);
return PackSlots(transformed[0], transformed[1], transformed[2], transformed[3]);
}
static int FindEmpty(int[] slots)
{
for (int i = 0; i < 4; i++)
if (slots[i] == Config.EMPTY)
return i;
throw new InvalidOperationException("No empty slot found.");
}
static int FindFirstOccupied(int[] slots, int startSlot)
{
for (int k = 1; k <= 3; k++)
{
int s = (startSlot + k) & 3;
if (slots[s] != Config.EMPTY)
return s;
}
throw new InvalidOperationException("No occupied slot found.");
}
static int PackSlots(int s0, int s1, int s2, int s3)
{
int p = s3;
p = p * Config.PACK_BASE + s2;
p = p * Config.PACK_BASE + s1;
p = p * Config.PACK_BASE + s0;
return p;
}
static int[] UnpackSlots(int packed)
{
int[] slots = new int[4];
for (int i = 0; i < 4; i++)
{
slots[i] = packed % Config.PACK_BASE;
packed /= Config.PACK_BASE;
}
return slots;
}
static int GetTableOffset(int pathId, int level, int motifCount, int levelCount)
{
return ((pathId * levelCount) + level) * motifCount;
}
// ============================================================
// FUNCTIONAL GRAPH / ATTRACTOR ANALYSIS
// ============================================================
static void AnalyzeFunctionalGraph(
int[] next,
out int[] cycleId,
out int[] cycleRepresentative,
out int[] cycleLength,
out int[] stepsToCycle)
{
int n = next.Length;
cycleId = new int[n];
cycleRepresentative = new int[n];
cycleLength = new int[n];
stepsToCycle = new int[n];
Array.Fill(cycleId, -1);
bool[] done = new bool[n];
int cycleCounter = 0;
for (int start = 0; start < n; start++)
{
if (done[start]) continue;
var path = new List<int>(64);
var pos = new Dictionary<int, int>(64);
int v = start;
while (true)
{
if (done[v])
{
int cid = cycleId[v];
int rep = cycleRepresentative[v];
int clen = cycleLength[v];
int steps = stepsToCycle[v];
for (int i = path.Count - 1; i >= 0; i--)
{
int node = path[i];
done[node] = true;
cycleId[node] = cid;
cycleRepresentative[node] = rep;
cycleLength[node] = clen;
stepsToCycle[node] = steps + 1;
steps++;
}
break;
}
if (pos.TryGetValue(v, out int cycleStart))
{
int cid = cycleCounter++;
int rep = path[cycleStart];
int clen = path.Count - cycleStart;
for (int i = cycleStart; i < path.Count; i++)
if (path[i] < rep) rep = path[i];
for (int i = cycleStart; i < path.Count; i++)
{
int node = path[i];
done[node] = true;
cycleId[node] = cid;
cycleRepresentative[node] = rep;
cycleLength[node] = clen;
stepsToCycle[node] = 0;
}
int steps = 1;
for (int i = cycleStart - 1; i >= 0; i--)
{
int node = path[i];
done[node] = true;
cycleId[node] = cid;
cycleRepresentative[node] = rep;
cycleLength[node] = clen;
stepsToCycle[node] = steps;
steps++;
}
break;
}
pos[v] = path.Count;
path.Add(v);
v = next[v];
}
}
}
// ============================================================
// DATA INIT / SYNC
// ============================================================
static void FillRandomFloatData(GraphicsDevice device, ReadWriteBuffer<float> buffer, int size, int seed)
{
const int CHUNK = 1_000_000;
var rand = new Random(seed);
int offset = 0;
while (offset < size)
{
int count = Math.Min(CHUNK, size - offset);
float[] tmp = new float[count];
for (int i = 0; i < count; i++)
tmp[i] = (float)(rand.NextDouble() * 2.0 - 1.0);
buffer.CopyFrom(tmp, 0, offset, count);
offset += count;
}
}
static void FillRandomMotifIds(GraphicsDevice device, ReadWriteBuffer<int> buffer, int size, int motifCount, int seed)
{
const int CHUNK = 1_000_000;
var rand = new Random(seed);
int offset = 0;
while (offset < size)
{
int count = Math.Min(CHUNK, size - offset);
int[] tmp = new int[count];
for (int i = 0; i < count; i++)
tmp[i] = rand.Next(motifCount);
buffer.CopyFrom(tmp, 0, offset, count);
offset += count;
}
}
static void FillRandomPathIds(GraphicsDevice device, ReadWriteBuffer<int> buffer, int size, int pathCount, int seed)
{
const int CHUNK = 1_000_000;
var rand = new Random(seed);
int offset = 0;
while (offset < size)
{
int count = Math.Min(CHUNK, size - offset);
int[] tmp = new int[count];
for (int i = 0; i < count; i++)
tmp[i] = rand.Next(pathCount);
buffer.CopyFrom(tmp, 0, offset, count);
offset += count;
}
}
static void FillGroupedPathIds(GraphicsDevice device, ReadWriteBuffer<int> buffer, int size, int pathCount)
{
const int CHUNK = 1_000_000;
int offset = 0;
while (offset < size)
{
int count = Math.Min(CHUNK, size - offset);
int[] tmp = new int[count];
for (int i = 0; i < count; i++)
{
long globalIndex = offset + i;
tmp[i] = (int)((globalIndex * pathCount) / size);
if (tmp[i] >= pathCount) tmp[i] = pathCount - 1;
}
buffer.CopyFrom(tmp, 0, offset, count);
offset += count;
}
}
static void Synchronize(GraphicsDevice device)
{
using var fence = device.AllocateReadWriteBuffer<int>(1);
device.For(1, new FenceKernel(fence));
int[] tmp = new int[1];
fence.CopyTo(tmp);
}
}
}
Автор: arhip1986
Источник [1]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/algoritm/447006
Ссылки в тексте:
[1] Источник: https://habr.com/ru/articles/1011646/?utm_campaign=1011646&utm_source=habrahabr&utm_medium=rss
Нажмите здесь для печати.