<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-11-19T21:12:42+00:00</updated><id>/feed.xml</id><title type="html">Zombie-Einstein Experiments</title><subtitle>Modelling, ML and coding projects</subtitle><entry><title type="html">Open Source Projects</title><link href="/2023/09/12/open_source_projects.html" rel="alternate" type="text/html" title="Open Source Projects" /><published>2023-09-12T00:00:00+00:00</published><updated>2023-09-12T00:00:00+00:00</updated><id>/2023/09/12/open_source_projects</id><content type="html" xml:base="/2023/09/12/open_source_projects.html"><![CDATA[<p>A couple of open-source I’ve been working on:</p>

<h2 id="jax-tqdm">JAX-Tqdm</h2>

<p><a href="https://github.com/jeremiecoullon/jax-tqdm">github.com/jeremiecoullon/jax-tqdm</a></p>

<p>This library allows you to add the popular Python <a href="https://github.com/tqdm/tqdm">tqdm progress bar</a>
to JAX compiled scans and loops. The original method was developed by
<a href="https://github.com/jeremiecoullon">Jeremie Coullon</a>, and this repo packages it up, 
and it can be installed from pip</p>

<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>pip <span class="nb">install </span>jax-tqdm
</code></pre></div></div>

<p>Its usage is as simple as annotating JAX scans or loops, e.g.</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kn">from</span> <span class="nn">jax_tqdm</span> <span class="kn">import</span> <span class="n">scan_tqdm</span>
<span class="kn">from</span> <span class="nn">jax</span> <span class="kn">import</span> <span class="n">lax</span>
<span class="kn">import</span> <span class="nn">jax.numpy</span> <span class="k">as</span> <span class="n">jnp</span>

<span class="n">n</span> <span class="o">=</span> <span class="mi">10_000</span>

<span class="o">@</span><span class="n">scan_tqdm</span><span class="p">(</span><span class="n">n</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">step</span><span class="p">(</span><span class="n">carry</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">carry</span> <span class="o">+</span> <span class="mi">1</span><span class="p">,</span> <span class="n">carry</span> <span class="o">+</span> <span class="mi">1</span>

<span class="n">last_number</span><span class="p">,</span> <span class="n">all_numbers</span> <span class="o">=</span> <span class="n">lax</span><span class="p">.</span><span class="n">scan</span><span class="p">(</span><span class="n">step</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="n">jnp</span><span class="p">.</span><span class="n">arange</span><span class="p">(</span><span class="n">n</span><span class="p">))</span>
</code></pre></div></div>

<p>see the <a href="https://github.com/jeremiecoullon/jax-tqdm#example-usage">README</a>
for more details.</p>

<h2 id="jaxpr-viz">Jaxpr-Viz</h2>

<p><a href="https://github.com/zombie-einstein/jaxpr-viz">github.com/zombie-einstein/jaxpr-viz</a></p>

<p>This library is designed to visualise JAX computation graphs. JAX has built-in
methods to visualise the HLO graph produced by JAX, but when I’ve used this, 
I’ve found it somewhat too low-level and hard to parse.</p>

<p>The intention of Jaxpr-viz it to generate higher-level representations of the
computation graph with more information about the structure of the program,
i.e. how <code class="language-plaintext highlighter-rouge">jax.jit</code> annotated sub-function are connected. It does this
by parsing the <a href="https://jax.readthedocs.io/en/latest/jaxpr.html">jaxpr</a> generated
by JAX when it parses the Python input.</p>

<p>So for example a JAX program with nested functions</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">jit</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
    <span class="k">return</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">x</span>

<span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">jit</span>
<span class="k">def</span> <span class="nf">bar</span><span class="p">(</span><span class="n">x</span><span class="p">):</span>
    <span class="n">x</span> <span class="o">=</span> <span class="n">foo</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
    <span class="k">return</span> <span class="n">x</span> <span class="o">-</span> <span class="mi">1</span>
</code></pre></div></div>

<p>when visualised by Jaxpr-viz produces:</p>

<figure class="image">
  <img src="/assets/open_source/bar_collapsed.png" alt="" />
  <figcaption></figcaption>
</figure>

<p>By default, it will collapse sub-graphs that only contain built in primitives, to
make the structure of the overall program clearer. This can be toggled to show
the full details of the computation graph:</p>

<figure class="image">
  <img src="/assets/open_source/bar_expanded.png" alt="" />
  <figcaption></figcaption>
</figure>

<p>It can also visualise more complex primitives like conditional statements</p>

<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">@</span><span class="n">jax</span><span class="p">.</span><span class="n">jit</span>
<span class="k">def</span> <span class="nf">conditional</span><span class="p">(</span><span class="n">arg</span><span class="p">):</span>
    <span class="k">return</span> <span class="n">jax</span><span class="p">.</span><span class="n">lax</span><span class="p">.</span><span class="n">cond</span><span class="p">(</span>
        <span class="n">arg</span> <span class="o">&gt;=</span> <span class="mf">0.0</span><span class="p">,</span>
        <span class="k">lambda</span> <span class="n">x_true</span><span class="p">:</span> <span class="n">x_true</span> <span class="o">+</span> <span class="mf">3.0</span><span class="p">,</span>
        <span class="k">lambda</span> <span class="n">x_false</span><span class="p">:</span> <span class="n">x_false</span> <span class="o">-</span> <span class="mf">3.0</span><span class="p">,</span>
        <span class="n">arg</span>
    <span class="p">)</span>
</code></pre></div></div>

<p>producing</p>

<figure class="image">
  <img src="/assets/open_source/conditional.png" alt="" />
  <figcaption></figcaption>
</figure>

<p>Switch statements and scans/loops are also supported.</p>

<p>See <a href="https://github.com/zombie-einstein/jaxpr-viz">the repo</a> for more details and installation 
instructions.</p>

<p>At the moment it used pydot/graphviz in the backend to produce static renders of the 
computation graph. In future, it might be nice to have a more interactive visualisation, for 
example to allow regions of the graph to be dynamically collapsed and expanded.</p>]]></content><author><name></name></author><category term="python" /><category term="jax" /><category term="open-source" /><summary type="html"><![CDATA[A couple of open-source I’ve been working on:]]></summary></entry><entry><title type="html">Continuous Probabilistic Cellular Automata Part 2: JAX and Differentiability</title><link href="/2022/12/28/probabilistic_ca_2.html" rel="alternate" type="text/html" title="Continuous Probabilistic Cellular Automata Part 2: JAX and Differentiability" /><published>2022-12-28T00:00:00+00:00</published><updated>2022-12-28T00:00:00+00:00</updated><id>/2022/12/28/probabilistic_ca_2</id><content type="html" xml:base="/2022/12/28/probabilistic_ca_2.html"><![CDATA[<p><em>The code for this project can be found on its 
<a href="https://github.com/zombie-einstein/probabilistic_ca">github repo</a></em></p>

<h2 id="introduction">Introduction</h2>

<p>This post extends previous work on probabilistic cellular automata (CA)
that can be found in <a href="/2020/06/27/probabilistic_ca.html">this post</a>
so have a read of that first.</p>

<p><em>TLDR:</em> The dynamics of a CA are described by its update rule
that maps the previous state of a cell, and its neighbours, to its updated 
state. In this extension cells are a probability distribution over possible
states. The update rules then map probability distributions to probability 
distributions and the update rules themselves can be probabilistic.</p>

<h2 id="project-improvements">Project Improvements</h2>

<p>There have been two main updates:</p>

<h3 id="log-probability-distributions">Log Probability Distributions</h3>

<p>As noted in the previous post, due to the recursive nature of the CA, using
state probabilities directly often results in numerical underflow as
probabilities decay to small values. This can be avoided using log 
probabilities and techniques like the 
“<a href="https://gregorygundersen.com/blog/2020/02/09/log-sum-exp/">log-sum-exp trick</a>”.</p>

<p>The conversion to log probabilities turned out to be fairly straightforward, 
instead of the direct probabilistic update</p>

\[\begin{align}
P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\right) = \sum_{q_{i}^{t}}P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:|\:q_{i}^{t}\right)P\left(q_{i}^{t}\right)
\end{align}\]

<p>we can use</p>

\[\begin{align}
\text{log }P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\right) &amp;= \text{log }\sum_{q_{i}^{t}}P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:|\:q_{i}^{t}\right)P\left(q_{i}^{t}\right)\\
&amp;= \text{log }\sum_{q_{i}^{t}}\text{exp}\left[\text{log }P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:|\:q_{i}^{t}\right) + \text{log}P\left(q_{i}^{t}\right)\right]
\end{align}\]

<p>Once we expand $\text{log}P\left(q_{i}^{t}\right)$, we find we need to only store</p>

\[\begin{align}
\text{log }P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\right)
\end{align}\]

<p>as the state of the CA.</p>

<p>This allows for much better numerical stability (and larger executions as 
shown below), at the cost of not being able to represent absolute values 
like zero.</p>

<figure class="image">
  <img src="/assets/prob_ca_2/prob_rules_entropy.png" alt="Time evolution of the entropy of Rules 14, 35 and 37 with small 
perturbations applied to the normal binary rule, and starting from a uniformly 
random initial state." />
  <figcaption>Time evolution of the entropy of Rules 14, 35 and 37 with small 
perturbations applied to the normal binary rule, and starting from a uniformly 
random initial state.</figcaption>
</figure>

<h3 id="jax-implementation">JAX Implementation</h3>

<p><a href="https://jax.readthedocs.io/en/latest/index.html">JAX</a> is a Python high 
performance numerical computation library that has been around for a few
years but seems to have recently gained a lot of popularity. There’s a lot
to be said about it (I particularly really love the functional API) but
it has two particularly killer features:</p>

<ul>
  <li><strong>Performance:</strong> JAX compiles to high performance code via XLA. The 
compilation and optimisation stage results in high performance CPU code, 
but can also compile to GPU or even TPU without changes to the code. In 
particular for this project this allows very fast execution of CA at 
large scales on GPU (speeding up the optimisation process detailed below).</li>
  <li><strong>Gradients:</strong> Programs written using JAX can then (usually) be
differentiated, and their gradients found (see <a href="https://jax.readthedocs.io/en/latest/notebooks/quickstart.html#taking-derivatives-with-grad">here</a>
for more details). This has numerous applications
across many areas of ML and mathematical modelling, but in this particular
case we can look at differentiation of probabilistic cellular automata.</li>
</ul>

<p>A notebook with examples of using this implementation can be found 
<a href="https://github.com/zombie-einstein/probabilistic_ca/blob/master/jax_usage.ipynb">here</a>.</p>

<h2 id="differentiability-and-optimisation">Differentiability and Optimisation</h2>

<p>The probabilistic CA effectively maps an update rule/distribution $R$ and 
initial distribution/state $S_{0}$ to an output series of states $S_{t}$:</p>

\[\begin{align}
C(R, S_{0}) \rightarrow S_{t}
\end{align}\]

