Powrót do strony głównej

Synchronizacja świetlików w Wolfram Language

Artykuł opisuje realizację modelu synchronizacji świetlików w Wolfram Language. Przejście od modelu agentowego do wektoryzowanego automatu komórkowego z użyciem konwolucji ListConvolve. Optymalizacja renderingu poprzez NumericArray i generowanie wideo. Demonstruje emergent behavior na przykładach 2–200 agentów i polach 50×50.

Świetliki synchronizują się: kod automatu komórkowego
Advertisement 728x90

Synchronizacja świetlików: od modelu agentowego do automatów komórkowych w języku Wolfram

Świetliki synchronizują błyski poprzez lokalne interakcje. Każdy agent ma wewnętrzny timer w zakresie [0,1], który zmniejsza się o 0,01 na krok. Gdy osiągnie 0, następuje błysk (stan 1), który wpływa na sąsiednie jednostki w określonym promieniu.

Kluczowy mechanizm to adaptacyjna korekcja fazy: jeśli sąsiad błysnął, aktualny timer przesuwa się do przodu lub do tyłu w zależności od pozycji w cyklu. Kierunek określa Sign[2 Round[state] - 1]: ujemny dla stanów bliższych 0, dodatni — bliższych 1.

Model agentowy: generowanie i połączenia

Najpierw umieszczamy n agentów w obszarze i nadajemy im losowe stany:

Google AdInline article slot
generateFireFlies[n_:200, region_:Rectangle[{-10,-10}, {10,10}]] := fireFlies = With[{ 
  pos = RandomPoint[region, n]
},
  Transpose[{pos, Table[RandomReal[{0,1.0}], {Length[pos]}]}]
];

Każdy świetlik to para {pozycja, stan}. Macierz połączeń obliczana jest na podstawie odległości Euklidesowej:

bakeConnections[r_:3.7] := (
  connectionMatrix = Table[
    If[i == j, Infinity, Power[Norm[i[[1]] - j[[1]]], 2]]
  , {i, fireFlies}, {j, fireFlies}];

  connectionMatrix = MapIndexed[
    Function[{value, index},
      If[value < r, index[[1]], Nothing]
    ],
    #
  ] & /@ connectionMatrix
);

Główne operacje na każdym kroku:

  • Zgaszenie: Clip[state - 0.01, {0,1}]
  • Błysk: gdy state==0 → 1
  • Korekcja: jeśli sąsiad jest w stanie 1, state + Sign[2 Round[state] - 1] * 0.0001

Aktualizacja: fireFlies = MapIndexed[adjust, decay /@ flash /@ fireFlies];

Google AdInline article slot

Testowanie na małej skali

Dla dwóch agentów synchronizacja trwa około 10 iteracji. Wykres stanów pokazuje zbieżność:

generateFireFlies[2, Circle[{0,0},0.5]];
bakeConnections[2.0];

Wizualizacja z animacją przez EventHandler[AnimationFrameListener[...]] demonstruje dopasowanie fazowe.

Przy 200 agentach używamy kolorowych dysków:

Google AdInline article slot
cf = Blend[{Darker[Red], Yellow}, #] &;
Graphics[{
  Table[
    With[{i = i, xy = fireFlies[[i,1]]},
      {RGBColor[colors[[i]]], Disk[xy, 0.3]}
    ], {i, Length[fireFlies]}
  ],
  EventHandler[AnimationFrameListener[colors], update]
}, Background->Black]

Rozmycie powtarzanej warstwy tworzy efekt świecenia.

Przejście do automatu komórkowego

Logika jest podobna do Gry w życie, ale z ciągłymi stanami. Pole o rozmiarze 25×25 inicjalizowane jest jako RandomReal[{0,1.01}, {25,25}].

Błyski definiowane są przez Floor[field]. Wpływ sąsiadów to splot 3×3 z jądrem:

ListConvolve[
  {{1.0,1.0,1.0}, {1.0,0.0,1.0}, {1.0,1.0,1.0}},
  Floor[field],
  2, 0
]

Korekcja: (2.0 Round[field] - 1.0) * Clip[convolution, {0, 0.001}]. Zgaszenie-błysk: Map[Clip[# - 0.01, {0.0,1.0}, {1.0,1.0}], f, {2}].

Pełny cykl w Refresh z częstotliwością 30 FPS:

Module[{f = RandomReal[{0,1.01}, {25,25}]}, Refresh[
  f = Map[Clip[# - 0.01, {0.0,1.0}, {1.0,1.0}]&, f, {2}];

  f = Clip[
    f + (2.0 Round[f] - 1.0)
      Clip[
        ListConvolve[
          {{1.0,1.0,1.0}, {1.0,0.0,1.0}, {1.0,1.0,1.0}},
          Floor[f],
          2, 0
        ], {0, 0.001}
      ],
    {0., 1.0}
  ];

  ArrayPlot[f, Frame->True, ColorFunction->"PlumColors"]
, 1/30.0]]

Po iteracjach powstają wzory falowe.

Optymalizacja wydajności

ArrayPlot w Refresh pasuje do prototypów, ale dla większych rozmiarów stosujemy NumericArray i Image:

field = RandomReal[{0,1.0}, {50,50}];

render = Function[Null,
  Do[
    field = Map[Clip[# - 0.01, {0.0,1.0}, {1.0,1.0}]&, field, {2}];
    field = Clip[
      field + (2.0 Round[field] - 1.0)
        Clip[ListConvolve[{{1,1,1},{1,0,1},{1,1,1}}, Floor[field], 2, 0], {0,0.001}],
      {0.,1.0}
    ];
  , {2}];

  imageBuffer = NumericArray[255.0 field, "Byte", "ClipAndRound"];
];

Image[imageBuffer, "Byte", Epilog -> EventHandler[AnimationFrameListener[imageBuffer], render], Magnification -> 20]

Na polu 50×50 powstają struktury przypominające radar.

Generowanie filmu

Do eksportu:

movie = Table[
  render[];
  ImageResize[Colorize[Image[imageBuffer]], Scaled[6], Method->"NearestNeighbor"],
  {600}
];
FrameListVideo[movie, FrameRate->60]

Model skaluje się od agentów do szybkich splotów GPU, prezentując zachowania emergentne.

Co jest ważne

  • Lokalne reguły (zgaszenie + adaptacyjna korekcja) prowadzą do globalnej synchronizacji.
  • Model agentowy → automat komórkowy: splot zastępuje pętle po sąsiadach.
  • Sign[2 Round[state] - 1] zapewnia dwukierunkową korekcję fazy.
  • NumericArray + Image optymalizują renderowanie dla dużych pól.
  • Prostokątne jądro splotu tworzy kwadratowe wzory; dla realistyczności potrzebne są jądra Gaussa.

— Editorial Team

Advertisement 728x90

Czytaj dalej