Комментарии 8
Не нашел ни в одном примере, как задаются связи между элементами слоя и между слоями
Связи жёстко зашиты в DefaultPhi.FromPrevLayer: каждый узел зависит от [i, i-1, i+1, i+2] предыдущего слоя (кольцевое замыкание). Это не конфигурируется через API, но можно реализовать свой IPhi с любой логикой. Однако если вы меняете радиус зависимости, интервальный пропагатор может дать неоптимальный affected set (он зашит на расширение 2 влево, 1 вправо). Для произвольных графов PhiFlow, скорее всего, не подойдёт — это библиотека для слоистых stencil-пайплайнов.
А объясните тогда, пожалуйста, пример 2 из области GameDev, где провинции и налоги, как это вообще работает? Доход провинции 1 зависит от налога провинций 1, 2, 3, 4? Как именно зависит, с какими коэффициентами?
В текущей реализации DefaultPhi зависимости детерминированные и не имеют настраиваемых коэффициентов. В примере с провинциями коэффициенты были бы единичными (все влияния равны).
Как это работает в коде
Напомню, что DefaultPhi.FromPrevLayer вычисляет значение узла так:
int p0 = prev[i]; // налог текущей провинции
int p1 = prev[Mod(i - 1, width)]; // налог левой провинции
int p2 = prev[Mod(i + 1, width)]; // налог правой провинции
int p3 = prev[Mod(i + 2, width)]; // налог следующей провинции
uint x = (uint)p0;
x ^= BitOperations.RotateLeft((uint)p1, 13);
x ^= BitOperations.RotateLeft((uint)p2, 29);
x ^= (uint)p3 * 0x9E3779B9u;
for (int k = 0; k < kWork; k++)
x = Mix32(x + (uint)p0 + ((uint)p1 ^ (uint)p2) + (uint)p3 * (uint)(k + 1));
return ReduceToDomain(x, domainSize);Что это значит для примера «провинции и налоги»
Пусть у нас 5 провинций, расположенных кольцом (провинция 0 соседствует с 4 и 1).
Провинции: [0] [1] [2] [3] [4]
Входной слой (layer 0) — налог в каждой провинции. Значения от 0 до domainSize-1 (например, 0..1000).
Выходной слой (layer 1) — доход провинции. Доход провинции i вычисляется из налогов провинций i, i-1, i+1, i+2.
То есть:
Доход(провинция 2) = F( налог(2), налог(1), налог(3), налог(4) )
Функция F — не взвешенная сумма, а хеширующая функция со следующими свойствами:
Детерминированная — одинаковый вход всегда даёт одинаковый выход.
Чувствительная к изменениям — меняется любой налог → меняется доход.
Принимает значения только в диапазоне [0..domainSize-1].
Имеет параметр kWork — количество дополнительных раундов перемешивания (чем больше, тем «сложнее» зависимость).
Почему не обычная формула вроде налог 0.7 + налог_соседа 0.3?
Потому что PhiFlow — не калькулятор линейных уравнений. Она решает другую задачу:
Нужно детерминированное отображение с предсказуемым распределением выходов.
Коэффициенты не нужны, потому что значения всё равно дискретны и домен ограничен.
Хеширующая функция даёт хорошее рассеивание — даже маленькое изменение на входе может дать любое значение на выходе. Это полезно для симуляций и тестирования, когда не нужна точная экономическая модель, а нужна быстрая цепочка вычислений.
Как сделать свой пример с коэффициентами?
Реализовать кастомный IPhi:
public class EconomicPhi : IPhi
{
private readonly float _selfWeight = 0.7f;
private readonly float _neighborWeight = 0.3f;
public int FromPrevLayer(ReadOnlySpan<int> prev, int i, int width, int domainSize, int kWork)
{
int self = prev[i];
int left = prev[Mod(i - 1, width)];
int right = prev[Mod(i + 1, width)];
// Взвешенная сумма
float weighted = _selfWeight * self + _neighborWeight * (left + right) / 2f;
// Приведение к домену
int result = (int)Math.Round(weighted);
return Math.Clamp(result, 0, domainSize - 1);
}
public int MutateInputInDomain(int oldValue, int domainSize)
{
// Простая мутация: случайный сдвиг
var newValue = oldValue + Random.Shared.Next(-10, 11);
return Math.Clamp(newValue, 0, domainSize - 1);
}
private static int Mod(int a, int m)
{
int r = a % m;
return r < 0 ? r + m : r;
}
}Использование:
var phi = new EconomicPhi();
var runtime = new PhiFlowRuntime(width: 5000, layers: 6, domainSize: 1000, phi);
runtime.SetInput(taxValues); // налоги
runtime.BuildAll(kWork: 0); // kWork не используется в кастомной логике
// Доходы в layer 1 теперь считаются по вашей формуле
var incomes = runtime.GetLayerSpan(1);Что происходит с интервальным пропагатором?
Если вы изменили радиус зависимости (например, убрали зависимость от i+2), интервальный пропагатор IntervalPropagator всё равно будет предполагать расширение 2 влево и 1 вправо, потому что он зашит на топологию DefaultPhi. Это значит:
ApplyInputUpdatesможет пересчитывать больше узлов, чем реально нужно (субоптимально, но корректно).Если вы хотите точного соответствия, нужно либо использовать полный пересчёт (
RecomputeAll), либо переопределять пропагатор.
Пример с налогами в статье иллюстрирует класс задач, где PhiFlow применима:
Есть много узлов (провинций).
Есть много слоёв (доход → счастье → производительность → сила армии).
Приходит одно изменение (налог подняли).
Нужно быстро пересчитать всё и ответить на запросы (тотальная сила армии, топ-3 самых сильных провинций).
Конкретные коэффициенты и формула — на разработчике игры. PhiFlow даёт инструмент для быстрого инкрементального пересчёта и готовые индексы для запросов. Саму экономическую модель можно написать в кастомном IPhi.
Позовите пожалуйста оператора - я хочу пообщаться с живым человеком, а не с ллмкой.
Доход(провинция 2) = F( налог(2), налог(1), налог(3), налог(4) )
В каком мире доход провинции может зависеть от налогов ее соседей??? С одинаковыми коэффициентами между всеми элементами в кольце и на всех слоях? Это абсурд, который нейросеть видимо сначала нагаллюцинировала, а теперь продолжает на голубом глазу расписывать длинными предложениями с примерами. Соответственно, остальные примеры также могут быть натягиванием совы на глобус.
да, увидел в коде stencil [i, i-1, i+1, i+2] и притянул к нему первую попавшуюся игровую метафору, пример был неудачным
P.S. Спасибо что обратили внимание, статью отредактировал, добавил корректный пример - 6 слоёв: заражение в городе → риск для соседних городов → плотность заболевших → нагрузка на больницы → дефицит ресурсов → уровень паники.
А в чем преимущество перед родным дотнетовским TPL Dataflow?
TPL Dataflow — для асинхронных конвейеров и произвольных графов. PhiFlow — для слоистых stencil-пайплайнов, где нужно после небольшого изменения получить мгновенные агрегаты (CountGreater, TopK, Sum) по всем слоям. Ускорение достигается за счёт интервального конуса влияния (не пересчитываем весь слой, только affected interval) и точных индексов (Fenwick, гистограммы). TPL Dataflow не умеет ни того, ни другого из коробки. Если вам нужно и то, и другое — поставьте PhiFlow внутрь блока Dataflow.

Реальный time series агрегатор: как обрабатывать 10 событий/сек на графе из 300k узлов