<p>Once implemented in JAX we can then calculate derivatives of the output with 
respect to inputs e.g.</p>

\[\begin{equation}
dS_{t}\mathbin{/}dR \quad\quad\text{or}\quad\quad dS_{t}\mathbin{/}dS_{0}
\end{equation}\]

<p>In this example we will look at differentiating with respect to the 
update rule, and using this to optimise the rule using gradient descent.</p>

<p>For binary states the update rules is designated by 8 values, $R_{j}$, giving 
the probability of a previous state mapping to a new state</p>

\[\begin{equation}
R_{j} = P(S_{i}^{t+1}=1 | (S_{i-1}^{t}, S_{i}^{t}, S_{i+1}^{t})=j)
\end{equation}\]

<p>where $j$ just maps the possible permutations of the previous states to 
integers.</p>

<p>We can then calculate $\partial S_{t}\mathbin{/}\partial R_{j}$, and use 
gradient descent to calculate iteratively update the rule</p>

\[\begin{equation}
R_{j}^{n+1} = R_{j}^{n} - \epsilon \frac{\partial L(S_{t})}{\partial R_{j}}(R^{n}, S_{0})
\end{equation}\]

<p>where $L(S_{t})$ here is some loss function on the output state.</p>

<h3 id="example">Example</h3>

<p>The code for this example can be found in 
<a href="https://github.com/zombie-einstein/probabilistic_ca/blob/master/ca_optimisation.ipynb">this notebook</a>.</p>

<p>As a simple optimisation task we will generate a target outputs state, 
$S^{\prime}_{t}$ from a known ruleset and then starting from a random 
initial rule, move the rule towards the known rule using the loss function</p>

\[\begin{equation}
L(S_{t}) = \text{MSE}(S_{t}, S_{t}^{\prime}) 
\end{equation}\]

<p>The output generated by the CA over the course of training is shown below, 
along with the target distribution. We can see how the initial random state
produces a mostly random output (as might be expected) but the target
behaviour is revealed as we move towards the target ruleset.</p>

<figure class="image">
  <img src="/assets/prob_ca_2/training_rules.png" alt="Time series generated by CA rules over the course of the training
process, starting from a completely random initial ruleset. Each iteration
runs the CA for a fixed number of steps and evaluates $L(S_{t})$" />
  <figcaption>Time series generated by CA rules over the course of the training
process, starting from a completely random initial ruleset. Each iteration
runs the CA for a fixed number of steps and evaluates $L(S_{t})$</figcaption>
</figure>

<p>The change in loss, and gradients show some interesting behaviour (as shown 
below), training slows initially, before quickly converging after ~20,000 steps 
which is also reflected in the gradients of the individual rule components $R_{j}$</p>

<figure class="image">
  <img src="/assets/prob_ca_2/loss_and_gradients.png" alt="MSE and gradients of rule components over the course of training." />
  <figcaption>MSE and gradients of rule components over the course of training.</figcaption>
</figure>

<h2 id="conclusion--next-steps">Conclusion &amp; Next Steps</h2>

<p>The combination of a proper log probability implementation and JAXs ability to
differentiate (plus performance gain from JAX) turned out really nicely.</p>

<p>It’d now be nice to take this and see if optimisation can be used to find 
interesting probabilistic CA rules. From initial experimentation the hard part 
of this was designing a good loss function. The example here relies on having
a known target, but what aggregate loss function will generate interesting CA
rules? I suspect some interesting entropy measure, but need to do more work
on what this might look like.</p>

<p>One are where this might be fruitful is for larger state spaces. The space of
binary states is small enough to explore manually, but three states already
becomes a much bigger space, being able to search this space effectively using
gradient based methods may be an interesting result.</p>]]></content><author><name></name></author><category term="cellular-automata" /><category term="python" /><category term="jax" /><summary type="html"><![CDATA[The code for this project can be found on its github repo]]></summary></entry><entry><title type="html">Multi Agent Flock RL with Shared Experience (Part 1)</title><link href="/2020/09/26/rl_flock.html" rel="alternate" type="text/html" title="Multi Agent Flock RL with Shared Experience (Part 1)" /><published>2020-09-26T00:00:00+00:00</published><updated>2020-09-26T00:00:00+00:00</updated><id>/2020/09/26/rl_flock</id><content type="html" xml:base="/2020/09/26/rl_flock.html"><![CDATA[<p><em>The code for this project can be found on it’s 
<a href="https://github.com/zombie-einstein/flock_env">github repo</a></em></p>

<h2 id="introduction">Introduction</h2>

<p>With this project I wanted to look at applying RL to a flocking model but 
also to see if the flock as a whole can be driven by a single RL agent
interacting with itself, despite treating the members of the flock as 
individuals.</p>

<p>The boid model contains rules designed to emerge flocking behavior (as you 
might see in large bird flocks, or fish shoals) but it’d be 
interesting to see if these behaviours could be learnt from the bottom up, i.e. 
the RL agent not learning a policy for the flock as a whole, but a policy at 
the individual agent level. This might then be a nice route to generating 
behaviours for other agent based models.</p>

<h2 id="boids-model">Boids Model</h2>

<p>The boid model was developed by Craig Reynolds in 1986, and now you will likely
find lots of implementations as it’s a nice hobby project (I’ve written it
myself a couple of times). Despite its simplicity the model demonstrates rich
emergent behaviour and produces flock formations reminiscent of flocks and
shoals seen in the natural world.</p>

<p>The model is formed from a set agents referred to as “boids” each moving about
the simulated space with the ability to rotate their trajectory (steer). 
Each boid follows 3 basic rules referred to as <em>separation</em>, <em>alignment</em> and 
<em>cohesion</em></p>

<ul>
  <li><em>Separation:</em> The boid steers away from any crowding flockmates to avoid
collisions</li>
  <li><em>Alignment:</em> The boid steers toward the average heading of local flockmates</li>
  <li><em>Cohesion:</em> The boid steers towards the centre of mass of local flockmates</li>
</ul>

<figure class="image">
  <img src="/assets/rl_flock/rules.gif" alt="Basic boid flocking rules: separation, alignment and 
cohesion. The red vector indicating the desired vector the boid steers towards. 
Taken from Wikipedia" />
  <figcaption>Basic boid flocking rules: separation, alignment and 
cohesion. The red vector indicating the desired vector the boid steers towards. 
Taken from Wikipedia</figcaption>
</figure>

<p>The model progresses stepwise, at each step the boids steering according to the 
above rules, then updating boid positions in parallel. These generally form 
the basic flocking rules, though additional rules can be added for more complex
behaviour such as avoiding environmental obstacles or seeking goals.</p>

<h2 id="motivation">Motivation</h2>

<p>In this specific case having coded up boids before, the rules can be hard to 
optimize, especially as more complex situations are added (for example 
environmental objects) and different contributions to the steering vector need 
to be weighed. So it is interesting to investigate if ML can 
generate nice optimizations of the existing rule, or policies in this RL case.</p>

<p>More generally, working with agent based models in many cases requires 
modelling and optimizing parameters for large numbers of homogenous (or 
homogenous subsets of) agents which can serve as a background for more complex 
parts of the simulation. For example</p>

<ul>
  <li>An agent based stock market model might contain a large numbers
of simple strategy traders that form the background noise of the market</li>
  <li>A traffic simulator may contain large numbers of simple background agents
filling out traffic</li>
</ul>

<p>As model complexity increases it becomes increasingly hard to both manually 
program robust complex behaviours and optimize parameters that control those 
behaviours. In this case RL could be a powerful tool to generate complex 
policies for these agents from simpler goals.</p>

<p>Large scale multi-agent RL comes at the obvious computational cost of running
and training large numbers of agents. As such I thought it would be interesting
to investigate whether a single RL agent could be used to design a policy
for a set of interacting homogenous agents, and can it learn emergent or
cooperative behaviours?</p>

<h2 id="implementation">Implementation</h2>

<h3 id="training-environment">Training Environment</h3>

<p>The environment consists of a flock of agents (boids), their phase space 
stored as positions $x_{i}$, heading $\theta_{i}$ and speed $s_{i}$ 
indexed by agent $i$. The agents live on a torus (this avoids the need to track 
information on the boundaries) such that $x_{i}=0=l$ where $l$ is the 
width/height of the space (usually normalized to $1.0$).</p>

<p>The action space of the agents are discrete rotations, for example we might use
the values <code class="language-plaintext highlighter-rouge">[-π/10, 0, π/10]</code>.</p>

<p>The environment treats each agent individually, as such the bulk of the 
computational work of the environment is generating local views of the flock
for each agent, done by shifting and rotating the co-ordinates to centre on 
each agent, and also relative headings between agents (with the added 
complication of working on the toroidal space)</p>

<ul>
  <li>Generate the component matrices $x_{ij} = x_{i} \rightarrow x_{j}$ where 
$i\neq j$. These are the shortest vectors from agent $i$ to agent $j$ on the 
torus</li>
  <li>From the components generate the distance matrix $d_{ij}$ the (shortest) 
Euclidean distance between agents $i$ and $j$</li>
  <li>Generate $\theta_{ij}$ the smallest angle between the headings of
pairs of agents</li>
  <li>Sort each agents neighbours by distance from that agent, then only 
include observations from the nearest $n$ neighbouring agents</li>
  <li>Rotate the shifted vectors to align the axes with each boids heading</li>
  <li>Return the concatenated relative vectors and headings of $n$ nearest 
neighbours to each agent returning the $n_{\text{agents}}\times 3n$ matrix of 
observations for each agent</li>
</ul>

<p>The sorting step ensures that each agents local view should have common
features with other agents (as opposed to features arranged according to 
agent indices).</p>

<p>The rewards signal is based purely on the distances between neighbouring 
agents $r_{i} = \sum_{j}f(d_{ij})$. Choosing $f(d_{ij})$ was one of the more 
challenging aspects, an initial choice was as simple binary $f(d_{ij})=1$ if 
$d_{ij}$ is less than some threshold and $0$ otherwise but this seems not 
encourage the boids to move closer to each other. A continuous function 
$\exp(-\alpha d)$ encourages agents to move closer, but due to the toroidal 
space, and the nature of the flock it seems there are good solutions where a 
boid is evenly distanced rather than close to other boids.</p>

<p>Along with a penalty for being to close to other boids, the environment 
currently uses</p>

\[f(d)=
\begin{cases}
    -p &amp; \text{if  } d&lt;d_{\text{close}}\\
    \exp(-\alpha d) &amp; \text{if  } d_{\text{close}}&lt;d&lt;d_{\text{cutoff}}\\
    0 &amp; \text{otherwise}
\end{cases}\]

<p>where $p$ is a large penalty value, and $\alpha$ controls how the rewards scale 
with distance.</p>

<p>The <code class="language-plaintext highlighter-rouge">step(actions)</code> function of the environment, as per the Open-AI API 
accepts actions and advances the model one step. The environment 
expects actions for each agent’s, in this case for discrete actions, this would 
be a 1d array length $n_agents$ indexing the possible rotations. The <code class="language-plaintext highlighter-rouge">step</code> 
function in turn returns local observations and rewards for each boid i.e. the 
$n_{\text{agents}}\times 3n$ local observation matrix and $n_{\text{agent}}$ 
rewards.</p>

<h3 id="agent-based-buffer">Agent Based Buffer</h3>

<figure class="image">
  <img src="/assets/rl_flock/buffer_schematic.png" alt="Schematic of usage of the agent based memory buffer. 
Values returned for each boid are stored in the 2d buffer indexed
by simulation step and agent index" />
  <figcaption>Schematic of usage of the agent based memory buffer. 
Values returned for each boid are stored in the 2d buffer indexed
by simulation step and agent index</figcaption>
</figure>

<p>To facilitate the multiple boids of the training environment, I’ve expanded the
experience buffer to store the transition values for each agent at every step.
The buffer acts as a queue with new values replacing the oldest entries, 
indexed by the current step and agent index. This is not strictly necessary, a 
single queue could be used (just pushing the all the agent values in order) but 
this format should allow for histories to be recalled for each agent as might
be required for RL agents using recursive networks.</p>

<p>The training loop then proceeds pretty much as a regular DQN agent with 
the addition that</p>

<ul>
  <li>Experience samples are drawn uniformly from the agent and steps</li>
  <li>The actions of the dqn are generated from a matrix of local observations 
from each agent, generating an array of actions for each agent</li>
</ul>

<h2 id="results">Results</h2>

<h3 id="training">Training</h3>

<p>Training with this model revealed a number of difficulties:</p>

<ul>
  <li><em>Local Minima:</em> The model seemed to have a few local policy solutions the
agent can settle on. In particular all agents moving in straight lines or
all agents always steering in one direction, both of which clearly produce
consistent rewards, and in particularly can produce excellent rewards if
agents randomly start close to each other. This seemed to be best mitigated 
by the appropriate choice of rewards function, or potentially penalising 
simple behaviour patterns.</li>
  <li><em>Feedback:</em> Since the agent is only interacting with its own actions
as training progresses and the exploration parameter decreases the actions
produced by the agent can become highly localised on a few actions as agents 
only gain experience of the limited (and predictable) actions of the flock as
a whole. It may be beneficial to either maintain a subset of boids that act 
in a random manner, or add randomness to the application of the steering 
vectors.</li>
  <li><em>Over-training:</em> Linked to the feedback issue, the model quite easily 
overtrains. The agent seemingly developing flocking behaviours at an 
optimal number of episodes, before then loosing a lot of reactive policies
past that point.</li>
</ul>

<p>Results are very sensitive to the parameters of the training environment,
in particular the choice of velocity, steering angles and reward function. 
Despite this some nice results can be produced, demonstrating flocking 
similar to that produced by the designed boids rules despite the simple reward
function. Some nice example are shown below, for increasing number of episodes
also showing the results of overtraining.</p>

<figure class="image">
  <img src="/assets/rl_flock/eps_050.gif" alt="50 episodes" width="500" />
  <figcaption>Flock behaviour after 50 episodes. The colour of boids indicates 
  the current rewards of the boid.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/rl_flock/eps_100.gif" alt="100 episodes" width="500" />
  <figcaption>Flock behaviour after 100 episodes. The colour of boids indicates 
  the current rewards of the boid.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/rl_flock/eps_125.gif" alt="125 episodes" width="500" />
  <figcaption>Flock behaviour after 125 episodes. The colour of boids indicates 
  the current rewards of the boid.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/rl_flock/eps_175.gif" alt="175 episodes" width="500" />
  <figcaption>Flock behaviour after 175 episodes. The colour of boids indicates 
  the current rewards of the boid. At the point the agent seems to have
  overtrained and boids show little interaction, and have settled on
  a simple policy of rotating at every step.</figcaption>
</figure>

<h2 id="further-work">Further Work</h2>

<p>I’ve labelled this post part 1 as I’ve I feel there are a number of interesting
directions to take this that I want to follow up on:</p>

<ul>
  <li><em>Continuous action space:</em> A discrete action space was chose in this case
to make use of a DQN agent, but the extension to a continuous space is simple
to implement; allowing for continuous steering values, and potentially 
changes in velocity.</li>
  <li><em>Environmental Obstacles:</em> Basic obstacles should be a simple extension, 
in particular spherical objects, with an associated penalty for interception,
should be simple to add.</li>
  <li><em>Competitive Agents:</em> It may be interesting to add adversarial agents into 
the model perhaps representing predators, or competition for resources. This
agents or agent(s) could then also be driven by an RL agent in the same manner. 
This may also have the benefit of driving the flock agent to generate more novel
and robust policies compared to the simple flocking policy.</li>
  <li><em>Vision Model:</em> Part of the difficulty in designing the environment was 
creating observations of the flock with fixed dimensions to be passed to the
RL agent. This may actually be easier if this is done using a view model
for each agent, i.e. each boids generates pixels representing the field
of vision of each boid. This would create a standard observation format that 
would more easily accommodate additional complexity in the model. In practice
though this would likely require a ray tracing for each boid which could be 
potentially very expensive for large flocks of agents.</li>
</ul>

<p><em>Modules for the environment and buffer, as well as examples of usage can
be found on the <a href="https://github.com/zombie-einstein/flock_env">github repo</a>.
The environment follows the Open AI gym API so should be compatible with 
RL agents using this that format!</em></p>]]></content><author><name></name></author><category term="rl" /><category term="boids" /><category term="multi-agent" /><summary type="html"><![CDATA[The code for this project can be found on it’s github repo]]></summary></entry><entry><title type="html">Firefly Networks</title><link href="/2020/07/18/firefly_network.html" rel="alternate" type="text/html" title="Firefly Networks" /><published>2020-07-18T00:00:00+00:00</published><updated>2020-07-18T00:00:00+00:00</updated><id>/2020/07/18/firefly_network</id><content type="html" xml:base="/2020/07/18/firefly_network.html"><![CDATA[<p><em>The code for this project can be found on it’s 
<a href="https://github.com/zombie-einstein/fireflies">github repo</a></em></p>

<h2 id="introduction">Introduction</h2>

<p>I read 
<a href="https://www.researchgate.net/publication/252350273_Firefly_Synchronization_in_Ad_Hoc_Networks">this paper</a>
a while ago and thought the problem it looked at was really interesting. It
seems like it might be nice to see if RL could be applied to generating
synchronization. This is an interim post looking at the implementation of the
model, which will hopefully be used as a RL training environment.</p>

<h2 id="theory">Theory</h2>

<p>As per the linked paper, this model models firefly swarms which synchronize 
their light flashes in a distributed manner. In the model nodes represent
fireflies, and each firefly fires/flashes based on its phase $\phi(t)$ and 
threshold phase $\phi_{t}$. In isolation a fireflies phase increases linearly
over time until it reaches $\phi_{t}$ where the firefly fires and resets its
phase to 0 (i.e. in isolation the firefly will oscillate firing at regular
intervals).</p>

<figure class="image">
  <img src="/assets/fireflies/time_evolution.jpg" alt="Time evolution of the phase. With no observed events (a) $\phi$
increases linearly until $\phi_{t}$ at which point if fires and resets.
When a signal is observed (b) $\phi$ is incremented by $\Delta\phi(\phi).$
Image taken from 'Firefly Synchronization in Ad-Hoc Networks': Tyrell,
Bettstetter &amp; Auer, 2006." />
  <figcaption>Time evolution of the phase. With no observed events (a) $\phi$
increases linearly until $\phi_{t}$ at which point if fires and resets.
When a signal is observed (b) $\phi$ is incremented by $\Delta\phi(\phi).$
Image taken from 'Firefly Synchronization in Ad-Hoc Networks': Tyrell,
Bettstetter &amp; Auer, 2006.</figcaption>
</figure>

<p>In an effort to co-ordinate their flashes the fireflies react to flashes from
other fireflies, updating their phase as $\phi\rightarrow\phi+\Delta\phi$ 
where the update depends on the current phase</p>

\[\phi+\phi\Delta\phi = \text{min}(\alpha\phi+\beta, 1)\]

<p>where</p>

\[\alpha=\exp(b\epsilon) \quad\text{and}\quad\beta=\frac{\exp(b\epsilon)-1}{\exp(b)-1}\]

<p>In the case the signals are instantaneous the synchronization eventually 
always occurs. The more interesting case is where there are delays in the 
signal (or this could be thought of as a finite propagation speed).</p>

<h2 id="implementation">Implementation</h2>

<p>Given the longer term goal of using this as an RL training environment time 
is discrete, with all nodes updated at each step. In the case that signals
propagate instantly this model would be almost trivial to implement. Simulating
delayed signals is done by placing events in the future of each node, so they
are processed with a delay as the model steps forward.</p>

<p>The model is initialized for a fixed number of steps $s$ and $n$ nodes 
along with:</p>

<ul>
  <li>A $s\times n$ array $P$ storing the phase of each node at time step $t$ of 
the model</li>
  <li>A $n\times n$ distance matrix $D$ representing the transmission time between 
each pair of nodes</li>
  <li>A $(s+\max(D))\times n$ array that will track events observed by each node.
The additional $max(D)$ rows are required to always allow events to 
set in the future of each node.</li>
</ul>

<p>The threshold phase $\phi_{t}$ is fixed at $1$, so the size of a time step
is effectively controlled by a parameter $\delta\phi$ which is the amount the
phase is increase for each node when no signal is observed</p>

\[\phi(t) = \phi(t-1)+\delta\phi\]

<p>At each step the model then advances using (in pseudocode)</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">for</span> <span class="n">each</span> <span class="n">node</span>
    <span class="k">if</span> <span class="n">phase</span> <span class="o">&gt;=</span> <span class="n">threshold</span> <span class="o">-&gt;</span> <span class="n">phase</span> <span class="o">=</span> <span class="mi">0</span>

<span class="k">for</span> <span class="n">each</span> <span class="n">node</span>
    <span class="k">if</span> <span class="n">phase</span> <span class="o">==</span> <span class="mi">0</span>
        <span class="k">for</span> <span class="n">each</span> <span class="n">node</span> <span class="n">x</span> <span class="p">(</span><span class="ow">not</span> <span class="n">including</span> <span class="n">this</span> <span class="n">node</span><span class="p">)</span>
            <span class="n">increment</span> <span class="n">the</span> <span class="n">number</span> <span class="n">of</span> <span class="n">events</span> <span class="k">for</span> <span class="n">x</span> <span class="n">at</span> <span class="n">time</span> <span class="n">t</span><span class="o">+</span><span class="n">distance</span>

<span class="n">step</span> <span class="o">&lt;-</span> <span class="n">step</span><span class="o">+</span><span class="mi">1</span>

<span class="k">for</span> <span class="n">each</span> <span class="n">node</span>
    <span class="k">if</span> <span class="n">events</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">node</span><span class="p">]</span> <span class="o">&gt;</span> <span class="mi">0</span> 
        <span class="n">node</span><span class="o">-</span><span class="n">phase</span> <span class="o">=</span> <span class="n">phase_update</span><span class="p">(</span><span class="n">phase</span><span class="p">)</span>
    <span class="k">else</span>
        <span class="n">node</span><span class="o">-</span><span class="n">phase</span> <span class="o">=</span> <span class="n">node</span><span class="o">-</span><span class="n">phase</span> <span class="o">+</span> <span class="n">delta</span><span class="o">-</span><span class="n">phase</span></code></pre></figure>

<p>effectively at each step, the nodes fires if its phase is at the threshold
value. This firing places an event at step $t+\text{distance}$ for each node
(the events are then effectively in the future of each node as we step the 
model forward). We then advance time and check if any events have been observed
and update each nodes phase accordingly.</p>

<figure class="image">
  <img src="/assets/fireflies/event_placement.png" alt="Events produced by a node are placed in the future 
of other nodes in the model to simulate delays in signals" />
  <figcaption>Events produced by a node are placed in the future 
of other nodes in the model to simulate delays in signals</figcaption>
</figure>

<p><em>Note: This does have the drawback that event distances have to be integer 
values (as it then informs where in the array to add the event). Events
have to be placed at minimum size of $1$ to be seen. This does mean a distance
$0$ can be used to implement a lack of communication between nodes.</em></p>

<p>This implementation has been chosen with a couple of things in mind:</p>

<ul>
  <li>Using this as an RL environment will be easier updates are step based.</li>
  <li>Implemented in numpy with pre-allocated arrays for the state of the model is
pretty fast. I think this could be done for continuous time with 
appropriate scheduling of the event observation, but feel it would be a lot
more computation to handle queues of events for each node.</li>
</ul>

<h2 id="conclusion">Conclusion</h2>

<figure class="image">
  <img src="/assets/fireflies/agent_phase.png" alt="Phase evolution over time represented by $\cos(\phi(t)))$ for a 
subset of nodes showing how nodes react to signals." />
  <figcaption>Phase evolution over time represented by $\cos(\phi(t)))$ for a 
subset of nodes showing how nodes react to signals.</figcaption>
</figure>

<p>The real trick in this model was modelling the delay/travel time of signals by
placing events in the future of each node. It’s interesting to see how the 
co-ordination breaks down as signal delays increase, or gaps are created in the
network.</p>

<p>As noted in the introduction I’d like to use this to train an RL model at a 
 node level, that is the agent should choose how to update it’s phase
 given the current phase and observed events, and if this results in global 
 coordination.</p>

<p>The code to run the current model,and examples of usage can be found on the
 <a href="https://github.com/zombie-einstein/fireflies">github repo</a>.</p>]]></content><author><name></name></author><category term="python" /><category term="networks" /><category term="rl" /><summary type="html"><![CDATA[The code for this project can be found on it’s github repo]]></summary></entry><entry><title type="html">Cellular Automata Causal Regions</title><link href="/2020/07/12/ca_causal_regions.html" rel="alternate" type="text/html" title="Cellular Automata Causal Regions" /><published>2020-07-12T00:00:00+00:00</published><updated>2020-07-12T00:00:00+00:00</updated><id>/2020/07/12/ca_causal_regions</id><content type="html" xml:base="/2020/07/12/ca_causal_regions.html"><![CDATA[<p><em>The code for this project can be found on it’s 
<a href="https://github.com/zombie-einstein/ca_causal_regions">github repo</a></em></p>

<h2 id="introduction">Introduction</h2>

<p>This project was off-shoot of the work on 
<a href="/2020/06/27/probabilistic_ca.html">probabilistic cellular automata</a>
the intention was to investigate how causal dependencies propagate through
a 1d cellular automata (CA), and how these might inform identification of the 
behaviour of cellular automata.</p>

<h2 id="theory">Theory</h2>

<p>If we consider cellular automata rules that only consider a cells nearest 
neighbours the ca update rule maps triples (of cells) to the state of the cell
at the next step. We can represent the state of the cellular automata as 
triples, on graph that represents adjacent and overlapping triples, where an 
edge represent shifting a triple of cells to the left and appending the next 
state (the generalization of this concept is the 
<a href="https://en.wikipedia.org/wiki/De_Bruijn_graph">de Bruijn Graph</a>) i.e. if we
move along the state array from left to right we are walking along the 
corresponding directed graph.</p>

<figure class="image">
  <img src="/assets/causal_regions/triples_graph.png" alt="Each node on the graph represents a triple of cells (in this case 
for binary states) and edges the result of shifting the triple left and
appending the next cell in the sequence i.e. a node is adjacent to another
if it overlaps its neighbours pattern. In this manner we can represent the 
state of the CA as a walk on this graph, following edges as we move along the 
array." />
  <figcaption>Each node on the graph represents a triple of cells (in this case 
for binary states) and edges the result of shifting the triple left and
appending the next cell in the sequence i.e. a node is adjacent to another
if it overlaps its neighbours pattern. In this manner we can represent the 
state of the CA as a walk on this graph, following edges as we move along the 
array.</figcaption>
</figure>

<p>For different rules we can then look at how the rule maps triple to triples, 
for example rule 110 is defined by the mapping from triple to states:</p>

<table>
  <thead>
    <tr>
      <th>000</th>
      <th>001</th>
      <th>010</th>
      <th>011</th>
      <th>100</th>
      <th>101</th>
      <th>110</th>
      <th>111</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>then the possible updates for a triple-to-triples (as opposed to 
triples-to-states) from applying rule 110 can be represented by adjacency 
matrices:</p>

<figure class="image">
  <img src="/assets/causal_regions/triple_updates.png" alt="Possible updates from triple to triples by applying rule 110 for
triples 1 (1,0,0) and 6 (0,1,1).
The adjacency matrix shows the possible overlapping neighbours of a triple
(on the left and right) and the corresponding updated triple given the 
neighbourhood.
" />
  <figcaption>Possible updates from triple to triples by applying rule 110 for
triples 1 (1,0,0) and 6 (0,1,1).
The adjacency matrix shows the possible overlapping neighbours of a triple
(on the left and right) and the corresponding updated triple given the 
neighbourhood.
</figcaption>
</figure>

<p>From this it should be clear that the state of a triple can be causally 
dependent on the previous state in 4 ways:</p>

<ul>
  <li>The triple always maps to the same value, independent of the neighbourhood</li>
  <li>The next state of the triple depends on both it’s neighbours</li>
  <li>The next state of the triple depends on either it’s left or right 
neighbours only</li>
</ul>

<p>We can then use this to examine the <em>causal region</em> that precedes a cell, that 
is if the state $s$ of triple at position $i$ at step $t$ of the model is 
$s_{i}^{t}$ then we say the causal region are preceding triples that the triple 
$s_{i}^{t}$ depends on. For example if the state of a triple $s_{i}^{t}$ is
independent of its neighbours then it’s causal region contains $s_{i}^{t-1}$
(and we can then recursively follow this backward). If $s_{i}^{t}$ depends
on only on it’s preceding left neighbour then the causal region contains 
$s_{i-1}^{t-1}$ and $s_{i}^{t-1}$.</p>

<h2 id="implementation">Implementation</h2>

<p>As usual this was quick to implement in numpy with some judicious use of
slicing operations. The algorithm to generate plots showing the size of
causal regions followed the following steps:</p>

<ul>
  <li>Generated the phase space array for the basic CA model (i.e. the space-time
state of cells).</li>
  <li>For each cell look-up it’s dependency on its neighbourhood. For each cell
assign it a pair containing it’s left and right dependency, where 0 indicates 
the cell is only dependent on the previous cell and $\pm 1$ indicates the 
cell is dependent on it’s neighbour.
For example:
    <ul>
      <li><code class="language-plaintext highlighter-rouge">( 0,0)</code> indicates the cell only depends on the previous cell</li>
      <li><code class="language-plaintext highlighter-rouge">(-1,0)</code> indicates the cell is dependent on the left (but not the right)</li>
      <li><code class="language-plaintext highlighter-rouge">( 0,1)</code> indicates the cell is dependent on the right (but not the left)</li>
      <li><code class="language-plaintext highlighter-rouge">(-1,1)</code> indicates the cell is dependent on the left and right</li>
    </ul>
  </li>
  <li>Iterate forward over rows (i.e. time) and for each row, add the contributions 
from the previous row dependent on the causal dependence.</li>
</ul>

<p>In the final step we are applying something like the following to each cell:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="n">decay</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">s</span><span class="o">+</span><span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="mi">0</span><span class="p">]][</span><span class="mi">0</span><span class="p">]</span>
<span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="n">decay</span><span class="o">*</span><span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="o">-</span><span class="mi">1</span><span class="p">][</span><span class="n">s</span><span class="o">+</span><span class="n">s</span><span class="p">[</span><span class="n">t</span><span class="p">][</span><span class="n">i</span><span class="p">][</span><span class="mi">1</span><span class="p">]][</span><span class="mi">1</span><span class="p">]</span></code></pre></figure>

<p>the <code class="language-plaintext highlighter-rouge">decay</code> term applies more weight to contributions from more recent rows, 
and also means the size of the causal region does not just explode over time.</p>

<p>The result of this algorithm is a 3d array in the shape <code class="language-plaintext highlighter-rouge">[steps][width][2]</code>
where the final index is the size of the casual region in the left and right
directions on the array respectively. To be able to plot this means flattening 
using some function that helps represent the underlying dynamics (this was also 
a similar issue in the 
<a href="/2020/06/27/probabilistic_ca.html">probabilistic cellular automata</a> 
project). I attempted to try and capture both the magnitude of the causal 
region and the imbalance between left and right causality and so settled
on</p>

\[\frac{\vert r_{i}^{t}\vert-\vert l_{i}^{t}\vert}{\vert r_{i}^{t}\vert+\vert l_{i}^{t}\vert}\]

<p>where $l_{i}^{t}$ and $r_{i}^{t}$ are the left and right dependencies 
respectively.</p>

<h2 id="results">Results</h2>

<p>I’ve chery-picked some of the interesting examples here, in most cases where
the rule has very simple behaviour (i.e. where the rule evolves to a fixed or
oscillating pattern) the causal region plot doesn’t reveal much over the 
phase space-diagram. The most interesting cases seem to be where the 
rule generates propagating structures over complex/random regions, in these
cases the causal regions seem to nicely pick out these structures from the
background noise:</p>

<figure class="image">
  <img src="/assets/causal_regions/rule_018.png" alt="Time evolution of Rule 18 from a random initial state 
along with the corresponding causal region evolution." />
  <figcaption>Time evolution of Rule 18 from a random initial state 
along with the corresponding causal region evolution.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/causal_regions/rule_062.png" alt="Time evolution of Rule 62 from a random initial state 
along with the corresponding causal region evolution." />
  <figcaption>Time evolution of Rule 62 from a random initial state 
along with the corresponding causal region evolution.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/causal_regions/rule_122.png" alt="Time evolution of Rule 122 from a random initial state 
along with the corresponding causal region evolution." />
  <figcaption>Time evolution of Rule 122 from a random initial state 
along with the corresponding causal region evolution.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/causal_regions/rule_126.png" alt="Time evolution of Rule 126 from a random initial state 
along with the corresponding causal region evolution." />
  <figcaption>Time evolution of Rule 126 from a random initial state 
along with the corresponding causal region evolution.</figcaption>
</figure>

<p>In the case of rules that produce totally chaotic behaviour, the causal region
plot itself seems to contain the same amount of noise.</p>

<figure class="image">
  <img src="/assets/causal_regions/rule_030.png" alt="Time evolution of Rule 30 from a random initial state 
along with the corresponding causal region evolution." />
  <figcaption>Time evolution of Rule 30 from a random initial state 
along with the corresponding causal region evolution.</figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>Like the 
<a href="/2020/06/27/probabilistic_ca.html">probabilistic cellular automata</a> 
project I feel the algorithm and implementation turned out really nicely
but there was not a strong conclusion to be drawn from the results. As noted
above in a few cases it seems to nicely pick out patterns from the background 
noise, but this is not general across the board.</p>

<p>I think part of this might be down to choice of function to flatten the 
causal information, this is one place where a better choice (or choice of
plotting method might reveal more information) though it still seems like 
it would need to contain information on both the size of the preceding
causal region and the dependence on direction.</p>

<p>As usual the code to produce these plots is available 
<a href="https://github.com/zombie-einstein/ca_causal_regions">here</a> with examples.</p>]]></content><author><name></name></author><category term="cellular-automata" /><category term="python" /><category term="causality" /><summary type="html"><![CDATA[The code for this project can be found on it’s github repo]]></summary></entry><entry><title type="html">Functional ABM API</title><link href="/2020/06/29/functional_abm.html" rel="alternate" type="text/html" title="Functional ABM API" /><published>2020-06-29T00:00:00+00:00</published><updated>2020-06-29T00:00:00+00:00</updated><id>/2020/06/29/functional_abm</id><content type="html" xml:base="/2020/06/29/functional_abm.html"><![CDATA[<p><em>The code for this project can be found on it’s 
<a href="https://github.com/zombie-einstein/functional_abm">github repo</a></em></p>

<h2 id="introduction">Introduction</h2>

<p>Most agent based modeling (ABM) frameworks tend to make use of an 
object-oriented (OOP) principals, and probably with good reason. The agents<br />
in an ABM are generally stateful and thus natural candidates for 
representation by classes encapsulating state and functionality; and 
inheritance (or composition) allows common functionality to be reused.</p>

<p>This project was an experiment in creating a “functional” ABM API inspired
in part by computation graph building APIs. Generally speaking these allow
a developer to construct a computation graph from function definitions with
the work of linking everything together done in the background (something like
<a href="https://github.com/dagster-io/dagster">dagster</a> is a good example).</p>

<p>It was also motivated by a couple of things I’ve noticed when writing 
ABMs using OOP patterns (though these are very subjective!):</p>

<ul>
  <li>Python has some really great OOP features, but it sometimes feels that the
flexibility and dynamic nature of python makes using OOP patterns a bit 
tedious when writing an ABM; where you often want interfaces and 
encapsulation to be followed quite strictly. This is obviously less of a 
concern in other typed/compiled languages, but then you lose the flexibility 
and speed of work in python.</li>
  <li>I also feel I end up writing a lot of boilerplate code, and ABM APIs
tend not to offer much apart from templates classes, or the patterns they do 
recommend tend to be quite restrictive.</li>
</ul>

<p>So the aim was to try and create and API that did a lot of work in the
background, and offered a clean and flexible API to build a model.</p>

<blockquote>
  <p><strong>Disclaimer</strong> <em>The design I ended up with did not turn out strictly
“functional” as it still sometimes relies on updating objects in-place for 
reasons that will be outlined in this post. Though I feel a proper functional
approach is possible with some tweaks.</em></p>
</blockquote>

<h2 id="theory">Theory</h2>

<p>Very an broadly an agent based model consists of agents that interact in some
manner (possibly inside a simulated environment). As the model progress
the state of agents are updated depending on the state of other agents
and the environment. It’s usually the job of the model designer to 
dictate how the agents behave and also how agents interact within the 
model/environment.</p>

<p>At the core of this is the concept of representing the time evolution of the 
model as a causal graph.</p>

<p>On the graph agents are represented by nodes, and edges represent causal 
dependence between agents. Each agent owns its own state, so altogether
the agents represent the model/simulation environment, and the graph how the 
components of the environment interact over time.</p>

<p>Each node (i.e. agent) then has:</p>

<ul>
  <li><em>Nodes that precede it causally</em>: The state of these nodes then act as 
inputs to the update function of the node. These nodes could also 
be thought of as being the observed state of the model when the node updates</li>
  <li>Downstream nodes that the node can update. This allows the node to alter
the environment outside the state it owns.</li>
</ul>

<figure class="image">
  <img src="/assets/functional_abm/causal_graph.png" alt="Representation of an ABM as a causal graph:&lt;br&gt;
a) Agents are represented as nodes on a causal graph, directed edges 
represent where an agent is dependent on the state of other agents, and the 
direction of the arrows indicate the direction of time direction. 
Unlike a DAG computational graph the graph can be recursive with agent 
updating at multiple time-steps.&lt;br&gt;
b) Each agent(node) has nodes in its causal past that act as 
inputs/observations, and downstream nodes that it can alter. 
" />
  <figcaption>Representation of an ABM as a causal graph:<br />
a) Agents are represented as nodes on a causal graph, directed edges 
represent where an agent is dependent on the state of other agents, and the 
direction of the arrows indicate the direction of time direction. 
Unlike a DAG computational graph the graph can be recursive with agent 
updating at multiple time-steps.<br />
b) Each agent(node) has nodes in its causal past that act as 
inputs/observations, and downstream nodes that it can alter. 
</figcaption>
</figure>

<p>If you are familiar with DAGS (or computational graphs, neural networks etc.)<br />
this will likely seem familiar with the differences being that the causal 
graph can be recursive (as agents can update multiple times/repeatedly) and 
the agents are stateful.</p>

<p>To represent this in a functional framework we break up an agent definition
into two components</p>

<ul>
  <li>The state of the agent</li>
  <li>An update function that is called when the agent is updated</li>
</ul>

<p>When the agent is updated, its update function is called with 
the states of the preceding agents and the current state of the agent
as arguments. The update function then returns the new state of the node, 
and any updates to the state of downstream nodes.</p>

<figure class="image">
  <img src="/assets/functional_abm/update_function.png" alt="Functional implementation of agent updates. When an agent is 
updated its update function is called, with the inputs being the upstream
nodes and current state of the agent and the outputs the new state of the 
node and any updates to downstream nodes." />
  <figcaption>Functional implementation of agent updates. When an agent is 
updated its update function is called, with the inputs being the upstream
nodes and current state of the agent and the outputs the new state of the 
node and any updates to downstream nodes.</figcaption>
</figure>

<h2 id="implementation">Implementation</h2>

<h3 id="agent-decorator"><code class="language-plaintext highlighter-rouge">@agent</code> Decorator</h3>

<p>An agents behaviour can be defined by decorating an update function with 
the <code class="language-plaintext highlighter-rouge">@agent</code> decorator. The function should have the signature</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="o">@</span><span class="n">agent</span><span class="p">(</span><span class="n">scheduler</span><span class="o">=</span><span class="n">scheduler</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">foo</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">antecedents</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="n">descendants</span><span class="p">):</span>
    <span class="c1"># Update state and descendants
</span>    <span class="p">...</span>
    <span class="k">return</span> <span class="n">next_event_time</span></code></pre></figure>

<p>When the update function is called the arguments will be</p>

<ul>
  <li><code class="language-plaintext highlighter-rouge">t</code>: The current model time</li>
  <li><code class="language-plaintext highlighter-rouge">antecedents</code>: Data structure containing the states of preceding nodes</li>
  <li><code class="language-plaintext highlighter-rouge">state</code>: The state of the node that is updating</li>
  <li><code class="language-plaintext highlighter-rouge">descendants</code>: Data structure containing states of descendant nodes
that this node can update</li>
</ul>

<p>and it returns one value, the time this agent will next update.</p>

<p>The decorator argument <code class="language-plaintext highlighter-rouge">scheduler</code> is a class that controls when agents are 
called and should be provided when the agent is defined (provided as part of 
the package).</p>

<p>In this example the decorator would create a new type (class) 
called <code class="language-plaintext highlighter-rouge">foo</code> wrapping the update function and allowing it to be used as a 
component of the model.</p>

<hr />
<p><em>As noted above this is where the ‘functional’ approach breaks down as a bit
as the update function is expected to update the <code class="language-plaintext highlighter-rouge">state</code> and <code class="language-plaintext highlighter-rouge">descendants</code> in 
place. This should be possible by instead having the function return 
new state and descendants</em>
<em>This was done here as in some cases it’s not possible to store a reference
and assign a value to it. For example for numpy arrays if you try something</em></p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">x</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">])</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">x</span><span class="p">[:</span><span class="mi">1</span><span class="p">]</span>
<span class="n">y</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="n">array</span><span class="p">([</span><span class="mi">10</span><span class="p">])</span></code></pre></figure>

<p><em>this will not update the slice of <code class="language-plaintext highlighter-rouge">x</code> that <code class="language-plaintext highlighter-rouge">y</code> refers to, it will just change 
what y refers to. So in practice it’s easier to pass states by reference and
update them in place</em></p>

<hr />

<h3 id="model-initialization">Model Initialization</h3>

<p>Initializing the model is then done by creating instances of agent types
following the same signature of the update function, for example for the 
<code class="language-plaintext highlighter-rouge">foo</code> function we decorated above we might do</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="c1"># Initial states of the nodes
</span><span class="n">agent_state_1</span> <span class="o">=</span> <span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="n">agent_state_2</span> <span class="o">=</span> <span class="p">[</span><span class="mi">1</span><span class="p">]</span>

<span class="c1"># Initialize instances of agent nodes
</span><span class="n">foo</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="n">agent_state_2</span><span class="p">,</span> <span class="n">agent_state_1</span><span class="p">,</span> <span class="p">{})</span>
<span class="n">foo</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">agent_state_1</span><span class="p">,</span> <span class="n">agent_state_2</span><span class="p">,</span> <span class="p">{})</span></code></pre></figure>

<p>Where the <code class="language-plaintext highlighter-rouge">t</code> argument is the time of the agents first event. In the background
this would initialize agent nodes with <code class="language-plaintext highlighter-rouge">agent_state_2</code> be a precedent of 
<code class="language-plaintext highlighter-rouge">agent_state_1</code> and vice versa (and both nodes have no downstream dependents).</p>

<h2 id="example">Example</h2>

<p>This is probably better explained with a more in depth example. We can 
implement a function that initializes and runs 
<a href="https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life">Conway’s game of life</a> 
using this API. In full this looks like:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">def</span> <span class="nf">gol</span><span class="p">(</span><span class="n">steps</span><span class="p">,</span> <span class="n">initial_state</span><span class="p">):</span>
    
    <span class="n">scheduler</span> <span class="o">=</span> <span class="n">StepBasedScheduler</span><span class="p">(</span><span class="n">steps</span><span class="p">)</span>
    <span class="n">history</span> <span class="o">=</span> <span class="p">[]</span>
    
    <span class="o">@</span><span class="n">agent</span><span class="p">(</span><span class="n">scheduler</span><span class="o">=</span><span class="n">scheduler</span><span class="p">)</span>
    <span class="k">def</span> <span class="nf">cell</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">antecedents</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="n">descendants</span><span class="p">):</span>
        
        <span class="n">live_neighbours</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">antecedents</span><span class="p">)</span> <span class="o">-</span> <span class="n">antecedents</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">]</span>
        
        <span class="k">if</span> <span class="n">live_neighbours</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">elif</span> <span class="n">live_neighbours</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">]</span>
        <span class="k">elif</span> <span class="n">live_neighbours</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="mi">1</span>
        <span class="k">elif</span> <span class="n">live_neighbours</span> <span class="o">&gt;</span> <span class="mi">3</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="mi">0</span>
        
        <span class="n">state</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_state</span>
        
        <span class="k">return</span> <span class="n">t</span> <span class="o">+</span> <span class="mi">1</span>
    
    <span class="c1"># Initialize the nodes on a grid        
</span>    <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">initial_state</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">initial_state</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
            <span class="n">cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> 
                 <span class="n">initial_state</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">:</span> <span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">,</span> <span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">:</span> <span class="n">j</span><span class="o">+</span><span class="mi">2</span><span class="p">],</span>
                 <span class="n">initial_state</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">j</span><span class="p">:</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">],</span>
                 <span class="p">{})</span>
    
    <span class="c1"># Run the model for the requested number of steps and 
</span>    <span class="c1"># at each step store a copy of the array
</span>    <span class="k">while</span> <span class="ow">not</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
        <span class="n">history</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">initial_state</span><span class="p">.</span><span class="n">copy</span><span class="p">())</span>
        <span class="n">scheduler</span><span class="p">.</span><span class="n">step</span><span class="p">()</span>
        
    <span class="k">return</span> <span class="n">history</span> </code></pre></figure>

<p>This function accepts the initial state of the model (a numpy array) and the
number of steps to run the model for. Breaking it down</p>

<ul>
  <li>This line initializes a scheduler that will run the model for a fixed number 
of steps</li>
</ul>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="n">scheduler</span> <span class="o">=</span> <span class="n">StepBasedScheduler</span><span class="p">(</span><span class="n">steps</span><span class="p">)</span></code></pre></figure>

<ul>
  <li>The agent behaviour (an agent is a cell on the array in this case) is defined 
using the decorator</li>
</ul>

<figure class="highlight"><pre><code class="language-python" data-lang="python">  <span class="o">@</span><span class="n">agent</span><span class="p">(</span><span class="n">scheduler</span><span class="o">=</span><span class="n">scheduler</span><span class="p">)</span>
      <span class="k">def</span> <span class="nf">cell</span><span class="p">(</span><span class="n">t</span><span class="p">,</span> <span class="n">antecedents</span><span class="p">,</span> <span class="n">state</span><span class="p">,</span> <span class="n">descendants</span><span class="p">):</span>
          <span class="n">live_neighbours</span> <span class="o">=</span> <span class="n">np</span><span class="p">.</span><span class="nb">sum</span><span class="p">(</span><span class="n">antecedents</span><span class="p">)</span> <span class="o">-</span> <span class="n">antecedents</span><span class="p">[</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">]</span>
        
        <span class="k">if</span> <span class="n">live_neighbours</span> <span class="o">&lt;</span> <span class="mi">2</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="mi">0</span>
        <span class="k">elif</span> <span class="n">live_neighbours</span> <span class="o">==</span> <span class="mi">2</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="n">state</span><span class="p">[</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">]</span>
        <span class="k">elif</span> <span class="n">live_neighbours</span> <span class="o">==</span> <span class="mi">3</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="mi">1</span>
        <span class="k">elif</span> <span class="n">live_neighbours</span> <span class="o">&gt;</span> <span class="mi">3</span><span class="p">:</span>
            <span class="n">new_state</span> <span class="o">=</span> <span class="mi">0</span>
        
        <span class="n">state</span><span class="p">[</span><span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">,</span><span class="mi">0</span><span class="p">:</span><span class="mi">1</span><span class="p">]</span> <span class="o">=</span> <span class="n">new_state</span>
        
        <span class="k">return</span> <span class="n">t</span> <span class="o">+</span> <span class="mi">1</span></code></pre></figure>

<p>as expected it looks at the previous state of the cells surrounding it 
  (its antecedents) and counts the number of live cells then updates its own
  state accordingly. It returns <code class="language-plaintext highlighter-rouge">t+1</code> which is the next of the model (all the
  cells will use this function so this will all the cells update each 
  step as desired).</p>
<ul>
  <li>We then initialize the cells with the relevant antecedents and state. In this
case the antecedents are slices of the numpy array representing the
neighbourhood surrounding each cell; and the state the relevant cell. It’s
then just case of iterating over the array and assigning each cell to 
an agent. We also set the first step of each agent to fire at 0:</li>
</ul>

<figure class="highlight"><pre><code class="language-python" data-lang="python">  <span class="k">for</span> <span class="n">i</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">initial_state</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
        <span class="k">for</span> <span class="n">j</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">initial_state</span><span class="p">.</span><span class="n">shape</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">-</span><span class="mi">1</span><span class="p">):</span>
            <span class="n">cell</span><span class="p">(</span><span class="mi">0</span><span class="p">,</span> 
                 <span class="n">initial_state</span><span class="p">[</span><span class="n">i</span><span class="o">-</span><span class="mi">1</span><span class="p">:</span> <span class="n">i</span><span class="o">+</span><span class="mi">2</span><span class="p">,</span> <span class="n">j</span><span class="o">-</span><span class="mi">1</span><span class="p">:</span> <span class="n">j</span><span class="o">+</span><span class="mi">2</span><span class="p">],</span>
                 <span class="n">initial_state</span><span class="p">[</span><span class="n">i</span><span class="p">:</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">,</span> <span class="n">j</span><span class="p">:</span><span class="n">j</span><span class="o">+</span><span class="mi">1</span><span class="p">],</span>
                 <span class="p">{})</span></code></pre></figure>

<ul>
  <li>The model can then be run using the scheduler, and additionally we store a 
copy of the state at each step ot track the history of the model</li>
</ul>

<figure class="highlight"><pre><code class="language-python" data-lang="python">  <span class="k">while</span> <span class="ow">not</span> <span class="n">scheduler</span><span class="p">.</span><span class="n">finished</span><span class="p">:</span>
        <span class="n">history</span><span class="p">.</span><span class="n">append</span><span class="p">(</span><span class="n">initial_state</span><span class="p">.</span><span class="n">copy</span><span class="p">())</span>
        <span class="n">scheduler</span><span class="p">.</span><span class="n">step</span><span class="p">()</span></code></pre></figure>

<p>This example used numpy as a data-structure to store the state of the model,
but the API is very general, and as long as it can be passed by reference any
data structure can be used to track the state and pass the relevant parts of it 
to the update function.</p>

<p>The scheduling is also very general, in this case the model was designed to 
update all the agents at fixed steps, but you could just as well use
<code class="language-plaintext highlighter-rouge">datetime</code> as a variable, or have agents act at different intervals.</p>

<p>For more examples see <a href="https://github.com/zombie-einstein/functional_abm/tree/master/examples">here</a> 
in the repo.</p>

<h2 id="conclusion">Conclusion</h2>

<p>For a short project I feel this turned out quite neatly. I think one of the
nice features that came out as a side effect is being able to make use of 
different backends to store the state of the model. An approach using classes
would require storing state as part of the class structure but with this API we
can make more efficient use of data structures to track state. For small models
this results in quite neat code.</p>

<p>This does come with some drawbacks though.</p>

<ul>
  <li>You need to pass everything required as part of the arguments to the update 
function (where as part of an OOP pattern you would be able to look up 
attributes on the class). For example, you might want to pass in a global 
object like a random number generator (the API might benefit from having an 
additional <code class="language-plaintext highlighter-rouge">context</code> argument to make this easier)</li>
  <li>Allowing nodes to alter other nodes can cause some conflicts. This seems
like a necessary feature to have to allow for a broader range of models
(agents should be able to effect their environments right?) but in a model
where agents update on the same step, this can cause issues where an agents
state is updated by itself and another node. Though this can be avoided
with the appropriate design choices.</li>
</ul>

<p>A python package to be able to use this API can be found on it’s 
<a href="https://github.com/zombie-einstein/functional_abm">github repo</a> along with 
usage examples. Please try it out, it’d be interesting to see how this API
holds up across more model implementations.</p>]]></content><author><name></name></author><category term="abm" /><category term="agent-based-modelling" /><category term="functional" /><category term="python" /><summary type="html"><![CDATA[The code for this project can be found on it’s github repo]]></summary></entry><entry><title type="html">Continuous Probabilistic Cellular Automata</title><link href="/2020/06/27/probabilistic_ca.html" rel="alternate" type="text/html" title="Continuous Probabilistic Cellular Automata" /><published>2020-06-27T00:00:00+00:00</published><updated>2020-06-27T00:00:00+00:00</updated><id>/2020/06/27/probabilistic_ca</id><content type="html" xml:base="/2020/06/27/probabilistic_ca.html"><![CDATA[<p><em>The code for this project can be found on its 
<a href="https://github.com/zombie-einstein/probabilistic_ca">github repo</a></em></p>

<h2 id="introduction">Introduction</h2>

<p>I have to admit I find cellular automaton (CA) endlessly fascinating. They have 
never seemed to have found that killer real-world applications or ultimate deep 
insight; but I find the questions they raise about emergent behaviour and self 
organization incredibly engaging, that always keep me coming back 
to try out some new approach to exploring them.</p>

<p>In this post I’ll be looking at an extension to CA to allow for cells 
to be in a mixed state and probabilistic update rules to be applied.</p>

<h3 id="1d-cellular-automata-briefly">1D Cellular Automata (Briefly)</h3>

<p>To keep this post brief I’ll not go into too much depth of the theory on CA, 
if you’ve not encountered them before there’s a ton of stuff out there and as 
always <a href="https://en.wikipedia.org/wiki/Cellular_automaton">wikipedia</a> is a good 
place to start.</p>

<p>This project focused on 1-dimensional CA. This model can be imagined as a 1d 
array of cells, with each cell in a (usually discrete) state
$s\in S$. At each update of the model, a cells state is updated based
on its own state and local neighbourhood of cells. For example if each cell 
only considers it’s nearest neighbours then an update rule of a CA is
the mapping</p>

\[(s_{i-1}^{t}, s_{i}^{t}, s_{i+1}^{t}) \rightarrow s_{i}^{t+1}\]

<p>where $i$ indexes the cells position in the array and $t$ the step. The array 
 is usually wrapped to form a closed loop (i.e. the end-cells are neighbours), 
 and the evolution of the model visualized as a 2d space-time array where each 
 row is one step of the model.</p>

<figure class="image">
  <img src="https://upload.wikimedia.org/wikipedia/commons/9/9d/CA_rule30s.png" alt="Space time diagram showing the evolution of 'rule 30' In this case 
each cell can be in one of two states (usually labelled dead/alive or 0/1). 
Time advances from top to bottom, the initial state of the model here being a 
single live cell. The evolution contains both 
structured and disordered chaotic regions" />
  <figcaption>Space time diagram showing the evolution of 'rule 30' In this case 
each cell can be in one of two states (usually labelled dead/alive or 0/1). 
Time advances from top to bottom, the initial state of the model here being a 
single live cell. The evolution contains both 
structured and disordered chaotic regions</figcaption>
</figure>

<p>CA can have multiple states, and rules can be defined for various 
neighbourhoods around a cell, but in this case we will concentrate on 
2-state and size 3 neighbourhood rules.</p>

<p>In many cases it’s convenient to index rules using the Wolfram system where the
index is derived from the mapping represented in base $\vert S\vert$. For 
example if $S=\{0,1\}$ then full specifying the update rule requires a 
mapping for the $\vert S\vert^{3}=2^3$ possible triple states resulting in 
$2^8=256$ possible rules (See 
<a href="https://en.wikipedia.org/wiki/Wolfram_code">here</a> for more details).</p>

<p>For example rule 110 is defined by the mapping</p>

<table>
  <thead>
    <tr>
      <th>$(s_{i-1}^{t}, s_{i}^{t}, s_{i+1}^{t})$</th>
      <th>000</th>
      <th>001</th>
      <th>010</th>
      <th>011</th>
      <th>100</th>
      <th>101</th>
      <th>110</th>
      <th>111</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>$s_{i}^{t}$</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
      <td>1</td>
      <td>1</td>
      <td>0</td>
    </tr>
  </tbody>
</table>

<p>A CA is then parameterized by an update rule, an initial state (common choice
are a single live cell, or a random initial state) and topology of the array
(i.e. a closed loop of fixed boundary conditions).</p>

<h2 id="motivation">Motivation</h2>

<p>This project was motivated by a couple of broad questions that have
motivated a great deal of research on 1d CA:</p>

<ul>
  <li>
    <p>Is there some way to classify the long term behaviour of CA? If one looks at
all the possible rules configurations (for a chosen CA configuration) the 
dynamics of rules seem to roughly fall into 4 categories</p>

    <ul>
      <li>Evolution to a static homogeneous state</li>
      <li>Periodic behaviour where cells oscillate between states 
at each step or stable inhomogeneous structures</li>
      <li>Evolution to chaotic or seemingly noisy patterns</li>
      <li>Formation of localised persistent structures that can interact</li>
    </ul>

    <p>though some rules can overlap some of these behaviors, and can obviously also 
depend on the initial state chosen. 
How can these classifications be made rigorous (or is there an underlying
statistics or feature that classifies rules) and what forms the 
boundary between these behaviours?</p>

    <p>Of particular interests are rules where stable structures form against a
chaotic background, and such states can propagate and interact. For exmample
it has been shown that rule 110 is turing complete.</p>
  </li>
  <li>
    <p>In the case where structures do form, how do they form and persist against
the background of chaotic noise? Is there some necessary condition for their
formation and how do they propagate information and interact?</p>
  </li>
</ul>

<blockquote>
  <p><strong>Note:</strong> These are a <strong>very</strong> brief outline of some of the interesting 
topics on the subject. There’s really a lot of interesting work covering 
these topic and more!!</p>
</blockquote>

<p>Interesting work has been done on how changes in the initial state change the
long term behaviour of the model (an analogue of the Lyapunov exponent used 
to study chaotic systems) and how information propagates through the array. 
It would seem expedient then to be able to express the initial state as a 
distribution across initial states or as something like a random walk that 
evolves over time as the update rule is applied to it.</p>

<p>This motivates being able to model a CA where the state of a 
cell can be a probability distribution on the state space (or maybe this could
be thought of a superposition of states as in QM, though without any phase
information). As the update rule is applied to a neighbourhood
of cells, this also requires that we are able to model the joint or conditional
probability of neighbouring cells.</p>

<h2 id="theory">Theory</h2>
<blockquote>
  <p><strong>Note:</strong> <em>In the literature “Probabilistic CA” seems to refer to a model 
where the update rule is probabilistic but each cell still always has a 
fixed discrete state. So for now the name “Continuous 
Probabilistic CA” seems like a good name to distinguish from this</em></p>
</blockquote>

<h3 id="probabilistic-states">Probabilistic States</h3>
<p>We’ll consider a model where each cell has a probability of being in a state
$s$ denoted as</p>

\[P(s_{i}^{t})=P(s_{i}^{t}=s)\quad\text{where}\quad s\in S\]

<p>The update rule applies to a cell, and its left and right neighbours. The 
probability of the cell being in a state at the next step is then given by</p>

\[P\left(s_{i}^{t+1}\right)=\sum_{f=s_{i}^{t+1}}P\left(s_{i-1}^{t},s_{i}^{t},s_{i+1}^{t}\right)\]

<p>where the r.h.s is the joint probability of a state of the preceding triple 
and the summation is performed over the triples will result in the 
state $s_{i}^{t+1}$, i.e. when</p>

\[f\left(s_{i-1}^{t},s_{i}^{t},s_{i+1}^{t}\right)=s_{i}^{t+1}\]

<p>for all combinations of the preceding states and $f(\dots)$ is the CA 
update function.</p>

<blockquote>
  <p><strong>Note:</strong> <em>The joint probability is important here as for certain rules
neighbouring cells cannot be in certain configurations and as such should
not be treated independently</em></p>
</blockquote>

<p>This approach is ok for one step of the model but to repeatedly perform 
this update for an arbitrary number of steps we need the joint 
probability of two neighbouring cells at each site, and its neighbour to the 
right for each time step</p>

\[P\left(s_{i}^{t}, s_{i+1}^{t}\right)\]

<p>extending the above approach for the update of a single cell gives</p>

\[P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\right)=\sum_{f=s_{i}^{t+1}, f=s_{i+1}^{t+1}}P\left(s_{i-1}^{t},s_{i}^{t},s_{i+1}^{t},s_{i+2}^{t}\right)\]

<p>the summation now over the overlapping triple cell states that will create the 
joint state. Then using the chain rule of probabilities we can decompose this
to</p>

\[\begin{align}
P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\right) &amp;=\sum_{f} P\left(s_{i}^{t},s_{i+1}^{t}\right)P\left(s_{i-1}^{t}\vert s_{i}^{t}\right)P\left(s_{i+2}^{t}\vert s_{i+1}^{t}\right)\\
&amp;=\sum_{f} P\left(s_{i-1}^{t},s_{i}^{t}\right)P\left(s_{i+1}^{t},s_{i+2}^{t}\right)h\left(s_{i}^{t},s_{i+1}^{t}\right)
\end{align}\]

<p>where</p>

\[h\left(s_{i}^{t},s_{i+1}^{t}\right)=\frac{P\left(s_{i}^{t},s_{i+1}^{t}\right)}{P\left(s_{i}^{t}\right)P\left(s_{i+1}^{t}\right)}\]

<p>This form is then useful as we need only store the joint probabilities
for each cell (and it’s neighbour) and can obtain $h$ from this and
the marginal probabilities.</p>

<p>Finally for the starting state at $t=0$ we assume that the initial 
probabilities are independent such that the initial array of joint 
probabilities can be found using</p>

\[P\left(s_{i}^{0},s_{i+1}^{0}\right)=P\left(s_{i}^{0}\right)P\left(s_{i+1}^{0}\right)\]

<h3 id="probabilistic-update-rules">Probabilistic Update Rules</h3>

<p>In the above model, any uncertainty in the model can only arise from 
uncertainty in the initial state (if all the cells are in only one state 
initially i.e. $P(s)\in\{0,1\}$ it behaves like a regular CA), as the update 
rule still maps discrete states to discrete states.</p>

<p>We can extend the model to include probabilistic updates, represented 
by the conditional distribution</p>

\[P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:\vert\: q_{i}^{t}\right)\]

<p>where $q_{i}^{t}$ is the preceding quadruple of states that inform the
updated joint probability</p>

\[q_{i}^{t} = \left(s_{i-1}^{t},s_{i}^{t},s_{i+1}^{t},s_{i+2}^{t}\right)\]

<p>this actually somewhat simplifies the form of the summation to give</p>

\[\begin{align}
P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\right) &amp;= \sum_{q_{i}^{t}}P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:|\:q_{i}^{t}\right)P\left(q_{i}^{t}\right)\\
&amp;= \sum_{q_{i}^{t}}P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:|\:q_{i}^{t}\right)P\left(s_{i-1}^{t},s_{i}^{t}\right)P\left(s_{i+1}^{t},s_{i+2}^{t}\right)h\left(s_{i}^{t},s_{i+1}^{t}\right)
\end{align}\]

<p>where the summation is over all the possible patterns of the preceding 
quadruples cells.</p>

<p>In the case that</p>

\[P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:\mid \: q_{i}^{t}\right) \in \{0,1\}\]

<p>then a deterministic CA rule is recovered.</p>

<h2 id="implementation">Implementation</h2>

<p>This was straightforward enough to implement in numpy. As with most cellular 
automata models like this (where the update is done in parallel for all cells) 
the real trick is shifting the state array left and right to be able to 
vectorize the update step.</p>

<p>The model is specified by 3 parameters (as in a regular CA):</p>

<ul>
  <li>
    <p>The initial state of the cells, in this case it has the dimensions
$\text{steps}\times\vert S\vert$ specifying the initial probability 
distribution for each cell</p>
  </li>
  <li>
    <p>The update rule, provided as a 2d array mapping the permutation of 
triples to the probability of the update state. This represents:</p>

\[P\left(s_{i}^{t+1}\:\vert\: s_{i-1}^{t}, s_{i}^{t}, s_{i+1}^{t}\right)\]
  </li>
  <li>
    <p>The number of steps to run the model for</p>
  </li>
</ul>

<p>In brief the models then follows the following steps:</p>

<ul>
  <li>
    <p>Use the rule array to generate an array representing the update of 
joint probabilities conditioned on the preceding quadruples of cells</p>

\[P\left(s_{i}^{t+1}, s_{i+1}^{t+1}\:\vert\: q_{i}^{t}\right)\]
  </li>
  <li>
    <p>Initialize an empty array for joint probabilities with the shape</p>

\[\text{steps}\times \text{width}\times \vert S \vert\times\vert S \vert\]

    <p>(i.e. a joint probability distribution for each cell and for each step of
 the model)</p>
  </li>
  <li>Set the initial joint probability row from the initial state, 
using the independence of the initial states</li>
  <li>At each step calculate the marginal probabilities required to calculate 
$h\left(s_{i}^{t+1},s_{i+1}^{t+1}\right)$ along with the left and right
shifted rows for $P\left(s_{i-1}^{t},s_{i}^{t}\right)$ and
$P\left(s_{i+1}^{t},s_{i+2}^{t}\right)$</li>
  <li>For each joint probability entry sum over all the contributions from the 
combinations of the preceding quadruple of cells</li>
</ul>

<h3 id="complexity-and-numerical-underflow">Complexity and Numerical Underflow</h3>

<p>Computationally there are a couple of potential drawbacks</p>

<ul>
  <li><strong>Computational complexity:</strong> Increasing the number of states increases both
the storage space required and the complexity of the update calculation. 
The number of states means scaling the array of joint probabilities like 
$\vert S\vert^2$ for each cell. When then need to then perform the update
for each of these new entities as well as including contributions for 
the additional permutation of states which scale as $\vert S\vert^4$.</li>
  <li><strong>Numerical Underflow:</strong> As with many models where probabilities are 
repeatedly multiplied numerical underflow can occur. A common approach is
to work with log probabilities and use techniques like the 
<a href="https://www.xarg.org/2016/06/the-log-sum-exp-trick-in-machine-learning/">log-sum-exp trick</a>.
In this case there a couple of things that make this tricky:
    <ul>
      <li>The lack of a well-defined 0 probability in log space prohibits using 
binary states as inputs to the model</li>
      <li>Calculating the marginal probabilities required in the update step still
means moving between log and normal probabilities which does not aid in
reducing numerical under/overflow</li>
    </ul>
  </li>
</ul>

<h2 id="analysisplotting">Analysis/Plotting</h2>

<p>Plotting the result of the model as a time-space diagram like a regular CA
requires aggregating the joint probability distribution of each cell in some 
manner, but I also looked to choose statistics that might reveal underlying
dynamics of the probabilistic CA:</p>

<ul>
  <li>
    <p>The probability distribution for each cell are easily recovered as the 
marginals of the joint distribution</p>

\[P\left(s_{i}^{t}\right)=\sum_{s_{i+1}^{t}}P\left(s_{i}^{t}, s_{i+1}^{t}\right)\]
  </li>
  <li>
    <p>The mutual information of the joint probabilities also seems like it should
be a useful, giving something like mutual dependence between neighbouring
cells. Here defined for a single cell as</p>

\[I_{i}^{t} = \sum_{s_{i}^{t}, s_{i+1}^{t}}P\left(s_{i}^{t}, s_{i+1}^{t}\right)
\text{log}\left(\frac{P\left(s_{i}^{t}, s_{i+1}^{t}\right)}{P\left(s_{i}^{t}\right)P\left(s_{i+1}^{t}\right)}\right)\]
  </li>
</ul>

<p>An additional issue is that in many cases the relative difference (of these
statistics) between cells decreases over time, meaning plots can fail to 
adequately show features contained in resulting arrays. Applying min-max 
scaling across rows of the array is one approach used to tackle this issue, 
though care should be taken in how this is interpreted, given that small 
relative differences could also be a result of numerical precision.</p>

<h2 id="results">Results</h2>

<p>Given the large number of potential parametrizations of the model (mixing
probabilistic update rules, and initial states) I looked to focus on two simple
cases:</p>

<ul>
  <li>
    <p>Standard update rules (i.e. deterministic) with a randomly chosen discrete 
initial state containing a single cell in a mixed state. Used to investigate 
how the uncertainty from a single cell propagates through the state over 
time.</p>
  </li>
  <li>
    <p>Update rules with the same perturbation applied to each mapping, and a 
randomly chosen discrete initial state. For example if the undeterred rule
maps $(0,0,0) \rightarrow 0$ then the perturbed probabilities are
$P(0\vert 0,0,0)=1-\epsilon$ and $P(1\vert 0,0,0)=\epsilon$ (applied to all
the permutations). This could be thought of as a probability of error when
applying the update rule, we can then consider how robust rules
and patterns are to these errors.</p>
  </li>
</ul>

<p>Below are some examples of space-time diagrams from these cases (though far
exhaustive given the possible combinations of parameters). In each case
the left image is the equivalent regular CA evolution (i.e. no perturbation in
the update rule or initial state). The right hand side has been coloured 
to emphasise values between 0-1.</p>

<h3 id="perturbed-initial-state">Perturbed Initial State</h3>

<figure class="image">
  <img src="/assets/prob_ca/rule_009_uncertain_cell.png" alt="Time evolution of Rule 9 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertainty from the single cell 
propagates but interacts with the background state." />
  <figcaption>Time evolution of Rule 9 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertainty from the single cell 
propagates but interacts with the background state.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_013_uncertain_cell.png" alt="Time evolution of Rule 13 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertain region is bounded
around the initial uncertain cell after several steps." />
  <figcaption>Time evolution of Rule 13 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertain region is bounded
around the initial uncertain cell after several steps.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_030_uncertain_cell.png" alt="Time evolution of Rule 30 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertain region propagates
asymmetrically, with a boundary formed on the right by the cell pattern." />
  <figcaption>Time evolution of Rule 30 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertain region propagates
asymmetrically, with a boundary formed on the right by the cell pattern.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_054_uncertain_cell.png" alt="Time evolution of Rule 54 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertainty region seems to
interact with the stable regions of the deterministic evolution." />
  <figcaption>Time evolution of Rule 54 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertainty region seems to
interact with the stable regions of the deterministic evolution.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_060_uncertain_cell.png" alt="Time evolution of Rule 60 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertainty propagates in an
asymmetric but consistent manner. Inside the uncertain region are 
cells that still have a fixed state." />
  <figcaption>Time evolution of Rule 60 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertainty propagates in an
asymmetric but consistent manner. Inside the uncertain region are 
cells that still have a fixed state.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_073_uncertain_cell.png" alt="Time evolution of Rule 73 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertain region propagates but
then quickly settles back to a fixed state, though destroying any initial
information from the initial state. The propagation is bounded on either
side by fixed patterns of the deterministic CA." />
  <figcaption>Time evolution of Rule 73 from a random initial state with a 
single cell in a mixed state $p(0)=0.5$. The uncertain region propagates but
then quickly settles back to a fixed state, though destroying any initial
information from the initial state. The propagation is bounded on either
side by fixed patterns of the deterministic CA.</figcaption>
</figure>

<h3 id="perturbed-update-rule">Perturbed Update Rule</h3>

<figure class="image">
  <img src="/assets/prob_ca/rule_005_prob.png" alt="Time evolution of Rule 5 from a random initial state with a 
perturbation of 0.0001 applied to the update mapping. Only certain patterns
persist pass the uncertainty introduced by the perturbed rule, otherwise 
initial information is replaced with an oscillating pattern. 
" />
  <figcaption>Time evolution of Rule 5 from a random initial state with a 
perturbation of 0.0001 applied to the update mapping. Only certain patterns
persist pass the uncertainty introduced by the perturbed rule, otherwise 
initial information is replaced with an oscillating pattern. 
</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_043_prob.png" alt="Time evolution of Rule 43 from a random initial state with a 
perturbation of 0.0001 applied to the update mapping. The uncertainty caused
by the rule perturbation appears to oscillate over time with a pattern
dependent on the initial distribution." />
  <figcaption>Time evolution of Rule 43 from a random initial state with a 
perturbation of 0.0001 applied to the update mapping. The uncertainty caused
by the rule perturbation appears to oscillate over time with a pattern
dependent on the initial distribution.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_043_mut_info.png" alt="The corresponding mutual information of the evolution of Rule 43 
showing the dependencies between neighbouring cells as the model progresses" />
  <figcaption>The corresponding mutual information of the evolution of Rule 43 
showing the dependencies between neighbouring cells as the model progresses</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_147_prob.png" alt="Time evolution of Rule 147 from a random initial state with a 
perturbation of 0.0001 applied to the update mapping. The stable patterns
in the regular CA seem to be unstable when the perturbation is applied, with
this instability propagating into the chaotic regions." />
  <figcaption>Time evolution of Rule 147 from a random initial state with a 
perturbation of 0.0001 applied to the update mapping. The stable patterns
in the regular CA seem to be unstable when the perturbation is applied, with
this instability propagating into the chaotic regions.</figcaption>
</figure>

<figure class="image">
  <img src="/assets/prob_ca/rule_147_mut_info.png" alt="The corresponding mutual information of the evolution of Rule 147 
showing how an increase in the mutual information corresponds to the 
propagation of uncertain region in the model." />
  <figcaption>The corresponding mutual information of the evolution of Rule 147 
showing how an increase in the mutual information corresponds to the 
propagation of uncertain region in the model.</figcaption>
</figure>

<h2 id="conclusion">Conclusion</h2>

<p>In terms of a model, I feel this turned out quite well. Extending a regular CA
into one that supports mixed/probabilistic cell states turned out to be quite
a neat algorithm, and the resulting implementation relatively speedy. 
It’d be nice to have a robust way of working in log-probability space, 
but the current model seems relatively robust in most cases. Currently, the 
model-runner only supports binary states, but the extension to larger state 
spaces should be relatively straight forward.</p>

<p>Unfortunately the results are mostly qualitative, there seem to be some 
nice features revealed that point towards interesting dynamics and information 
propagation between cells. One could maybe make some statements about 
robustness of patterns, or the propagation of uncertainty relate to the
classes of behaviours (described earlier in this post) but this is in need of
further analysis.</p>

<p>One thing that might be nice to explore is the transition between the 
behaviour of update rules. Being able to apply probabilistic update rules means 
one could explore the continuous space of update rules inside the 
$\vert S\vert$-dimensional interval (of which the regular discrete CA rules
form the corners).</p>

<p>At a minimum though, some of the images would make for cool album covers!</p>

<p>All the code needed to run the model and produce the plots included in this
post is available <a href="https://github.com/zombie-einstein/probabilistic_ca">here</a>
so please go ahead and try it out, it’d be interesting to see what other people
come up with!</p>]]></content><author><name></name></author><category term="cellular-automata" /><category term="python" /><summary type="html"><![CDATA[The code for this project can be found on its github repo]]></summary></entry></feed>