<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title><![CDATA[SgAI Notebook]]></title>
    <link>https://aria-sgai-notebook.inkandswitch.com/</link>
    <description><![CDATA[Notes, essays, and working documents from the ARIA Safeguarded AI programme.]]></description>
    <lastBuildDate>Mon, 14 Sep 2026 09:22:12 GMT</lastBuildDate>
    <atom:link href="https://aria-sgai-notebook.inkandswitch.com/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title><![CDATA[alifib, the language of higher-dimensional diagrams]]></title>
      <link>https://aria-sgai-notebook.inkandswitch.com/alifib/</link>
      <guid isPermaLink="true">https://aria-sgai-notebook.inkandswitch.com/alifib/</guid>
      <pubDate>Wed, 16 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[alifib is an experimental programming language and interactive proof assistant in which programs <em>are</em> diagrams — not illustrations of programs, but the syntax itself, the internal representation, and the thing that is checked and run.]]></description>
      <content:encoded><![CDATA[<p>As part of <a href="https://aria.org.uk/opportunity-spaces/mathematics-for-safe-ai/safeguarded-ai/">ARIA’s Safeguarded AI programme</a>, I have been leading the development of
<strong>alifib</strong>, an experimental programming language and interactive proof
assistant founded on higher-dimensional rewriting. In alifib, programs are
diagrams: not illustrations of programs, but the syntax itself, the
internal representation, the thing that is checked and run. This notebook post
is an attempt to say what that means and what it is for. The proof assistant runs
in a browser at <a href="http://compose.ee/alifib">http://compose.ee/alifib</a>, with nothing to install, and the
reader may want to keep it open beside the text.</p>
<figure class="wide">
<a href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/01-interface-overview.png"><img src="https://aria-sgai-notebook.inkandswitch.com/alifib/img/01-interface-overview.png" alt="The alifib web interface: three panes side by side. On the left, a code
editor with an .ali file open, syntax-highlighted. In the middle, a REPL pane
with a session-setup form (a type selector, source and target fields, a Start
button). On the right, a visualisation pane rendering a string diagram
(coloured nodes joined by wires) above a list of available
rewrites."></a>
<figcaption>
<p>The web interface, with an editor, an interactive session, and a live string-diagram visualisation side by side. <a class="expand" href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/01-interface-overview.png"><span class="link-label">View Full Size </span><span class="link-tail"><span class="link-label">Image</span><svg class="icon icon-arrows-expand" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-corner-tl"><path d="M0.5 3.5v-3h3"/><path d="m0.5 0.5 3 3"/></g><g class="icon-corner-tr"><path d="M9.5 3.5v-3h-3"/><path d="m9.5 0.5-3 3"/></g><g class="icon-corner-bl"><path d="M0.5 6.5v3h3"/><path d="m0.5 9.5 3-3"/></g><g class="icon-corner-br"><path d="M9.5 6.5v3h-3"/><path d="m9.5 9.5-3-3"/></g></svg></span></a></p>
</figcaption>
</figure>
<h2 id="from-terms-to-diagrams">From terms to diagrams</h2>
<p>Nearly every programming language represents a program as a <em>term</em>, a
tree-like structure. The tree records what the program <em>is</em>; how it <em>runs</em>
is settled elsewhere—by reduction, by evaluation, by whatever mechanism
an interpreter or compiler chooses—outside the syntax, and taken on faith.</p>
<p>Term syntax also makes a quiet assumption: that data may be copied and
discarded at will. A variable may appear twice in a term, or never. Much of
computation is not like that—a resource consumed cannot be reused, a message
sent cannot be unsent—and category theory has long had a syntax for such
situations: <em>string diagrams</em> for monoidal categories, that is, nodes joined by
wires, each wire carrying a value, exactly once, from one operation into the
next. Terms are the special case in which every wire comes with copying and
discarding operations.</p>
<p>The second step is to go up in dimension. A string diagram is already a
two-dimensional object, having besides its nodes and wires the regions of the
plane that the wires bound. Once regions, wires, and nodes are counted as
cells of dimension 0, 1, and 2, there is no reason to stop counting. A
<em>pasting diagram</em> has cells of every dimension, each bounded by an input and
an output face: a point, an arrow between points, a filled surface between two
paths of arrows, a solid between two such surfaces. Each level, moreover,
reads as a rewrite of the level below—an arrow carries its source to its
target, a 2-cell carries one path of arrows into another—so that every storey of the
tower, however high one climbs, is the same kind of object. (The counting is
by role rather than by drawn shape: the string diagram is the Poincaré dual of
a two-dimensional pasting diagram, with regions standing for points, and nodes
for 2-cells.)</p>
<p>This is the entire ontology of the language: one kind of thing, the diagram,
and one non-trivial operation upon it, <em>subdiagram search</em>, which finds a
diagram sitting inside another. The whole rests on a single observation, the
founding principle of higher-dimensional rewriting:</p>
<blockquote>
<p>a computation on <em>n</em>-dimensional data is itself (<em>n</em>+1)-dimensional data.</p>
</blockquote>
<p>In every other model of computation that I know of, a program and a run of that
program are objects of different kinds: usually, a piece of syntax on one side, a
sequence of machine configurations on the other. Here they are the same kind
of object, one dimension apart, and the run is data again, ready in its turn
to be computed on.</p>
<h2 id="an-alphabet-of-two-letters">An alphabet of two letters</h2>
<p>A type in alifib is a diagrammatic signature, a collection of building blocks
of increasing dimension from which diagrams can be assembled. Let us look at
a simple 2-dimensional example, essentially an encoding of a string rewrite
system. Here is a type with a point <code>pt</code>, and two arrows <code>a</code>, <code>b</code> from the
point to itself, so that words, that is, strings of <code>a</code>s and <code>b</code>s, are
one-dimensional diagrams. A rule of grammar for these words is nothing but a
two-dimensional generator:</p>
<pre><code class="language-ali"><div class="code-line"><span class="token atrule">@Type</span></div><div class="code-line"><span class="token class-name">Letters</span> <span class="token operator">&lt;&lt;=</span> <span class="token punctuation">{</span></div><div class="code-line">    pt<span class="token punctuation">,</span></div><div class="code-line">    a<span class="token punctuation">:</span> pt <span class="token operator">-></span> pt<span class="token punctuation">,</span></div><div class="code-line">    b<span class="token punctuation">:</span> pt <span class="token operator">-></span> pt<span class="token punctuation">,</span></div><div class="code-line"></div><div class="code-line">    <span class="token comment">(* a rewrite rule is just a 2-dimensional generator *)</span></div><div class="code-line">    swap<span class="token punctuation">:</span> a b <span class="token operator">-></span> b a</div><div class="code-line"><span class="token punctuation">}</span></div></code></pre><p><code>swap</code> rewrites the pattern <code>a b</code>, wherever it occurs inside a word, into
<code>b a</code>. Note what is missing: there is no separate register in which
“evaluation rules” are declared, no computation mechanism external to the
type. The rule and the data it acts on are generators of one type, a dimension
apart. A point, two letters, and one rule are very little, but they are
enough to compute with.</p>
<figure>
<a href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/02-letters-evaluate.png"><img src="https://aria-sgai-notebook.inkandswitch.com/alifib/img/02-letters-evaluate.png" alt="The editor pane with the Letters type as above; the Evaluate button has just
been pressed, and the output area below lists the elaborated type with its
generators pt, a, b, swap, each annotated with its dimension and
boundaries."></a>
<figcaption>
<p>Evaluating the file. The interpreter checks that the boundary of every
generator is a well-formed diagram, and reports what it has understood. <a class="expand" href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/02-letters-evaluate.png"><span class="link-label">View Full Size </span><span class="link-tail"><span class="link-label">Image</span><svg class="icon icon-arrows-expand" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-corner-tl"><path d="M0.5 3.5v-3h3"/><path d="m0.5 0.5 3 3"/></g><g class="icon-corner-tr"><path d="M9.5 3.5v-3h-3"/><path d="m9.5 0.5-3 3"/></g><g class="icon-corner-bl"><path d="M0.5 6.5v3h3"/><path d="m0.5 9.5 3-3"/></g><g class="icon-corner-br"><path d="M9.5 6.5v3h-3"/><path d="m9.5 9.5-3-3"/></g></svg></span></a></p>
</figcaption>
</figure>
<h2 id="watching-it-run">Watching it run</h2>
<p>Begin an interactive session on the word <code>a b a b</code>. The visualisation pane
draws it as a string diagram, while the engine searches out every position at
which <code>swap</code> matches, and lists them. This is subdiagram search at work;
everything else in the system is bookkeeping around it, and everything one is
ever asked to trust reduces to it.</p>
<figure>
<a href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/03-session-rewrites.png"><img src="https://aria-sgai-notebook.inkandswitch.com/alifib/img/03-session-rewrites.png" alt="A rewrite session in progress. The visualisation pane shows a horizontal
string diagram: four wires in sequence labelled a, b, a, b. Below it, a
rewrite list with two entries, each naming the rule swap and the position at
which it matches. The second occurrence of &quot;a b&quot; in the diagram is highlighted
in colour, because the pointer is hovering over the corresponding entry in the
list."></a>
<figcaption>
<p>Hovering over an entry highlights the subdiagram that the rewrite would consume, and clicking applies it. <a class="expand" href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/03-session-rewrites.png"><span class="link-label">View Full Size </span><span class="link-tail"><span class="link-label">Image</span><svg class="icon icon-arrows-expand" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-corner-tl"><path d="M0.5 3.5v-3h3"/><path d="m0.5 0.5 3 3"/></g><g class="icon-corner-tr"><path d="M9.5 3.5v-3h-3"/><path d="m9.5 0.5-3 3"/></g><g class="icon-corner-bl"><path d="M0.5 6.5v3h3"/><path d="m0.5 9.5 3-3"/></g><g class="icon-corner-br"><path d="M9.5 6.5v3h-3"/><path d="m9.5 9.5-3-3"/></g></svg></span></a></p>
</figcaption>
</figure>
<p>Each click applies one rewrite. The session, however, is not mutating a
string, as a text editor would: it is building a two-dimensional diagram, one
<code>swap</code> cell pasted on at each step, and the word on screen is only the
frontier of the construction. Stop at any point and switch the visualisation
from the step view to the proof view: everything that has happened is there,
as a single diagram.</p>
<figure>
<a href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/04-proof-trace.png"><img src="https://aria-sgai-notebook.inkandswitch.com/alifib/img/04-proof-trace.png" alt="The proof view after three steps: a two-dimensional string diagram read
bottom-to-top. Along the bottom boundary, the wires a, b, a, b; along the top,
b, b, a, a; in between, three beads, each an application of swap, joined by
wires that trace how each letter moved through the computation. The REPL pane
alongside records the session history: step 1, step 2, step 3, each with the
rule applied."></a>
<figcaption>
<p>The trace is a 2-diagram whose input boundary is where the computation began
and whose output boundary is where it ended, with everything in between
retained. <code>store</code> gives it a name, after which it may be pasted into other
diagrams like any other term. <a class="expand" href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/04-proof-trace.png"><span class="link-label">View Full Size </span><span class="link-tail"><span class="link-label">Image</span><svg class="icon icon-arrows-expand" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-corner-tl"><path d="M0.5 3.5v-3h3"/><path d="m0.5 0.5 3 3"/></g><g class="icon-corner-tr"><path d="M9.5 3.5v-3h-3"/><path d="m9.5 0.5-3 3"/></g><g class="icon-corner-bl"><path d="M0.5 6.5v3h3"/><path d="m0.5 9.5 3-3"/></g><g class="icon-corner-br"><path d="M9.5 6.5v3h-3"/><path d="m9.5 9.5-3-3"/></g></svg></span></a></p>
</figcaption>
</figure>
<p>It is worth pausing on that diagram. It is not a log of the computation; it
<em>is</em> the computation, a first-class term of the language, which can be
inspected, replayed, pasted into larger diagrams, and acted upon by
higher-dimensional computations in its turn. If one only wants the answer,
one asks for its output boundary.</p>
<h2 id="computational-transparency">Computational transparency</h2>
<p>Recall the Curry–Howard correspondence, according to which programs are
proofs: a well-typed term certifies, by existing, that the program it denotes
is correctly formed. The correspondence, however, speaks only of the program.
A term also carries an equational theory—a notion of when two terms are
“the same”—and this theory, the very mechanism by which programs run, is
meta-theoretical: assumed to exist, assumed to be computable, and in practice
entrusted to the compiler. One may certify the program inside the language; to
certify its execution, one must trust something outside it.</p>
<p>With diagrams, this boundary disappears. The rules by which a type computes
are generators of the type itself, written down beside the ordinary ones,
and a run of a program is a higher term—exactly the diagram built in the
session above.</p>
<blockquote>
<p>Well-typed terms are verified programs.
Well-typed <em>higher</em> terms are verified executions.</p>
</blockquote>
<p>I call this principle <strong>computational transparency</strong>, and it is alifib’s
reason to exist. A verified program is not yet a verified run; between the two
stands the whole tower of compilers and machines, and it is there that
guarantees tend to evaporate. Our concern in this programme is with what a
system <em>did</em>, rather than with what it was meant to do, and transparency is
that concern made into syntax: the object one examines is the run itself, not
a report of the run.</p>
<p>There is a consequence worth stating on its own. Since the rules by which a
type computes are its own generators, each type is its own computational
universe: its generators create, at the same time, the space in which
computations happen, the data on which they act, and the computations
themselves; nothing arrives from outside. A type expressive enough to house
a Turing machine is still, in this sense, a sandbox, accounted for cell
by cell by a kernel small enough to be read.</p>
<p>None of this is confined to two-letter alphabets. <code>examples/TRS.ali</code> sets up
term rewriting systems in general, their terms two-dimensional diagrams and
their runs three-dimensional ones, and <code>examples/BinaryNat.ali</code> instantiates
it with binary arithmetic, so that</p>
<pre><code class="language-ali"><div class="code-line"><span class="token atrule">@BinaryNat</span></div><div class="code-line"><span class="token keyword">let</span> d <span class="token operator">=</span> <span class="token punctuation">(</span><span class="token keyword">run</span> <span class="token keyword">auto</span> <span class="token keyword">on</span> <span class="token class-name">Eval</span><span class="token punctuation">.</span><span class="token punctuation">(</span><span class="token keyword">run</span> <span class="token keyword">auto</span> <span class="token keyword">on</span> parse <span class="token string">"57 * (12 + 30)"</span> <span class="token keyword">end</span><span class="token punctuation">)</span><span class="token punctuation">)</span></div></code></pre><p>binds <code>d</code> not to the number 2394 but to a witness of the entire computation,
whose output boundary is 2394. Since parsing, evaluation, and execution are
all the same kind of diagram, parsing itself turns out to be functorial—the
parse map sends parse traces of arithmetic expressions to the diagrams they
denote—a remark that I will not develop here, beyond noting that those two
files carry it out.</p>
<h2 id="eckmann-hilton-by-hand">Eckmann–Hilton, by hand</h2>
<p>Because equations are <em>directed</em> cells, equational reasoning in a
higher-categorical structure is the same activity as running a machine. The
classic Eckmann–Hilton argument says that two endomorphisms <code>a</code>, <code>b</code> of an
identity must commute. In <code>examples/EckmannHilton.ali</code> the setting is built
over a <code>Bicategory</code> module—itself written in ordinary alifib, its structural
laws oriented into pairs of cells one dimension up—and the claim is stated
as a map into the module’s type of equations, with two images withheld:</p>
<pre><code class="language-ali"><div class="code-line"><span class="token atrule">@EckmannHilton</span></div><div class="code-line"><span class="token keyword">let</span> <span class="token keyword">total</span> <span class="token class-name">Commutativity</span> <span class="token operator">::</span> <span class="token class-name">Equation</span> <span class="token operator">=</span> <span class="token punctuation">[</span></div><div class="code-line">    lhs <span class="token operator">=></span> a b<span class="token punctuation">,</span></div><div class="code-line">    rhs <span class="token operator">=></span> b a<span class="token punctuation">,</span></div><div class="code-line">    dir <span class="token operator">=></span> <span class="token operator">?</span><span class="token punctuation">,</span></div><div class="code-line">    inv <span class="token operator">=></span> <span class="token operator">?</span></div><div class="code-line"><span class="token punctuation">]</span></div></code></pre><p>Each <code>?</code> is a <em>hole</em>: an image left open, to be filled interactively. The two
fills are the proof: a pair of three-dimensional diagrams assembled by
clicking through rewrites exactly as before, in which <code>a</code> and <code>b</code> slide past
one another. Textbooks give this argument as a sequence of pictures, and the
sequence built here is scarcely longer; the difference is that every move now
exists as a cell, checked and retained, where the pictures rely on the
reader’s goodwill to interpolate between them.</p>
<figure>
<a href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/05-eckmann-hilton.png"><img src="https://aria-sgai-notebook.inkandswitch.com/alifib/img/05-eckmann-hilton.png" alt="A hole-filling session on EckmannHilton. The visualisation shows a string
diagram with two beads labelled a and b sitting on wires of the identity
1-cell, mid-proof: b has slid past a and now stands to its left. The rewrite
list offers the structural moves available at this step. The REPL pane shows
the session was begun with &quot;fill 0&quot; and displays the goal: rewrite a b into
b a."></a>
<figcaption>
<p>Filling the hole <code>dir =&gt; ?</code>. The proof that <code>a b</code> rewrites to <code>b a</code> is
assembled move by move; <code>done</code> splices the finished diagram into the map. <a class="expand" href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/05-eckmann-hilton.png"><span class="link-label">View Full Size </span><span class="link-tail"><span class="link-label">Image</span><svg class="icon icon-arrows-expand" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-corner-tl"><path d="M0.5 3.5v-3h3"/><path d="m0.5 0.5 3 3"/></g><g class="icon-corner-tr"><path d="M9.5 3.5v-3h-3"/><path d="m9.5 0.5-3 3"/></g><g class="icon-corner-bl"><path d="M0.5 6.5v3h3"/><path d="m0.5 9.5 3-3"/></g><g class="icon-corner-br"><path d="M9.5 6.5v3h-3"/><path d="m9.5 9.5-3-3"/></g></svg></span></a></p>
</figcaption>
</figure>
<h2 id="four-readings">Four readings</h2>
<p>Beneath the syntax, an alifib type is a <em>finite directed complex</em>: a finite
family of cells graded by dimension, each carrying a partition of its boundary
into an input half and an output half. The combinatorics—the <em>molecules</em> of
my book, <em>Combinatorics of higher-categorical diagrams</em>—descend from
far-sighted work of Richard Steiner in the early 1990s, in a lineage that
runs back through Ross Street to the early days of higher category theory.
What holds the construction together is <em>topological soundness</em>: every such complex
is realised by an honest cell complex with exactly one cell per generator, so
the combinatorics never drift from the geometry they claim to describe.</p>
<p>The reward is that one object may be read, faithfully, in four ways at once:
as a piece of directed topology; as a presentation of a higher category; as a
higher-dimensional rewrite system; and as a directed higher inductive type,
with constructors between constructors. Each reading is a way of using the
language—one may reason equationally in monoidal categories and
bicategories, specify abstract machines whose every run is a witnessed trace,
or describe a space and ask for its homology at the prompt—and the readings
illuminate one another. In <code>examples/Bicategory_examples.ali</code>, the walking
equivalence (a morphism invertible up to invertible 2-cells) sits beside the
walking <em>adjoint</em> equivalence, which adds the two triangle equations of an
adjunction. Asked for their homology, the interpreter reports <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>H</mi><mn>2</mn></msub><mo>=</mo><mi mathvariant="double-struck">Z</mi></mrow><annotation encoding="application/x-tex">H_2 = \mathbb{Z}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0813em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0813em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6889em;"></span><span class="mord mathbb">Z</span></span></span></span> for the
first and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>H</mi><mn>2</mn></msub><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">H_2 = 0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal" style="margin-right:0.0813em;">H</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3011em;"><span style="top:-2.55em;margin-left:-0.0813em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight">2</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span> for the second: a hole in dimension two, witnessing
topologically the classical fact that a bare equivalence is not coherent—and
that promoting it to an adjoint equivalence is precisely what fills the hole.</p>
<figure>
<a href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/06-homology.png"><img src="https://aria-sgai-notebook.inkandswitch.com/alifib/img/06-homology.png" alt="The REPL pane after two commands. &quot;homology Equivalence&quot; reports homology
groups by dimension with H_2 = Z; &quot;homology AdjointEquivalence&quot; reports the
same groups with H_2 = 0. Both types are loaded from
examples/Bicategory_examples.ali."></a>
<figcaption>
<p>A type is a cell complex; its holes, counted by dimension, are one command
away. Here the topology detects a categorical fact, the incoherence of a
non-adjoint equivalence. <a class="expand" href="https://aria-sgai-notebook.inkandswitch.com/alifib/img/06-homology.png"><span class="link-label">View Full Size </span><span class="link-tail"><span class="link-label">Image</span><svg class="icon icon-arrows-expand" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-corner-tl"><path d="M0.5 3.5v-3h3"/><path d="m0.5 0.5 3 3"/></g><g class="icon-corner-tr"><path d="M9.5 3.5v-3h-3"/><path d="m9.5 0.5-3 3"/></g><g class="icon-corner-bl"><path d="M0.5 6.5v3h3"/><path d="m0.5 9.5 3-3"/></g><g class="icon-corner-br"><path d="M9.5 6.5v3h-3"/><path d="m9.5 9.5-3-3"/></g></svg></span></a></p>
</figcaption>
</figure>
<h2 id="what-comes-next">What comes next</h2>
<aside class="move-up" style="--move-up: 0">The proof assistant is joint work with Alex Kavvos (University of Bristol),
with further contributions from Wessel de Weijer; the algorithms at the
interpreter’s core were developed with Diana Kessler, and the mathematics it
rests on owes much to my ongoing work with Clémence Chanavat, both at Tallinn.</aside>
<p>alifib today is a working system: a batch interpreter, a terminal REPL, the
browser interface pictured throughout this post, a line-protocol daemon for
editors, and an MCP server offering the same engine, tool by tool, to AI
agents. The last deserves a word. In alifib, building a proof is search, and
search is untrusted, whether a human is clicking or a model is calling; what
is trusted is the kernel that accepts or rejects each step. It does not matter,
then, how capable or how erratic the model is: nothing it produces enters a
type except through the same check that governs a human hand. This division
of labour—an inventive but untrusted proposer set against a small checker—will
sound familiar to readers from the Safeguarded AI programme.</p>
<aside class="move-up" style="--move-up: 0">The name <em>alifib</em> is borrowed from a song on Robert Wyatt’s <em>Rock Bottom</em>: a love song
written in a private version of the English language.</aside>
<p>alifib can be tried at <a href="http://compose.ee/alifib">http://compose.ee/alifib</a>, and its source lives at
<a href="https://github.com/ahadziha/alifib">https://github.com/ahadziha/alifib</a>. A paper giving a full account of the
language and its interpreter is currently under review.</p>
<p>Further off, modules will acquire higher cells of their own (a module is
already, in disguise, a complex whose points are types); a strategy language,
where the effectful part of the runtime will live, I/O included,
is being worked out with Wessel de Weijer; and a small family of type
constructors—duals, suspensions, Gray products, joins—will make globes,
oriented simplices, and oriented cubes available structurally, rather than as
constructions made by hand.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[SDCPNs: A Common Language for Complex Systems]]></title>
      <link>https://aria-sgai-notebook.inkandswitch.com/sdcpns/</link>
      <guid isPermaLink="true">https://aria-sgai-notebook.inkandswitch.com/sdcpns/</guid>
      <pubDate>Thu, 03 Sep 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[Formal guarantees of AI safety begin as a modelling problem: to reason rigorously about whether an AI-controlled system is safe, you first need a mathematical representation of that system. This post builds a Stochastic Dynamic Coloured Petri Net up from an ordinary Petri net, one feature at a time, and then uses three cyber-physical examples — an industrial gas supply chain, a truck fleet, and a semiconductor fab — to show the range of systems the resulting formalism can represent.]]></description>
      <content:encoded><![CDATA[<p>We want to provide formal guarantees of AI safety across a wide range of domains and applications. That creates a modelling problem before it creates an AI problem: if we want to reason rigorously about whether an AI-controlled system is safe, we first need a mathematical representation of that system and the potential consequences of action within it.</p>
<p>There are many established mathematical languages for describing the world. Differential equations describe quantities that evolve continuously over time, such as temperature or motion. State machines and automata describe systems that move between discrete modes. Markov decision processes capture decision-making under uncertainty. Petri nets represent concurrency and competition for shared resources. Each of these formalisms help model different sorts of behaviours that we find in the real world.</p>
<p>Hybrid-system formalisms are designed to bring multiple of these lenses together. We are investing in <em>Stochastic Dynamic Coloured Petri Nets</em> (SDCPNs), a Petri net variant that provides a way to represent continuous and discrete behaviour, multiple interacting entities, structured state, data-dependent decisions, concurrency and randomness within a single framework.</p>
<p>That combination is particularly useful for <em>cyber-physical</em> systems: where a physical process is monitored and even controlled by digital elements. A physical change may enable or block a decision; a decision in cyberspace can alter a physical trajectory; and failures often emerge not from either side independently, but from their interaction.</p>
<p>In this post we’ll build up an SDCPN from an ordinary Petri net, explain what each extension adds, and then use several cyber-physical examples to show the range of systems the resulting formalism can represent.</p>
<h2 id="what-is-an-sdcpn">What is an SDCPN?</h2>
<p>A <a href="https://petrinaut.org/">Petri net</a> is a directed bipartite graph where <strong>places</strong> (circles) and <strong>transitions</strong> (rectangles) are connected by <strong>arcs</strong> (edges/arrows), in totality representing a process. Places can represent conditions or resource buckets and are <em>marked</em> by <strong>tokens</strong>, with the markings recording the state of the process at any given moment. Transitions are enabled by the presence of tokens in their input places, and when they <em>fire</em> they consume tokens from their input places and produce tokens in their output places, thereby changing the state of the process.</p>
<p>For example, a very simple Petri net modelling a pedestrian traffic light might look like this:</p>
<figure class="border autoplay">
<video controls playsinline preload autoplay loop muted aria-label="An animated Petri net of a pedestrian traffic light: a token moves from a Red place through a Switch to Green transition into a Green place, then back through Switch to Red, cycling continuously." src="https://aria-sgai-notebook.inkandswitch.com/sdcpns/traffic-light.mp4"></video>
<figcaption>
<p>A pedestrian traffic light as a plain Petri net, drawn in Petrinaut. The token moves from <em>Red</em> to <em>Green</em> as each transition fires, and back again.</p>
</figcaption>
</figure>
<p>The above Petri net includes two places, Red and Green, connected by two transitions. The net starts with one token in Red, when “Switch to Green” fires, it moves to Green; when “Switch to Red” fires, it moves back to Red. This illustrates the logic of switching between green and red on a traffic light. In practice the firings would be near-instantaneous.</p>
<p>A plain Petri net records what can happen and in what order, but without modelling durations, likelihoods, or what distinguishes one token from another, and it cannot answer “how often” or “how likely” questions about a given scenario.</p>
<p>An SDCPN extends a plain Petri net with 4 features:</p>
<ul>
<li>
<p><strong>Colour</strong>. Each place is associated with a <em>colour set</em> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>C</mi></mrow><annotation encoding="application/x-tex">C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0715em;">C</span></span></span></span>, which defines the type and structure of data that tokens in that place may carry. A token residing in the place has a particular <em>colouring</em> <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>∈</mo><mi>C</mi></mrow><annotation encoding="application/x-tex">x \in C</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.5782em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0715em;">C</span></span></span></span>: that is, a specific assignment of values to the attributes defined by the place’s colour set. Colouring therefore makes otherwise identical tokens distinguishable. For example, if a place represents stored food products, its colour set might specify the tuple <em>(product ID, production date, expiry date, …)</em>, while an individual token could have colouring <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi><mo>=</mo><mo stretchy="false">(</mo><mtext>P123</mtext><mo separator="true">,</mo><mn>01</mn><mi mathvariant="normal">/</mi><mn>06</mn><mi mathvariant="normal">/</mi><mn>2026</mn><mo separator="true">,</mo><mn>01</mn><mi mathvariant="normal">/</mi><mn>09</mn><mi mathvariant="normal">/</mi><mn>2026</mn><mo separator="true">,</mo><mo>…</mo><mtext> </mtext><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">x = (\text{P123}, 01/06/2026, 01/09/2026, \dots)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord text"><span class="mord">P123</span></span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">01/06/2026</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">01/09/2026</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="minner">…</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mclose">)</span></span></span></span>. Token values can be used to influence net behaviour, for example the introduction of a transition guard function <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">G(x)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">G</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span>, which evaluates the colouring <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> of the tokens available to a transition. The transition is enabled when <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><mn>1</mn></mrow><annotation encoding="application/x-tex">G(x)=1</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">G</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6444em;"></span><span class="mord">1</span></span></span></span> and disabled when <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mo>=</mo><mn>0</mn></mrow><annotation encoding="application/x-tex">G(x)=0</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">G</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6444em;"></span><span class="mord">0</span></span></span></span>. For instance, a transition representing “ship to customer” could have a guard <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">G(x)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">G</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span> = 1 if the product’s expiry_date &gt; today + 365 days, and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>G</mi><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">G(x)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">G</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span></span></span></span> = 0 otherwise, so that only products that have a sufficient shelf life are eligible for shipment.</p>
</li>
<li>
<p><strong>Stochastic firing.</strong> Rather than firing immediately once enabled, each enabled transition <em>i</em> independently draws a random firing delay from a probability distribution whose parameters can depend on the token’s colouring <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span>. In the common exponential case, <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>P</mi><mo stretchy="false">(</mo><mtext>fire in </mtext><mi>d</mi><mi>t</mi><mo stretchy="false">)</mo><mo>≈</mo><msub><mi>λ</mi><mi>i</mi></msub><mo stretchy="false">(</mo><mi>x</mi><mo stretchy="false">)</mo><mtext> </mtext><mi>d</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">P(\text{fire in } dt) \approx \lambda_i(x)\,dt</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">P</span><span class="mopen">(</span><span class="mord text"><span class="mord">fire in </span></span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">≈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord"><span class="mord mathnormal">λ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span></span></span></span>, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msub><mi>λ</mi><mi>i</mi></msub></mrow><annotation encoding="application/x-tex">\lambda_i</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8444em;vertical-align:-0.15em;"></span><span class="mord"><span class="mord mathnormal">λ</span><span class="msupsub"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.3117em;"><span style="top:-2.55em;margin-left:0em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mathnormal mtight">i</span></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.15em;"><span></span></span></span></span></span></span></span></span></span> is the firing rate of transition <em>i</em>. When multiple transitions are enabled, the one with the shortest sampled delay fires first. A higher <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow><annotation encoding="application/x-tex">\lambda</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal">λ</span></span></span></span> means a shorter expected delay, so faster processes tend to fire before slower ones, but a slower transition can still occasionally fire first. For example, the chance of a delivery van breaking down can depend on colour dimensions which record the van’s age and the length of time since its last maintenance.</p>
</li>
<li>
<p><strong>Deterministic dynamics (ordinary differential equations - ODEs).</strong> While a token sits in a place, its colouring <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>x</mi></mrow><annotation encoding="application/x-tex">x</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.4306em;"></span><span class="mord mathnormal">x</span></span></span></span> (e.g. fluid level, machine’s wear) evolves continuously through</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>d</mi><mi>x</mi><mo>=</mo><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo><mtext> </mtext><mi>d</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">dx = f(x, p)\,dt</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">p</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span></span></span></span></span>
<p>where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(x, p)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">p</span><span class="mclose">)</span></span></span></span> is the deterministic drift term which dictates the trend of the colouring evolution and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>p</mi></mrow><annotation encoding="application/x-tex">p</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.625em;vertical-align:-0.1944em;"></span><span class="mord mathnormal">p</span></span></span></span> is the set of fixed constants such as flow rates or degradation coefficients. This evolution replaces the assumption of static token colouring: a token’s data changes continuously while it remains in a place, according to differential equations that can represent processes such as material consumption, fluid-level changes, or progressive equipment degradation. Whether a token enables a transition or at what rate the enabled transition will fire can therefore evolve over time depending on the evolution of token data. We can use this to model the freshness of food decreasing over time at a rate of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>d</mi><mi>x</mi><mo>=</mo><mo>−</mo><mi>k</mi><msup><mi>e</mi><mrow><mo>−</mo><mi>T</mi><mi mathvariant="normal">/</mi><mn>20</mn></mrow></msup><mtext> </mtext><mi>d</mi><mi>t</mi></mrow><annotation encoding="application/x-tex">dx = -k e^{-T/20}\,dt</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.9713em;vertical-align:-0.0833em;"></span><span class="mord">−</span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.888em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight" style="margin-right:0.1389em;">T</span><span class="mord mtight">/20</span></span></span></span></span></span></span></span></span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span></span></span></span>, where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>k</mi></mrow><annotation encoding="application/x-tex">k</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal" style="margin-right:0.0315em;">k</span></span></span></span> is a base decay rate and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>T</mi></mrow><annotation encoding="application/x-tex">T</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">T</span></span></span></span> is the storage temperature, so warmer conditions accelerate the loss of freshness and the product becomes unsellable when its freshness crosses a threshold.</p>
</li>
<li>
<p><strong>Stochastic dynamics (stochastic differential equations - SDEs).</strong> The differential equations can include random noise, so continuous evolution can be perturbed by random noise. This is represented as a stochastic differential equation (SDE):</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>d</mi><mi>x</mi><mo>=</mo><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo><mtext> </mtext><mi>d</mi><mi>t</mi><mo>+</mo><mi>σ</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo><mtext> </mtext><mi>d</mi><mi>B</mi></mrow><annotation encoding="application/x-tex">dx = f(x, p)\,dt + \sigma(x, p)\,dB</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">x</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">p</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin">+</span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">σ</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">p</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">d</span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span></span></span></span></span>
<p>where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>B</mi></mrow><annotation encoding="application/x-tex">B</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6833em;"></span><span class="mord mathnormal" style="margin-right:0.0502em;">B</span></span></span></span> denotes a stochastic process such as the Wiener process through which randomness enters and <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>σ</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">\sigma(x, p)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.0359em;">σ</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">p</span><span class="mclose">)</span></span></span></span> is the diffusion term acting as the scaling factor of randomness. The first term <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>f</mi><mo stretchy="false">(</mo><mi>x</mi><mo separator="true">,</mo><mi>p</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">f(x, p)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1076em;">f</span><span class="mopen">(</span><span class="mord mathnormal">x</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord mathnormal">p</span><span class="mclose">)</span></span></span></span> is the same drift term appearing in deterministic dynamics. Examples of stochastic dynamics are demand rates or ambient temperature drifting randomly.</p>
</li>
</ul>
<p>The four features together allow SDCPNs to represent all aspects of a cyber-physical system in the same state and clock: its physical process (a tank emptying, a machine wearing), control logic (when to dispatch, when to service), and the randomness that affects both (a breakdown, a demand spike).</p>
<h3 id="the-origin-of-sdcpns">The origin of SDCPNs</h3>
<p>While ordinary Petri nets date back to the 1960s, SDCPNs were developed by <a href="https://link.springer.com/chapter/10.1007/11587392_10">Mariken Everdij and Henk Blom</a> at the National Aerospace Laboratory (NLR) in the Netherlands, in the early 2000s. The problem they were trying to solve was quantifying the probability of a mid-air collision, an event too rare to observe in testing, and requires modelling many aircraft moving continuously, controlled by different humans and automated systems. Everdij and Blom extended the Dynamically Coloured Petri Net (DCPN) by adding Brownian motion to the equations governing how token data evolves between firings, and defined the result as a formal 12-component specification (<a href="https://old.sf.bg.ac.rs/downloads/katedre/apatc/ICRAT2010.pdf">P, T, A, N, S, C, V, W, G, D, F, I</a>).</p>
<p>The formalism was designed to support a safety case, expressed in probabilistic terms: a defensible and quantitative statement about how often something bad happens in a system too complex to test exhaustively. With a view to enabling the same kind of guarantees to be made regarding the behaviour of frontier AI models, the ARIA <a href="https://aria.org.uk/opportunity-spaces/trust-everything-everywhere/safeguarded-ai">Safeguarded AI</a> (SgAI) programme is also building on SDCPNs. Our goal is to enable the development of world models expressive enough to represent both complex physical systems and the AI that control them, with a safety specification over both. The air traffic problem, with continuous dynamics, multiple agents, human and automated controllers, rare catastrophic events is exactly the class of system SgAI is building for. The only difference is the controller: the original use case modelled human operators and procedural automation, while SgAI focuses on neural networks.</p>
<h2 id="modelling-the-real-world-with-sdcpns">Modelling the real world with SDCPNs</h2>
<p>The three sections that follow model real systems as SDCPNs. The first, an industrial gas supply chain, is built progressively: one feature of the formalism is added at each step until the model is a full SDCPN. The other two, truck fleet maintenance and semiconductor fabrication, are described as full SDCPNs from the start, to illustrate the range of domains the formalism reaches.</p>
<h2 id="industrial-gas-supply-chain">Industrial gas supply chain</h2>
<p>Taking an industrial gases supply chain as an example use case: a gas supplier delivers liquid gases (e.g. nitrogen, oxygen) to customer sites by road tankers. The supply chain operates in a standard practice where the supplier owns the liquid in each customer’s tank, reads the level by telemetry, and decides when to send a refill. The customer draws product as needed and only pays for what they consume, but does not place orders.</p>
<p>Each tank empties from two sources: the customer’s consumption and heat leaking through the walls, which boils liquid gas off continuously. A delivery that arrives before enough space exists risks overfilling. The supplier must keep every tank within a safe range:</p>
<ul>
<li>
<p><strong>Too low</strong>: the customer’s production line stops (stockout).</p>
</li>
<li>
<p><strong>Too full</strong>: boil-off gas has nowhere to go, pressure rises, and the relief valve vents the gases into the atmosphere, creating wastage and potential safety concerns.</p>
</li>
</ul>
<p>The reorder point, load size, consumption rate, boil-off rate, and delivery lead time are all coupled. A tanker dispatched to one site is unavailable to other customer sites until it returns.</p>
<h3 id="plain-petri-net">Plain Petri net</h3>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/gases-1-pn" title="Gases 1 — one customer, ordering on a level trigger. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Gases 1 — one customer, ordering on a level trigger. <a class="outlink" href="https://demo.petrinaut.org/examples/gases-1-pn"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>First we model the system using a plain Petri net with only places, transitions, tokens (without colours) and different arcs and arc weights.</p>
<p>The net consists of just 1 customer site to illustrate the core concepts. To start, there is a tank of nitrogen on site holding 42 out of 54 units of liquid gas; one tanker at the depot and up to 2 loads may be on order at once. The customer’s production line is running.</p>
<p>As the customer consumes the nitrogen and some boils off, the level drops. When the level drops to 15 (representing the telemetry-based sensor in the tank), an order is placed. A tanker dispatches, arrives and delivers 12 units (only if 12 units of space exist in the tank). The permit and tanker return on delivery and cycle repeats.</p>
<p>Stockout happens if the tank hits zero and the production line stops. It restarts when at least 1 unit arrives.</p>
<p>If the tank is completely full, a relief valve opens and reduces the level of gas (by 1 unit). Under this level trigger order policy, venting is unreachable since the maths of the reorder point and load size prevent it (gas only refills by 12 units when at or below 15 units). In later timed-extensions of the model, we introduce pressure-driven venting since in practice, venting is required when pressure gradually builds as the liquid warms.</p>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/gases-1-pn-consumption-trigger" title="Gases 1 — the consumption-trigger variant. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Gases 1 — the consumption-trigger variant. <a class="outlink" href="https://demo.petrinaut.org/examples/gases-1-pn-consumption-trigger"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>The above shows the net for a variant of the order policy based on consumption-trigger. Instead of reordering when the level drops below a threshold, the system reorders after every 8 units are drawn by the customer without accounting for any evaporation. This results in a failure mode whereby, If enough nitrogen boils off, the tank empties without the consumption counter ever reaching 8. The system reaches a deadlock: the tank is at zero, fewer than 8 units have been drawn since the last order, and nothing in the model can change the state in the system so the production line stops and never restarts.</p>
<p>Without time accounted for in the model, the net picks any enabled transition to fire without any rules on ordering. There can be a scenario where the transition for consuming nitrogen is fired repeatedly and empties the contents without dispatching the tanker for refill. Adding durations fixes this so events happen according to rates rather than random choice, which we explore in the next progression where stochastic firing rates are added.</p>
<p>This basic Petri net is useful for checking the logic of the system, such as finding deadlocks (consumption-trigger variant), checking conservation laws (liquid + ullage = 54 always) etc. Without the additional features, it falls short on answering the more interesting timing questions like whether the tank runs dry before the tanker arrives, or what load size and reorder point minimise stockouts.</p>
<h3 id="add-time">Add time</h3>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/gases-2-spn" title="Gases 2 — a stochastic net, with one tanker shared between two sites. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Gases 2 — a stochastic net, with one tanker shared between two sites. <a class="outlink" href="https://demo.petrinaut.org/examples/gases-2-spn"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>The above shows a Stochastic Petri net (SPN), based on the same structure as the basic Petri net with the addition of rates on each transition and a second site (SlowNitrogen) to represent a low-consumption customer.</p>
<p>Each enabled transition now independently draws a random delay time from a general family of distributions. One prominent distribution in this family is the exponential distribution, where each transition has a random delay <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>t</mi><mo>∈</mo><mo stretchy="false">(</mo><mn>0</mn><mo separator="true">,</mo><mi mathvariant="normal">∞</mi><mo stretchy="false">)</mo></mrow><annotation encoding="application/x-tex">t \in (0, \infty)</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6542em;vertical-align:-0.0391em;"></span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∈</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mopen">(</span><span class="mord">0</span><span class="mpunct">,</span><span class="mspace" style="margin-right:0.1667em;"></span><span class="mord">∞</span><span class="mclose">)</span></span></span></span> drawn from:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mi>P</mi><mo stretchy="false">(</mo><mi>t</mi><mo>∣</mo><mi>λ</mi><mo stretchy="false">)</mo><mo>=</mo><mi>λ</mi><msup><mi>e</mi><mrow><mo>−</mo><mi>λ</mi><mi>t</mi></mrow></msup></mrow><annotation encoding="application/x-tex">P(t \mid \lambda) = \lambda e^{-\lambda t}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal" style="margin-right:0.1389em;">P</span><span class="mopen">(</span><span class="mord mathnormal">t</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">∣</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1em;vertical-align:-0.25em;"></span><span class="mord mathnormal">λ</span><span class="mclose">)</span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.8991em;"></span><span class="mord mathnormal">λ</span><span class="mord"><span class="mord mathnormal">e</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8991em;"><span style="top:-3.113em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mathnormal mtight">λ</span><span class="mord mathnormal mtight">t</span></span></span></span></span></span></span></span></span></span></span></span></span>
<p>where <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow><annotation encoding="application/x-tex">\lambda</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal">λ</span></span></span></span> is the average delay per hour (firing rate) for that transition. So whichever transition is delayed the least fires first. For example, when the customer’s (SteadyNitrogen) tank is half full, three transitions are enabled at once: the customer drawing product (rate 0.80/h), boil-off (0.16/h), and the tanker arriving (rate 1/6 ≈ 0.17/h). The runtime engine rolls a random delay time for each: 0.45 hours for product draw, 3.1 hours for boil-off, and 2.8 hours for the arrival. The nitrogen draw transition fires first so one unit leaves the tank, the clock advances 0.45 hours, and all three roll again from scratch.</p>
<p>A limitation of modelling journey time as a stochastic rate on the arrival transition (1/6 or 1/9 per hour, giving mean journeys of 6 and 9 hours) is that the exponential distribution allows a delivery to arrive almost immediately or take far longer than its mean. Realistic journey times need a minimum and a bounded spread, which requires clocks counted down by dynamics.</p>
<p>With the addition of a second customer, the one supplier tanker serves both sites with a new “returning” state to represent the time the tanker spends driving home after each delivery, introducing a risk of stockout if one customer is left waiting whilst the tanker delivers for another. For example, if the SteadyNitrogen tank crosses its reorder threshold (16 units) while the tanker is mid-journey to the SlowNitrogen site, it must wait for the delivery to complete (up to 9 hours remaining), the return trip (~4 hours) plus its own journey (~6 hours) before receiving the product. At a combined drain of ~0.96 units/hour, 16 units of buffer lasts roughly 17 hours, so on some runs can result in a stockout for the high consumption customer (SteadyNitrogen).</p>
<p>This stochastic net introduces time and contention with transitions competing to fire next and additional customers that can lock out delivery for each other. It answers questions on stockout frequency, the cost of sharing a tanker and the effect of slower routes. It falls short on the realism of timing (a 6-hour journey is exponentially distributed, so it sometimes arrives in minutes and sometimes takes a day), distinguishing between different products, and continuous processes like boil-off are still modelled as discrete random events rather than a steady flow.</p>
<h3 id="add-data-types-colours">Add data types (colours)</h3>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/gases-3-cpn" title="Gases 3 — colours added, a mixed fleet across three customers. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Gases 3 — colours added, a mixed fleet across three customers. <a class="outlink" href="https://demo.petrinaut.org/examples/gases-3-cpn"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>To distinguish which product a tanker is holding, we can add colours so each tanker token now has a data field identifying its gas type. In this example, the timing mechanism stays stochastic but the network grows to three customers and three tankers.</p>
<p>The third customer requires a different type of gas, oxygen with a draw rate of 0.60/h and average delivery journey time of 12 hours. The depot now holds 3 tankers (2 nitrogen, 1 oxygen) and a transition guard checks the correct product is dispatched. If a nitrogen order is placed and only the oxygen tanker is idle, the order waits. The cycle for drawing, boiling-off and ordering works as before.</p>
<p>With product identity added to the fleet, the SPN with colours can answer questions on whether fleet composition matters more than fleet count and by how much. For example, the same three tankers re-specified from one to two oxygen tankers can eliminate the oxygen stockout completely if they are the most critical customer from a business perspective. The colour features also allows for modelling of tanker spot hires. For example if 3 or more orders are waiting and no tanker is idle, a hire transition fires: its kernel writes a new tanker token into the depot with a rental flag, and a release transition destroys it when the demand clears. (This is included in the next progression where the colours are evolving (dynamics).)</p>
<p>This model still has the same timing limitations as the SPN: exponential journey times and discrete boil-off events.</p>
<h3 id="add-continuous-evolution">Add continuous evolution</h3>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/gases-4-dcpn" title="Gases 4 — the full SDCPN: colour with continuous dynamics. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Gases 4 — the full SDCPN: colour with continuous dynamics. <a class="outlink" href="https://demo.petrinaut.org/examples/gases-4-dcpn"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>This model adds dynamic colours and replaces the stack of unit tokens with a single token governed by differential equations, to model the level of gas and pressure as real numbers that can fall or grow continuously. With the inclusion of dynamics, the net can now model scenarios that simpler nets couldn’t:</p>
<ul>
<li>
<p><strong>Pressure building in a tank with relief valve cycling.</strong> Boil-off gas fills the empty space above the liquid, the reduced space raises the pressure more so a fuller tank pressurises faster. When pressure reaches the relief setpoint (8 units), the valve opens, 0.4 units of liquid escape as gas and the pressure drops just below 8. If the tank is still nearly full, pressure climbs back to 8 and the valve opens again. This reveals a trade-off where a fuller tank means fewer stockouts but more wastage through vented product, resulting in higher supplier costs.</p>
</li>
<li>
<p><strong>Realistic durations from lognormal distributions.</strong> The dispatch kernel samples a journey time from a lognormal distribution and writes it onto the tanker token. Place dynamics counts the remaining time down by 1 per hour, and the arrival transition fires when it reaches 0. This gives each journey a reasonable minimum, a mode near the nominal hours, and a long tail for delays.</p>
</li>
</ul>
<p>To add further realism, the net models outages in supplier’s own liquid production as discrete events. The production plant can experience an outage, on average every 90 hours (frequency exaggerated for visibility in simulation) and takes 24 hours to restart. While it is down, deliveries are dispatched from a more distant plant and increases each journey time by almost double.</p>
<p>This model captures the physical behaviour that the simpler nets cannot: tanks drain smoothly, pressure builds and vents in cycles, and journey times have realistic distributions rather than memoryless exponentials. What it does not capture is variability in the environment: customers always draw products at a constant contracted rate, and pressure boil-off does not vary with temperature.</p>
<h2 id="truck-fleet-maintenance">Truck fleet maintenance</h2>
<p>SDCPNs can be used for truck fleet operators to solve their maintenance problem, in deciding when and where they should service each vehicle. The service must occur early enough to prevent a breakdown on the road, and late enough not to waste maintenance capacity. The maintenance schedule must ensure deliveries are still completed within the agreed window.</p>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/truck-fleet-predictive-maintenance" title="Truck fleet predictive maintenance. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Truck fleet predictive maintenance. <a class="outlink" href="https://demo.petrinaut.org/examples/truck-fleet-predictive-maintenance"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>In this example, the fleet operator has 8 trucks over three route classes: motorway (420 km, flat), urban (180 km, stop-start), and mountain (260 km, steep gradients). Loads are posted to a freight board at stochastic rates; if no truck collects one within 10 hours, it goes to a competitor. Each delivery has a time window (2.2× driving time): missing the window incurs a 30% revenue penalty, missing it entirely (breakdown mid-route) means losing the load and paying for recovery.</p>
<p>The depot has 2 service bays, 2 technicians, and a stock of spare parts. A truck that reaches a wear threshold is serviced (5 hours, resets to new). A truck that breaks down at the roadside needs a recovery vehicle, a tow, and a longer repair (12 hours, only partially restores condition). Both compete for the same bays, technicians, and parts.</p>
<p>Each truck is modelled as a coloured token carrying 19 fields of continuous and discrete colouring. The key mechanisms are:</p>
<ul>
<li>
<p><strong>Evolution of three wear components per truck governed by deterministic dynamics.</strong> Brakes, engine, and tyres each degrade at different rates depending on the route. Brake wear accumulates 2.5× faster on mountain descents (heavy braking on gradients); engine wear increases when carrying load; tyre wear rises with road roughness and weather severity.</p>
</li>
<li>
<p><strong>Breakdown as stochastic firing driven by the weakest component.</strong> The roadside failure rate is set by whichever component is the most degraded. As degradation increases, the firing rate <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>λ</mi></mrow><annotation encoding="application/x-tex">\lambda</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.6944em;"></span><span class="mord mathnormal">λ</span></span></span></span> increases, so a truck with fresh brakes and fresh tyres but a worn engine fails at the engine’s rate.</p>
</li>
<li>
<p><strong>Road condition evolution governed by stochastic dynamics.</strong> The same diffusion mechanism that models ambient temperature in the industrial gas supply chain is also used here to model the variations in driving conditions that a truck encounters on any given trip. Two state variables on each truck token represent this road conditions: road severity which multiplies all wear rates and fuel consumption and speed factor which impacts travel speed and therefore journey time.</p>
</li>
<li>
<p><strong>Driver hours enforced through colour and guard.</strong> Each truck token carries a running hours field, and a guard that prevents dispatch once it exceeds the legal limit. EU Regulation (<a href="https://eur-lex.europa.eu/eli/reg/2006/561/oj/eng">561/2006</a>) limits drivers to 9 hours of continuous driving, so the model includes a compulsory rest stop at the depot for 11 hours before it can be dispatched again.</p>
</li>
</ul>
<p>The model helps the operator understand the interactions between servicing policy, workshop capacity and dispatch rules in relation to fleet profitability whilst accounting for variability in weather and roads. The main simplification is that it models one depot with identical trucks and generic parts. A real operator has multiple depots, mixed-age vehicles, component-specific spares, and demand that varies with season.</p>
<h2 id="semiconductor-wafer-fabrication">Semiconductor wafer fabrication</h2>
<p>A semiconductor foundry processes batches of wafers (wafer lots) through 28 steps using shared machines. The same machine group handles multiple steps in the sequence, for example the same lithography group is visited at layers 0, 4, 9, 12, 16, 20, and 24, so wafer lots at different stages compete for the same machines.</p>
<p>The main trade-off is between throughput and yield. Machines degrade as they work, but taking one offline for maintenance backs up the production. Deferring maintenance increases the risk for breakdown or slow accumulation of defects that only becomes visible at final inspection.</p>
<figure class="petrinaut wide">
<iframe class="petrinaut-frame" src="https://demo.petrinaut.org/embed/examples/semiconductor-fab-drift" title="Semiconductor fab drift. Open in Petrinaut" loading="lazy" sandbox="allow-scripts allow-same-origin" referrerpolicy="no-referrer" allowfullscreen></iframe>
<figcaption>
<p>Semiconductor fab drift. <a class="outlink" href="https://demo.petrinaut.org/examples/semiconductor-fab-drift"><span class="link-label">Open in </span><span class="link-tail"><span class="link-label">Petrinaut</span><svg class="icon icon-link-share-arrow" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><g class="icon-arrow"><path d="M6.5 0.5h3v3"/><path d="M9.5 0.5 5 5"/></g><path d="M4.788 2.502H1c-0.276 0-0.5 0.224-0.5 0.5V9c0 0.276 0.224 0.5 0.5 0.5h5.998c0.276 0 0.5-0.224 0.5-0.5V5.089"/></svg></span></a></p>
</figcaption>
</figure>
<p>For this use case we modelled 16 chambers across 4 machine groups (4 lithography, 6 etch, 4 furnace, 2 inspection), 3 product types (logic, memory, analog) arriving stochastically, a capacity limit of 50 lots in progress, and 3 technicians shared between planned and unplanned work.</p>
<p>Each lot token carries its product type, current layer, cumulative defects, age, and a customer due date. Each machine token holds data on its condition, particle count, hours since maintenance, machine group, qualification level, and batch counter. The degradation mechanisms use the following SDCPN features:</p>
<ul>
<li>
<p><strong>Machine degradation is modelled as deterministic dynamics</strong> that accelerate with contamination. When the machine crosses a threshold (default 0.85) it triggers preventive maintenance. The breakdown rate grows exponentially with condition, so a machine at 0.9 fails 8 times faster than a fresh one.</p>
</li>
<li>
<p><strong>Microscopic particle contamination as stochastic dynamics.</strong> Contamination fluctuates randomly but trends upward the longer a machine runs without maintenance.</p>
</li>
<li>
<p><strong>Per-chamber process drift as stochastic dynamics.</strong> Each chamber’s process accuracy varies independently via a second diffusion process. Drift in either direction from zero increases defect rates. Maintenance recalibrates the chamber, but calibration is imperfect and each reset samples a small residual error. This means two chambers on the same tool can produce different defect rates even at identical condition and particle levels.</p>
</li>
<li>
<p><strong>Defects at each step are sampled from a distribution when the processing transition fires.</strong> The mean depends on the machine’s current condition, its particle count and process drift. A clean, well-calibrated chamber deposits few defects; a degraded, contaminated or drifted chamber deposits many. Defects accumulate across all 28 layers but the number of defects is only checked at final inspection.</p>
</li>
</ul>
<p>To model the dispatch mechanism as close to real-world fabrication as possible, the net enforces all four of the below constraints simultaneously:</p>
<ul>
<li>
<p><strong>Machine-specific qualification.</strong> Not every machine can run every product. Each machine carries a bitmask encoding which product types it is certified for.</p>
</li>
<li>
<p><strong>Chamber-level recipes.</strong> Processing time depends on the product being made. For example, a furnace step takes 5 hours at baseline; analog lots take 15% longer, memory lots 15% shorter. The same applies to lithography and etch steps.</p>
</li>
<li>
<p><strong>Batch processing for furnaces.</strong> Furnace steps require loading multiple lots before the chamber fires. Lots destined for a furnace layer enter a batch queue and are loaded one at a time into an available furnace chamber. In the model the furnace only starts when the batch reaches 4 lots, or after 3 hours if fewer are available.</p>
</li>
<li>
<p><strong>Dynamic priority from customer due dates.</strong> Each lot arrives with a due date. Between dispatch events, the lot’s priority escalates continuously via dynamics. Priority is recalculated every 2 hours based on remaining time to deadline. A lot close to its due date is dispatched ahead of a lot with time in hand. Lots that exceed their due date by 30 hours trigger a deadline renegotiation, and the due date extends by one full cycle time and priority resets, representing the real-world practice of agreeing a new delivery window with the customer.</p>
</li>
</ul>
<p>The model can help fabrication managers understand the interaction between maintenance policy, chamber calibration, batch sizing and in progress capacity in relation to yield and on-time delivery. The main simplifications are that each chamber processes one lot at a time and lots cannot be split for partial rework (lot-splitting). This means the model’s absolute throughput figures are lower than a real foundry’s, but relative comparisons between scenarios remain valid because all scenarios share the same simplification. Modelling lot-splitting would reduce the cost of contamination events by allowing partial recovery as a secondary effect, but does not change the fundamental question of when to maintain.</p>
<h2 id="beyond-simulation-guarantees-and-probabilistic-claims">Beyond simulation: guarantees and probabilistic claims</h2>
<p>Running a simulation shows what’s expected to happen under a specific set of conditions. However, simulations don’t show what happens under untested conditions, nor is it guaranteed that simulations which are run are in any way representative of the real world. This section covers two kinds of claims that go beyond individual runs: structural guarantees that follow from the net’s topology alone, and probabilistic claims built on sampling. Other parts of the SgAI programme are developing methods for reasoning formally about complex, compositional SDCPN models at scale; here we focus on what the net structure and simulation-based methods already provide.</p>
<h3 id="structural-guarantees">Structural guarantees</h3>
<p>These come from the net’s topology, arc weights and connections:</p>
<ul>
<li>
<p><strong>Reachability</strong>. Can the system ever reach a specific state? For example: “is there any sequence of events that results in the tank being empty with no order on its way?” The check works by building the full graph of every state the net can reach from its starting state by firing every enabled transition and recording each new state. If the target state appears in that graph, it is reachable. If it does not appear, it is proven unreachable from the given starting state regardless of timing or ordering.</p>
</li>
<li>
<p><strong>Coverability</strong>. Reachability asks whether the net can reach a specified marking: the exact number and types of tokens in every place. Coverability asks whether the net can reach a marking that contains at least what you specify, while allowing additional tokens in any place. For example it answers the question, “Can there be at least 10 failed machines?&quot;, without specifying token counts for the other places.</p>
</li>
<li>
<p><strong>Boundedness</strong>. For any place in the net, you can determine the highest number of tokens it can ever hold by scanning every state in the full state graph and recording the maximum token count seen at each place. If the graph is finite, that maximum is the proven bound and can never be exceeded in any execution. If the graph cannot be completed (because some place grows without limit), the net is unbounded at that place, which usually signals a modelling error or a missing constraint. Conservation is a special case, where if a set of places always sums to the same total (e.g. items in storage + items in transit = total inventory), then each place in that set is bounded.</p>
</li>
<li>
<p><strong>Liveness</strong>. Can every transition fire at least once? A transition that never appears in the state graph is dead, either from a modelling error or a mechanism that is unreachable by design. Deadlock is the extreme case: no transition can fire and the system freezes.</p>
</li>
</ul>
<p>These checks require a finite state space. Once tokens carry real-valued data (continuous levels, pressures, temperatures), the state graph cannot be exhaustively checked and these proofs do not apply directly. Extending formal guarantees to models with continuous state and stochastic dynamics is an open research problem, and one of the reasons the ARIA Safeguarded AI programme is investing in this formalism.</p>
<h3 id="probabilistic-claims">Probabilistic claims</h3>
<p>Once the model includes randomness, it produces distributions rather than single answers. An SDCPN produces probabilistic results differently from a conventional simulator in two ways:</p>
<ul>
<li>
<p><strong>The randomness is formal and inspectable.</strong> Firing rates, lognormal durations, diffusion terms etc. are explicit components of the model specification. They can be inspected so a reviewer can read exactly what distribution governs each event. In a conventional simulator, stochastic behaviour typically lives in code scattered across event handlers.</p>
</li>
<li>
<p><strong>Rare-event probabilities can be quantified.</strong> Some failures are too rare to observe in ordinary Monte Carlo, estimating a probability of <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><msup><mn>10</mn><mrow><mo>−</mo><mn>9</mn></mrow></msup></mrow><annotation encoding="application/x-tex">10^{-9}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8141em;"></span><span class="mord">1</span><span class="mord"><span class="mord">0</span><span class="msupsub"><span class="vlist-t"><span class="vlist-r"><span class="vlist" style="height:0.8141em;"><span style="top:-3.063em;margin-right:0.05em;"><span class="pstrut" style="height:2.7em;"></span><span class="katex-sizing reset-size6 size3 mtight"><span class="mord mtight"><span class="mord mtight">−</span><span class="mord mtight">9</span></span></span></span></span></span></span></span></span></span></span></span> would need billions of runs. SDCPNs support acceleration methods (<a href="https://doi.org/10.1109/acc.2011.5991305">importance sampling</a>, <a href="https://doi.org/10.1201/9781420008548.ch10">interacting particle systems</a>) that exploit the net’s structure (strong Markov property) to estimate these probabilities efficiently.</p>
</li>
</ul>
<h3 id="tooling-for-scalable-oversight">Tooling for scalable oversight</h3>
<p><a href="https://hash.ai/">HASH</a> leads the “Interaction Paradigms” technical area (TA1.3) of the <a href="https://aria.org.uk/opportunity-spaces/trust-everything-everywhere/safeguarded-ai/">SgAI programme</a>. As part of this work, we’ve developed tooling to enable not only the modelling and simulation of complex systems as SDCPNs, but also AI that assists users in capturing their understanding of a domain while ensuring information relevant to a model or experiment isn’t missed or misrepresented. The same AI is also usable to help users understand complicated models developed by other people (or AI), and enables the precision editing, customisation, and hierarchical use of existing models. This open-source tooling is available at <a href="https://petrinaut.org">petrinaut.org</a>, as well as integrated into HASH’s semantic graph platform (<a href="https://app.hash.ai">app.hash.ai</a>). All of the interactive examples embedded in this post were built and run from the <a href="https://demo.petrinaut.org/">Petrinaut demo</a> website.</p>
<h2 id="conclusion">Conclusion</h2>
<p>As we’ve shown in this post, SDCPNs can be applied to virtually any cyber-physical domain: from industrial gas supply and truck fleet maintenance through to semiconductor fabrication, and the other use-cases we’re exploring as part of SgAI — with our own work predominantly focused on biopharmaceutical and chemicals manufacturing and logistics.</p>
<p>Each feature from the SDCPN formalism adds something new:</p>
<ul>
<li>
<p>A plain Petri net finds logical and structural failures like deadlocks</p>
</li>
<li>
<p>Stochastic firing adds timing, so the model can quantify how often events like stockouts occur.</p>
</li>
<li>
<p>Colour makes identity visible so fleet composition or product routing can be modelled.</p>
</li>
<li>
<p>Continuous dynamics replace discrete approximations with differential equations for flows like gas levels, machine degradation and contamination.</p>
</li>
<li>
<p>Stochastic dynamics helps to add the necessary real world noise, like environmental variability to the model.</p>
</li>
</ul>
<p>Not only can SDCPNs represent these various interactions, but it can quantify the frequency, costs and conditions that trigger them, too. This kind of quantification is what will enable us to move away from a world of weak evals and assumptions to one in which AI can be used as a real-time decision maker in safety-critical, cyber-physical systems, backed by evidence and verification.</p>]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Changing the frame: formal modeling with LLM assistance]]></title>
      <link>https://aria-sgai-notebook.inkandswitch.com/changing-the-frame/</link>
      <guid isPermaLink="true">https://aria-sgai-notebook.inkandswitch.com/changing-the-frame/</guid>
      <pubDate>Fri, 21 Aug 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[Every formal model — a scientific model, an engineering design, a program, a spreadsheet — is expressed within some <em>frame</em> that fixes the concepts available and the rules the expression must obey. Computing has overwhelmingly preferred unconstrained frames, and LLM assistance is making the resulting drift of abstractions worse. This post argues for frames that are more constrained but can be <em>changed</em> fluidly, and works through a case study in structured spreadsheets: inferring a schema for a flight-itinerary dataset, tightening it with types and path equations, and then restructuring it.]]></description>
      <content:encoded><![CDATA[<h2 id="introduction">Introduction</h2>
<h3 id="frames-and-formal-models-today">Frames and formal models today</h3>
<p>A wide swathe of technical creative activity, from creating scientific models
and engineering designs to writing computer programs to making spreadsheets, is
aimed at expressing a model of something precisely enough to be operationalized
on a computer. The model, or at least the part of it given algorithmic content,
is thus in principle mathematizable. We will think of scientific models,
engineering designs, computer programs, and spreadsheets all as kinds of <em>formal
models</em>, even if the degree to which they are formalized mathematically varies
in the current state of practice.</p>
<p>Any formal model exists within some <em>frame</em>,<sup class="footnote-ref"><a href="#fn1" id="fnref1">1</a></sup> defining the concepts and
language within which the model is expressed and the rules to which this
expression must conform. A typical frame for scientific modeling might be “a
parameterized system of ordinary differential equations,” specific enough to
unambiguously describe a class of models and to support uniform algorithms for
fitting and simulation. The frame for a computer program is, to a first
approximation, the programming language in which it’s written along with the
ecosystem of libraries supporting it.</p>
<p>Frames can be more or less constrained. A relatively unconstrained frame has the
obvious benefit of allowing creator flexibility to formally express their
intent. A relatively constrained frame carries a more definite and opinionated
viewpoint, stipulating stronger structure and assumptions. This makes it easier
to say <em>some</em> things, while prohibiting the expression of other things. This is
good to the extent that it blocks expressions that would be outright <em>mistakes</em>
or, more subtly, would deviate from settled design decisions or conventions. Of
course, it is bad to the extent that it prevents useful and desirable
expressions not anticipated by the choice of frame.</p>
<p>Builders and users of computing technology seem to have revealed an overwhelming
preference for unconstrained frames. From the beginning, the field of computing
has, in its theory, emphasized the <em>universality</em> of programming languages; in
its practice, language designers herald something more elusive, the
“expressivity” of their languages. Meanwhile, the species of mathematized
software most widely used by people who are not mathematicians or
programmers—the spreadsheet—is highly unconstrained, a blank canvas for
tabular data with no further requirements on data layout or typing. Advanced
users can introduce their own logic, of arbitrary complexity, within cell
formulas, which can vary across cells in arbitrary ways.</p>
<p>It is not hard to imagine what might lie behind this preference for
unconstrained frames. Working in a constrained frame, if a creator reaches the
boundary of the frame, they may be unable to express their intent at all within the
formal model, an experience at best frustrating and at worst intolerable. In the
most unconstrained frames, like a general-purpose programming language, it is
usually possible to find <em>some</em> way to proceed, even if it involves blurring or
subverting what were once crisp abstractions. Put simply, most people would
rather muddle through than risk hitting an immovable wall.</p>
<p>Nevertheless, the costs borne under the status quo are severe, and growing. With
the frame for the formal model having few hard constraints, the typical software
project of even moderate complexity relies on a web of semi-formal and informal
abstractions described by a mix of code and documentation but not fully
specified by either. Rather, the abstractions are maintained—if they are
maintained—by a shared understanding among the engineering team, created and
renewed through careful communication. Competent programmers create bugs not by
trivial logic errors but by failing to understand intricacies of what software
abstractions assume or how they interact with each other.</p>
<p>As for spreadsheets, a notorious weakness of the spreadsheet frame is in
transitioning from individual use to reliable organizational processes. Because
the assumptions behind a spreadsheet are highly implicit, hidden within a web of
cell dependencies and in the mind of the spreadsheet’s designer, it is all too
easy to make ad hoc changes that silently break those assumptions. The lack of
type constraints in spreadsheets is a frequent source of errors, including the
perennial joke that Excel interprets every input it sees as a date. While
different in many respects, what unites the standard paradigms for programming
and spreadsheets is that by situating the formal model in a generic and
unconstrained frame, the domain-specific abstractions that inevitably support
the model possess little formal content; to compensate, disciplined human
processes are required to safely use and change the model at any scale beyond a
single person.</p>
<h3 id="the-effect-of-emerging-llm-technology">The effect of emerging LLM technology</h3>
<p>Such is the status quo, an equilibrium beset by familiar problems but so far
ultimately workable. New technology, in the form of generative machine learning
based on LLMs, threatens the status quo by changing the tradeoffs involved. It
also presents an opportunity to rethink how we design technologies for formal
modeling.</p>
<p>In programming, the emergence of LLMs is decreasing the cost of generating
more or less functional code, while exacerbating the challenge of maintaining a
coherent set of abstractions and assumptions. The experienced programmer, when
attempting to make a change, will notice when the change seems to depart from
existing structures or assumptions and take the opportunity to rethink their
approach, which can ultimately lead to revising past design decisions or
changing the abstractions. The LLM, ever compliant, proceeds blithely along the
path of least resistance. Moreover, while adept at mirroring surface-level
patterns present in the codebase, the LLM is not privy to the web of shared
mental representations that enable the effective engineering team to
function.<sup class="footnote-ref"><a href="#fn2" id="fnref2">2</a></sup> Thus, at present, LLM coding agents appear to enable
significant short-term productivity gains while gradually reducing the
codebase’s overall concision and coherence. As LLMs become more prolific in
office work, the lack of explicit structure and typing constraints in tools like
spreadsheets will only exacerbate the fragility already present. In both cases,
the distinction between <em>creating</em>, an active process that engages the mind and
forces it to confront problems, and <em>reviewing</em>, a passive activity often
experienced as boring, means that opportunities to catch mistakes or revisit
assumptions are more likely to be missed when LLMs are heavily involved.</p>
<p>It has been suggested that the solution to this problem is that humans cease to
interact directly with formal models at all. A formal model is useful, the
thought goes, only insofar as it fulfills some externally validatable purpose;
if it does so adequately, it does not matter how the model was constructed or
how messy it might be internally. The LLM will mediate the interaction between
the human and the formal model exclusively through natural language, so that the
human never has to look inside.</p>
<p>Whether or not it is technically feasible, this vision misses much of the point
of using formal models in the first place. Be they scientific models or computer
programs or spreadsheets or abstract philosophical schemes, mathematical (or at
least mathematizable) models have been among humanity’s most successful tools to
augment our capacity for thinking. Not only does <em>having</em> good formal models
increase our powers of deduction, prediction, and counterfactual reasoning, but
the process of <em>creating</em> models forces us to sharpen and clarify our ideas,
both for ourselves and to communicate with others. To abdicate a direct role in
formal modeling would be to weaken human capability and agency.</p>
<p>The question then is not <em>whether</em> we should continue to directly interact with
formal models but <em>how</em> the process of model building should adapt to a potent
new source of automation and augmentation.</p>
<h3 id="the-future-of-formal-modeling">The future of formal modeling</h3>
<p>All of the foregoing points to the same conclusion: that safe and reliable
formal modeling pairing humans with LLM agents requires more constrained frames
than are common today. Under our meaning of “frame,” this entails more than a
Markdown file making vague suggestions; a frame specifies concepts, language,
and assumptions that set the rules for formal modeling and are enforced when
type checking a putative model.</p>
<p>How can we adopt more constrained frames while retaining the flexibility that
has made the present paradigm of unconstrained modeling dominant? The question
may seem paradoxical but the answer is, in a sense, simple: by insisting on more
constrained frames, while making it possible to <em>change the frame</em> when needed.</p>
<p>It is easier to make this assertion than to say how it can be realized. Existing
languages and tools that serve as frames for formal models tend to be very
rigid. Changing them is costly and impractical. There are two reasons to believe
that now is the right time to build more malleable tools for formal modeling.</p>
<p>First, as we have seen, LLMs have made it cheaper to generate code and other
formal artifacts; consequently, they have made precise specification both more
useful as input and more necessary to prevent abstraction drift. There is a
growing recognition that existing practices must change. This recognition should
create the will to explore new paradigms rather than tinker around the margins
of existing ones.</p>
<p>Second, there is now a mature enough body of theoretical knowledge to actually
create the new infrastructure that will allow fluidly defining and changing
frames. This body of knowledge is broadly <em>categorical logic</em>, the intersection
of category theory with mathematical logic and type theory. The role of logic
and type theory as the mathematical underpinning for <em>syntax</em> in formal modeling
is well known, and the use of categories as a denotational <em>semantics</em> for
programming languages is familiar to experts. What is less appreciated, even
among experts, is how category theory provides a meta-mathematical toolbox to
enable modularity and reuse within and between frames, both in the narrow
technical sense and in a more nebulous cultural sense affecting how we design
formal languages. As we have noted, language designers have overwhelmingly
sought to create ever more general and expressive languages, the ideal frame in
which to say everything that can be said. This attitude is essentially
reductionist. Category theory encourages us to see logic not as something prior
to mathematics and science, but as a part of it, and to search for well-adapted
mathematical universes in which to create models.<sup class="footnote-ref"><a href="#fn3" id="fnref3">3</a></sup> These
universes can look familiar, like functional programming or first-order logic,
but can also natively encompass non-standard features crucial in certain
domains, such as resource sensitivity, reversibility, nondeterminism,
probability, and fallibility. Categorical logic offers a uniform technical
toolbox to describe these paradigms without privileging any of them.</p>
<p>In the <a href="https://catcolab.org/">CatColab</a> project, supported by the ARIA
<a href="https://aria.org.uk/opportunity-spaces/trust-everything-everywhere/safeguarded-ai">Safeguarded
AI</a>
program, we are exploring this vision for scientific and cyberphysical modeling,
programming in domain-specific logics, and tabular and relational data. It is
beyond the scope of this post to describe these efforts in detail, nor do we
claim to have a complete account of any of these vast topics. Rather, in the
remainder of this post, we will hone in on one of the paradigms highlighted
above—spreadsheet-like data modeling—as a case study in how to build tools,
underpinned by categorical logic, that set constrained frames for formal
modeling while also allowing the frame to be changed fluidly.</p>
<h2 id="case-study-structuring-spreadsheets">Case study: Structuring spreadsheets</h2>
<p>Among the most successful species of software ever created, the spreadsheet
enables people who might otherwise never encounter formal models to
interactively build their own. It is also a notorious source of ill-specified,
unreliable processes. Instead of taking the spreadsheet to exist within a fixed,
unconstrained frame,<sup class="footnote-ref"><a href="#fn4" id="fnref4">4</a></sup> we envision a spreadsheet-like
tool that allows creating and transforming not just data itself but frames that
constrain the data.</p>
<p>This idea has an obvious precedent: the relational database, another very
successful type of software. Using a relational database, the modeler defines a
frame via a <em>schema</em>. In the most common incarnation of a relational database as
a SQL database, a schema specifies types of entities (tables) with data
attributes (columns), as well as functional relations (foreign keys) between
entity types. Once a schema is defined, the database checks that the <em>instance</em>
of the schema, given by actual tables of data, conforms to the schema, raising
an error if a transaction would violate the constraints. It is a testament to
their utility and soundness that SQL databases have enjoyed such a long
lifespan, in essentially the same form, when so many other computing
technologies have endlessly churned.</p>
<p>While SQL databases are, from a strictly logical standpoint, more powerful than
spreadsheets, the two kinds of tool have little overlap in users and usage.
There are more and less deep reasons for this. Certainly, SQL databases are
harder to use than spreadsheets. Users of SQL databases, mostly programmers,
interact with the database using the SQL language, a domain-specific formal
language, while spreadsheets provide an intuitive graphical interface based on
direct manipulation. This is practically important, and in our work on
structured spreadsheets, we aim to create interfaces that allow and encourage
direct manipulation. But the more basic reason that SQL databases are not a
substitute for spreadsheets is that SQL databases are rigid where spreadsheets
are fluid. SQL provides powerful languages to define schemas and query
instances, but a far more limited language to change schemas. Database
migrations usually comprise an ad hoc mix of SQL statements and generic code in
an orchestrating programming language, and are a frequent source of frustration
even for professional engineers. In short, SQL databases make it easy to
<em>define</em> a frame but hard to <em>change</em> the frame.</p>
<p>Such situations are typical across science and engineering: experts know well
their objects of study and often devise sophisticated formalisms for them, but
tend to undertheorize the <em>relations</em> between objects.<sup class="footnote-ref"><a href="#fn5" id="fnref5">5</a></sup>
Category theory encourages us to take relations seriously by not introducing a
class of objects without also considering the <em>morphisms</em> between them. In the
case of relational databases, formulating a category-theoretic account of
schemas leads directly to a plethora of notions of morphism between schemas. A
morphism between schemas, defined declaratively, then induces a data migration
between instances over those schemas.<sup class="footnote-ref"><a href="#fn6" id="fnref6">6</a></sup></p>
<p>The vignettes that follow illustrate how constrained yet fluid modeling with
tabular data enables more reliable automation with LLM agents. We will not enter
into technical details about the underlying mathematics and technology, which is
being actively developed. Rather, the aim is to convey a sense of what is
possible through concrete examples backed by working demonstrations.</p>
<h3 id="inferring-an-initial-frame">Inferring an initial frame</h3>
<p>Relational data is often artificially flattened into a smaller number of tables
or even a single table, in deference to the dominant abstraction in data
science, the data frame. As a typical example, consider the <a href="https://www.transtats.bts.gov/DatabaseInfo.asp?QO_VQ=EFI">DB1B
dataset</a>, a random
sample of flight itineraries from reporting carriers, published quarterly as a
set of three CSV files by the U.S. Bureau of Transportation Statistics. The
intended schema for the data is conveniently
<a href="https://jblevins.org/notes/airline-data">summarized</a> by a third-party note
about airline industry datasets.</p>
<p>The three tables correspond to three types of entities, all related to
itineraries: “tickets” (<code>Ticket</code>), covering the whole itinerary; “markets”
(<code>Market</code>), covering a single direction of a ticket; and “coupons” (<code>Coupon</code>),
covering individual flights.<sup class="footnote-ref"><a href="#fn7" id="fnref7">7</a></sup> The entity types are linked by
three mappings (foreign keys), as expressed by the diagram:</p>
<figure>
<svg class="diagram" width="349.16" height="160.96" viewBox="34 25 282 130" style="--diagram-share: 0.5301" role="img" aria-labelledby="d1t d1d">
<title id="d1t">Schema: Coupon, Market and Ticket</title>
<desc id="d1d">Three boxed entity types. An arrow labelled MktID goes from Coupon to Market, an arrow labelled ItinID from Market to Ticket, and a third arrow labelled ItinID from Coupon directly to Ticket.</desc>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M111.2 40.0L154.2 40.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M195.8 40.0L233.3 40.0"/>
  <path class="head" fill="currentColor" d="M238.8 40.0L232.3 42.7L232.3 37.3Z"/>
  <text class="edge" x="175.0" y="40.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">MktID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M111.2 59.6L153.8 79.9"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M196.2 100.1L233.8 118.0"/>
  <path class="head" fill="currentColor" d="M238.8 120.4L231.8 120.1L234.1 115.1Z"/>
  <text class="edge" x="175.0" y="90.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M280.0 60.0L280.0 79.9"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M280.0 100.1L280.0 114.5"/>
  <path class="head" fill="currentColor" d="M280.0 120.0L277.3 113.5L282.7 113.5Z"/>
  <text class="edge" x="280.0" y="90.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="35.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="70.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Coupon</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="245.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="280.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Market</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="245.8" y="127.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="280.0" y="140.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Ticket</text>
</svg>
</figure>
<p>In addition, each of the entity types has a couple dozen data attributes.</p>
<p>We could prompt the LLM generically to “infer an appropriate schema for the
data, then import the data” and hope for the best. That may well produce a
decent result, but what would we learn from it? Instead, we pursue a more
incremental and interactive strategy, starting with the loose frame advertised
directly by the source material, then gradually building our understanding of
the data and reifying that understanding by tightening the frame.</p>
<p>So we instead ask the LLM to “create a schema for the data capturing the stated
foreign key relations and including all other columns as string-valued data
attributes”. Since the columns in CSV files are untyped, the latter stipulation
is safest for an initial frame. The result is the schema shown in the diagram
above, augmented with many data attributes of type <code>String</code> (not shown).</p>
<p>We then ask the LLM to import the data itself as an instance of the schema. Even
under our minimalist frame, it is already possible that the data could fail to
conform to the schema by having links to entities that don’t exist. Since this
data was presumably exported from a relational database enforcing foreign key
constraints, we don’t expect this to happen, but it could still happen if, for
example, the random sampling was not consistent with the relational structure.
In any case, once the data is imported, the validity of the links will be
automatically checked—not by the LLM, but by the type checker for instances,
which is perfectly reliable—and any errant rows will be marked and reported.</p>
<h3 id="tightening-the-frame">Tightening the frame</h3>
<p>Having set an initial frame and imported the data, we strengthen our
understanding of the implicit model by attempting to add more constraints to the
frame.</p>
<p>First of all, while it may not be obvious from the nomenclature, the entity
types—<code>Coupon</code>, <code>Market</code>, and <code>Ticket</code>—form a hierarchy. Each ticket
contains some number of markets, and each market contains some number of
coupons. This means that, for each coupon, the ticket to which the coupon is
attached should be equal to the coupon’s market’s ticket. We can state this
logical requirement as a diagram (“the triangle commutes”):</p>
<figure>
<svg class="diagram" width="349.16" height="160.96" viewBox="34 25 282 130" style="--diagram-share: 0.5301" role="img" aria-labelledby="d2t d2d">
<title id="d2t">The triangle commutes</title>
<desc id="d2d">The same three entity types and three arrows, with a check mark inside the triangle they form, indicating that following MktID then ItinID from Coupon gives the same result as following ItinID directly.</desc>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M111.2 40.0L154.2 40.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M195.8 40.0L233.3 40.0"/>
  <path class="head" fill="currentColor" d="M238.8 40.0L232.3 42.7L232.3 37.3Z"/>
  <text class="edge" x="175.0" y="40.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">MktID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M111.2 59.6L153.8 79.9"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M196.2 100.1L233.8 118.0"/>
  <path class="head" fill="currentColor" d="M238.8 120.4L231.8 120.1L234.1 115.1Z"/>
  <text class="edge" x="175.0" y="90.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M280.0 60.0L280.0 79.9"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M280.0 100.1L280.0 114.5"/>
  <path class="head" fill="currentColor" d="M280.0 120.0L277.3 113.5L282.7 113.5Z"/>
  <text class="edge" x="280.0" y="90.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <path class="check" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M210.0 74.0l3.6 3.9l6.4 -8.0"/>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="35.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="70.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Coupon</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="245.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="280.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Market</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="245.8" y="127.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="280.0" y="140.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Ticket</text>
</svg>
</figure>
<p>Equivalently, as an equation in textual syntax:</p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mi mathvariant="monospace">M</mi><mi mathvariant="monospace">k</mi><mi mathvariant="monospace">t</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">D</mi></mrow><mo lspace="0.22em" rspace="0.22em"><mi separator="true" lspace="0em" rspace="0em">;</mi></mo><mrow><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">t</mi><mi mathvariant="monospace">i</mi><mi mathvariant="monospace">n</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">D</mi></mrow><mo>=</mo><mrow><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">t</mi><mi mathvariant="monospace">i</mi><mi mathvariant="monospace">n</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">D</mi></mrow><mo>:</mo><menclose notation="box"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow><mi mathvariant="monospace">C</mi><mi mathvariant="monospace">o</mi><mi mathvariant="monospace">u</mi><mi mathvariant="monospace">p</mi><mi mathvariant="monospace">o</mi><mi mathvariant="monospace">n</mi></mrow><mpadded height="-0.23em" depth="0.23em" voffset="-0.23em"><mspace mathbackground="black" width="0em" height="0.23em"></mspace></mpadded></mrow></mstyle></mstyle></mstyle></menclose><mo>→</mo><menclose notation="box"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow><mi mathvariant="monospace">T</mi><mi mathvariant="monospace">i</mi><mi mathvariant="monospace">c</mi><mi mathvariant="monospace">k</mi><mi mathvariant="monospace">e</mi><mi mathvariant="monospace">t</mi></mrow><mpadded height="-0.23em" depth="0.23em" voffset="-0.23em"><mspace mathbackground="black" width="0em" height="0.23em"></mspace></mpadded></mrow></mstyle></mstyle></mstyle></menclose></mrow><annotation encoding="application/x-tex">\mathtt{MktID} \mathbin{\boldsymbol{;}} \mathtt{ItinID} = \mathtt{ItinID} :
  \boxed{\mathtt{Coupon}\rule[-0.23em]{0pt}{0.23em}} \to \boxed{\mathtt{Ticket}\rule[-0.23em]{0pt}{0.23em}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8055em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathtt">MktID</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin"><span class="mord"><span class="mord"><span class="mpunct mathbf">;</span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6111em;"></span><span class="mord"><span class="mord mathtt">ItinID</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">=</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6111em;"></span><span class="mord"><span class="mord mathtt">ItinID</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1.5211em;vertical-align:-0.57em;"></span><span class="mord"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9511em;"><span style="top:-3.5211em;"><span class="pstrut" style="height:3.5211em;"></span><span class="boxpad"><span class="mord"><span class="mord"><span class="mord"><span class="mord mathtt">Coupon</span></span><span class="mord katex-rule" style="border-right-width:0em;border-top-width:0.23em;bottom:-0.23em;"></span></span></span></span></span><span style="top:-2.9511em;"><span class="pstrut" style="height:3.5211em;"></span><span class="katex-stretchy fbox" style="height:1.5211em;border-style:solid;border-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.57em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1.5211em;vertical-align:-0.57em;"></span><span class="mord"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9511em;"><span style="top:-3.5211em;"><span class="pstrut" style="height:3.5211em;"></span><span class="boxpad"><span class="mord"><span class="mord"><span class="mord"><span class="mord mathtt">Ticket</span></span><span class="mord katex-rule" style="border-right-width:0em;border-top-width:0.23em;bottom:-0.23em;"></span></span></span></span></span><span style="top:-2.9511em;"><span class="pstrut" style="height:3.5211em;"></span><span class="katex-stretchy fbox" style="height:1.5211em;border-style:solid;border-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.57em;"><span></span></span></span></span></span></span></span></span></span>
<p>When we add this path equation to the schema, the type checker will confirm that
it is true of the instance. Assuming it is, we can keep the equation as a
constraint that should apply to any future data. Better, though, is to observe
that the equation makes the <code>ItinID</code> column of <code>Coupon</code> logically redundant,
since it can always be computed by following the links through <code>Market</code> to
<code>Ticket</code>. So we can save on physical storage by deleting the <code>ItinID</code> column of
<code>Coupon</code> entirely. There is no disadvantage to doing so, since we can
reintroduce it as a <em>definition</em></p>
<span class="katex-display"><span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML" display="block"><semantics><mrow><mrow><mi mathvariant="monospace">C</mi><mi mathvariant="monospace">o</mi><mi mathvariant="monospace">u</mi><mi mathvariant="monospace">p</mi><mi mathvariant="monospace">o</mi><mi mathvariant="monospace">n</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">t</mi><mi mathvariant="monospace">i</mi><mi mathvariant="monospace">n</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">D</mi></mrow><mo><mi mathvariant="normal">≔</mi></mo><mrow><mi mathvariant="monospace">M</mi><mi mathvariant="monospace">k</mi><mi mathvariant="monospace">t</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">D</mi></mrow><mo lspace="0.22em" rspace="0.22em"><mi separator="true" lspace="0em" rspace="0em">;</mi></mo><mrow><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">t</mi><mi mathvariant="monospace">i</mi><mi mathvariant="monospace">n</mi><mi mathvariant="monospace">I</mi><mi mathvariant="monospace">D</mi></mrow><mo>:</mo><menclose notation="box"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow><mi mathvariant="monospace">C</mi><mi mathvariant="monospace">o</mi><mi mathvariant="monospace">u</mi><mi mathvariant="monospace">p</mi><mi mathvariant="monospace">o</mi><mi mathvariant="monospace">n</mi></mrow><mpadded height="-0.23em" depth="0.23em" voffset="-0.23em"><mspace mathbackground="black" width="0em" height="0.23em"></mspace></mpadded></mrow></mstyle></mstyle></mstyle></menclose><mo>→</mo><menclose notation="box"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="false"><mstyle scriptlevel="0" displaystyle="true"><mrow><mrow><mi mathvariant="monospace">T</mi><mi mathvariant="monospace">i</mi><mi mathvariant="monospace">c</mi><mi mathvariant="monospace">k</mi><mi mathvariant="monospace">e</mi><mi mathvariant="monospace">t</mi></mrow><mpadded height="-0.23em" depth="0.23em" voffset="-0.23em"><mspace mathbackground="black" width="0em" height="0.23em"></mspace></mpadded></mrow></mstyle></mstyle></mstyle></menclose></mrow><annotation encoding="application/x-tex">\mathtt{CouponItinID} \coloneqq \mathtt{MktID} \mathbin{\boldsymbol{;}} \mathtt{ItinID}  :
  \boxed{\mathtt{Coupon}\rule[-0.23em]{0pt}{0.23em}} \to \boxed{\mathtt{Ticket}\rule[-0.23em]{0pt}{0.23em}}</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.8333em;vertical-align:-0.2222em;"></span><span class="mord"><span class="mord mathtt">CouponItinID</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel"><span class="mrel"><span class="mop" style="position:relative;top:-0.0347em;">:</span></span><span class="mrel"><span class="mspace" style="margin-right:-0.0667em;"></span></span><span class="mrel">=</span></span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.8055em;vertical-align:-0.1944em;"></span><span class="mord"><span class="mord mathtt">MktID</span></span><span class="mspace" style="margin-right:0.2222em;"></span><span class="mbin"><span class="mord"><span class="mord"><span class="mpunct mathbf">;</span></span></span></span><span class="mspace" style="margin-right:0.2222em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:0.6111em;"></span><span class="mord"><span class="mord mathtt">ItinID</span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">:</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1.5211em;vertical-align:-0.57em;"></span><span class="mord"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9511em;"><span style="top:-3.5211em;"><span class="pstrut" style="height:3.5211em;"></span><span class="boxpad"><span class="mord"><span class="mord"><span class="mord"><span class="mord mathtt">Coupon</span></span><span class="mord katex-rule" style="border-right-width:0em;border-top-width:0.23em;bottom:-0.23em;"></span></span></span></span></span><span style="top:-2.9511em;"><span class="pstrut" style="height:3.5211em;"></span><span class="katex-stretchy fbox" style="height:1.5211em;border-style:solid;border-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.57em;"><span></span></span></span></span></span><span class="mspace" style="margin-right:0.2778em;"></span><span class="mrel">→</span><span class="mspace" style="margin-right:0.2778em;"></span></span><span class="katex-base"><span class="katex-strut" style="height:1.5211em;vertical-align:-0.57em;"></span><span class="mord"><span class="vlist-t vlist-t2"><span class="vlist-r"><span class="vlist" style="height:0.9511em;"><span style="top:-3.5211em;"><span class="pstrut" style="height:3.5211em;"></span><span class="boxpad"><span class="mord"><span class="mord"><span class="mord"><span class="mord mathtt">Ticket</span></span><span class="mord katex-rule" style="border-right-width:0em;border-top-width:0.23em;bottom:-0.23em;"></span></span></span></span></span><span style="top:-2.9511em;"><span class="pstrut" style="height:3.5211em;"></span><span class="katex-stretchy fbox" style="height:1.5211em;border-style:solid;border-width:0.04em;"></span></span></span><span class="vlist-s">​</span></span><span class="vlist-r"><span class="vlist" style="height:0.57em;"><span></span></span></span></span></span></span></span></span></span>
<p>that will be computed on demand and updated automatically when the data changes.
In a spreadsheet, such a definition is called a <em>formula</em>; in a relational
database, a <em>view</em>.</p>
<p>Redundancies can also be found in the data attributes. One such constraint is
helpfully if informally noted in the third-party summary (footnote 1):</p>
<blockquote>
<p>Carriers report complete itineraries so this [reporting carrier code] should
be equal for all markets and coupons in an itinerary.</p>
</blockquote>
<p>Or, formally:</p>
<figure>
<svg class="diagram" width="609.17" height="215.44" viewBox="4 25 492 174" style="--diagram-share: 0.9248" role="img" aria-labelledby="d3t d3d">
<title id="d3t">RPCarrier agrees across all three entity types</title>
<desc id="d3d">Coupon, Market and Ticket in a row, each with an arrow labelled RPCarrier down to a shared String type. Two check marks indicate that all three arrows agree, so the reporting carrier code is the same for every coupon and market in an itinerary.</desc>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M81.2 40.0L124.2 40.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M165.8 40.0L203.3 40.0"/>
  <path class="head" fill="currentColor" d="M208.8 40.0L202.3 42.7L202.3 37.3Z"/>
  <text class="edge" x="145.0" y="40.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">MktID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M291.2 40.0L330.8 40.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M379.2 40.0L413.3 40.0"/>
  <path class="head" fill="currentColor" d="M418.8 40.0L412.3 42.7L412.3 37.3Z"/>
  <text class="edge" x="355.0" y="40.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M68.0 60.0L134.8 107.7"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M163.1 127.9L225.4 172.5"/>
  <path class="head" fill="currentColor" d="M229.9 175.7L223.0 174.1L226.2 169.7Z"/>
  <text class="edge" x="149.0" y="117.8" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">RPCarrier</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M250.0 60.0L250.0 107.7"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M250.0 127.9L250.0 170.2"/>
  <path class="head" fill="currentColor" d="M250.0 175.7L247.3 169.2L252.7 169.2Z"/>
  <text class="edge" x="250.0" y="117.8" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">RPCarrier</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M432.0 60.0L365.2 107.7"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M336.9 127.9L274.6 172.5"/>
  <path class="head" fill="currentColor" d="M270.1 175.7L273.8 169.7L277.0 174.1Z"/>
  <text class="edge" x="351.0" y="117.8" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">RPCarrier</text>
  <path class="check" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M163.0 70.0l3.6 3.9l6.4 -8.0"/>
  <path class="check" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" d="M326.0 70.0l3.6 3.9l6.4 -8.0"/>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="5.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="40.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Coupon</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="215.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="250.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Market</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="425.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="460.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Ticket</text>
  <text class="node" x="250.0" y="190.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">String</text>
</svg>
</figure>
<p>Experiments show that LLMs are adept at identifying plausible constraints, a
useful complement to human inspection. Many, but not all, of the proposed
constraints validate against the data, and some are more complex than path
equations, involving aggregations or injectivity/surjectivity constraints. For
example, every ticket has at least one market, and every market has at least one
coupon. These are surjectivity constraints, denoted graphically by double-headed
arrows:</p>
<figure>
<svg class="diagram" width="609.17" height="37.14" viewBox="4 25 492 30" style="--diagram-share: 0.9248" role="img" aria-labelledby="d4t d4d">
<title id="d4t">MktID and ItinID are surjective</title>
<desc id="d4d">Coupon, Market and Ticket in a row, joined by arrows labelled MktID and ItinID. Both arrows are drawn double-headed, indicating surjectivity: every ticket has at least one market and every market at least one coupon.</desc>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M81.2 40.0L124.2 40.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M165.8 40.0L203.3 40.0"/>
  <path class="head" fill="currentColor" d="M208.8 40.0L202.3 42.7L202.3 37.3Z"/>
  <path class="head" fill="currentColor" d="M199.7 40.0L193.2 42.7L193.2 37.3Z"/>
  <text class="edge" x="145.0" y="40.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">MktID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M291.2 40.0L330.8 40.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M379.2 40.0L413.3 40.0"/>
  <path class="head" fill="currentColor" d="M418.8 40.0L412.3 42.7L412.3 37.3Z"/>
  <path class="head" fill="currentColor" d="M409.7 40.0L403.2 42.7L403.2 37.3Z"/>
  <text class="edge" x="355.0" y="40.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="5.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="40.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Coupon</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="215.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="250.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Market</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="425.8" y="27.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="460.0" y="40.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Ticket</text>
</svg>
</figure>
<p>Another important vector for tightening the frame is to set stronger types on
the data attributes, rather than making them all strings. Some attributes should
plainly be (positive) integers (<code>Year</code>, <code>Quarter</code>), (nonnegative) real
numbers (<code>Distance</code>, <code>MilesFlown</code>), or booleans (<code>RoundTrip</code>, <code>BulkFare</code>). In
this data, booleans are encoded as indicator variables (0 or 1), requiring a
modest amount of common sense on the part of the human or LLM to distinguish
them from integers. In all cases the type conversions are automatically checked
against the instance data and any type errors are reported.</p>

<h3 id="changing-the-frame">Changing the frame</h3>
<p>So far, we have reified and tested hypotheses about the model by introducing
types and constraints and eliminating redundant data, while keeping intact the
basic entity-relation structure of the original semi-formal schema. We may wish
to alter the frame more drastically.</p>
<p>Inspecting the current schema, the experienced modeler of relational data will
be struck that, in each table, the origin and destination airports are
represented not by a single column (<code>Origin</code> or <code>Dest</code>), but additionally by
many attributes of the airport (e.g. <code>OriginCityNum</code>, <code>OriginCountry</code>,
<code>OriginState</code>). This flattening of the same relational data across many tables
is messy, inefficient, and error-prone. Good modeling practice suggests that we
should reify the airports as a new type of entity (<code>Airport</code>) with data
attributes of its own.</p>
<p>With a little common sense, this should be a straightforward if tedious job, so
we instruct the LLM to “refactor the schema by creating an <code>Airport</code> entity type
with its own data attributes and factoring all information about
origin/destination airports through maps to this new type.” A first check on
this refactoring is by inspection of the target schema, which should look
something like:</p>
<figure>
<svg class="diagram" width="658.70" height="334.30" viewBox="19 15 532 270" style="--diagram-share: 1.0000" role="img" aria-labelledby="d5t d5d">
<title id="d5t">Airport reified as its own entity type</title>
<desc id="d5d">A revised schema with a fourth boxed type, Airport, at the centre. Coupon and Market each send a pair of arrows labelled Origin and Dest to Airport, and Ticket sends one labelled Origin. Airport in turn carries its own data attributes, shown as arrows labelled Country and State down to String types, with an ellipsis for those omitted. Coupon still maps to Market by MktID and Market to Ticket by ItinID.</desc>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M91.8 135.0L151.4 102.6"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M188.6 82.4L243.4 52.6"/>
  <path class="head" fill="currentColor" d="M248.2 50.0L243.8 55.5L241.2 50.7Z"/>
  <text class="edge" x="170.0" y="92.5" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">MktID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M321.8 50.0L381.4 82.4"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M418.6 102.6L473.4 132.4"/>
  <path class="head" fill="currentColor" d="M478.2 135.0L471.2 134.3L473.8 129.5Z"/>
  <text class="edge" x="400.0" y="92.5" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">ItinID</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M278.0 50.0L278.0 129.5"/>
  <path class="head" fill="currentColor" d="M278.0 135.0L275.3 128.5L280.7 128.5Z"/>
  <text class="edge" x="266.2" y="92.5" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="end">Origin</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M292.0 50.0L292.0 129.5"/>
  <path class="head" fill="currentColor" d="M292.0 135.0L289.3 128.5L294.7 128.5Z"/>
  <text class="edge" x="303.8" y="92.5" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="start">Dest</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M96.2 146.0L234.1 146.0"/>
  <path class="head" fill="currentColor" d="M239.6 146.0L233.1 148.7L233.1 143.3Z"/>
  <text class="edge" x="167.9" y="134.2" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle">Origin</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M96.2 164.0L234.1 164.0"/>
  <path class="head" fill="currentColor" d="M239.6 164.0L233.1 166.7L233.1 161.3Z"/>
  <text class="edge" x="167.9" y="175.8" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle">Dest</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M473.8 155.0L426.3 155.0"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M377.9 155.0L335.9 155.0"/>
  <path class="head" fill="currentColor" d="M330.4 155.0L336.9 152.3L336.9 157.7Z"/>
  <text class="edge" x="402.1" y="155.0" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Origin</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M264.2 175.0L230.1 207.7"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M209.0 227.9L178.9 256.8"/>
  <path class="head" fill="currentColor" d="M174.9 260.6L177.7 254.2L181.5 258.1Z"/>
  <text class="edge" x="219.6" y="217.8" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Country</text>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M305.8 175.0L339.9 207.7"/>
  <path class="arrow" fill="none" stroke="currentColor" stroke-width="1.25" d="M361.0 227.9L391.1 256.8"/>
  <path class="head" fill="currentColor" d="M395.1 260.6L388.5 258.1L392.3 254.2Z"/>
  <text class="edge" x="350.4" y="217.8" font-size="11.5" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">State</text>
  <circle class="dot" fill="currentColor" cx="278.5" cy="275.0" r="1.5"/><circle class="dot" fill="currentColor" cx="285.0" cy="275.0" r="1.5"/><circle class="dot" fill="currentColor" cx="291.5" cy="275.0" r="1.5"/>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="250.8" y="17.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="285.0" y="30.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Market</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="20.8" y="142.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="55.0" y="155.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Coupon</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="246.6" y="142.0" width="76.8" height="26.0" rx="2"/>
  <text class="node" x="285.0" y="155.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Airport</text>
  <rect class="box" fill="none" stroke="currentColor" stroke-width="1.25" x="480.8" y="142.0" width="68.4" height="26.0" rx="2"/>
  <text class="node" x="515.0" y="155.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">Ticket</text>
  <text class="node" x="160.0" y="275.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">String</text>
  <text class="node" x="410.0" y="275.0" font-size="14.0" fill="currentColor" font-family="ui-monospace, monospace" text-anchor="middle" dominant-baseline="central">String</text>
</svg>
</figure>

<h2 class="unnumbered" id="notes">Notes</h2>
<ol class="footnote-list" role="doc-endnotes">
<li id="fn1" class="footnote-item"><p>We will not attempt to be too precise about what is a “frame”—for us, a
pre-theoretical concept—but the choice of word is intended to evoke both a
“frame of reference,” meaning a chosen coordinate system or, more
abstractly, a chosen point of view, and a “framework,” defined by
<a href="https://en.wikipedia.org/wiki/Framework">Wikipedia</a> as an “essential
supporting structure on which other things are built.” <a href="#fnref1" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
<li id="fn2" class="footnote-item"><p>The use of Markdown files like <code>AGENTS.md</code> is a primitive means to mitigate
this problem. <a href="#fnref2" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
<li id="fn3" class="footnote-item"><p>The evocative phrase “mathematical universe” we borrow from Ingo
Blechschmidt’s paper on exploring “custom-tailored mathematical universes”
using topos theory <a href="#ref-blechschmidt2022" class="cite">[<span class="cite-label">Blechschmidt, 2022</span>]</a>. For technically minded readers, we
remark that our usage is intended to encompass categorical-logical
structures generally, not just toposes. <a href="#fnref3" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
<li id="fn4" class="footnote-item"><p>This frame is something like a countably infinite but finitely supported
list of sheets. Each sheet contains a countably infinite but finitely
supported grid of cells, and each cell contains a literal value or is
computed by a formula referencing other cells. <a href="#fnref4" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
<li id="fn5" class="footnote-item"><p>This may seem a strange thing to say about relational databases, but note
the level shift: relations <em>in</em> a schema are central to the standard
formalism, but we are talking about relations <em>between</em> schemas. <a href="#fnref5" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
<li id="fn6" class="footnote-item"><p>Categorical databases and functorial migrations have been studied for the
past fifteen years by applied category theorists <a href="#ref-spivak2012" class="cite">[<span class="cite-label">Spivak, 2012</span></a>, <a href="#ref-schultz2017" class="cite"><span class="cite-label">Schultz et al., 2017</span></a>, <a href="#ref-lambert2025" class="cite"><span class="cite-label">Lambert and Patterson, 2025</span></a>, <a href="#ref-carlson2026" class="cite"><span class="cite-label">Carlson and Patterson, 2026</span>]</a>, with a prehistory going back much further.
Technology realizing this theory consistent with its philosophy (cf. <a href="#ref-spivak2012ologs" class="cite">[<span class="cite-label">Spivak and Kent, 2012</span>]</a>) has been slower to develop. In past work <a href="#ref-patterson2022" class="cite">[<span class="cite-label">Patterson et al., 2022</span>]</a>,
we implemented categorical databases
(<a href="https://github.com/AlgebraicJulia/ACSets.jl">ACSets.jl</a>) and functorial
data migrations
(<a href="https://github.com/AlgebraicJulia/DataMigrations.jl">DataMigrations.jl</a>)
as in-memory data structures and embedded DSLs in the Julia programming
language. While it makes creating and changing schemas lighter weight than
in SQL, this software suffers from usability problems of its own, even for
its intended audience of programmers. Our present effort aims to demonstrate
the potential of fluid formal modeling with relational data to
non-specialists. <a href="#fnref6" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
<li id="fn7" class="footnote-item"><p>The words “ticket,” “market,” and “coupon” are quoted because in the DB1B
dataset they are used as airline industry jargon that may not align with
their everyday use. The literate interface for defining schemas in CatColab
encourages documenting the meaning of these terms in plain English. <a href="#fnref7" class="footnote-back" aria-label="Back to text"><svg class="icon icon-arrow-bent-up-right" viewBox="-0.5 -0.5 11 11" fill="none" stroke="currentColor" stroke-width="1" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false" xmlns="http://www.w3.org/2000/svg"><path d="M8.5 3 6.25 0.5 4 3"/><path d="M1.5 9.5h1.75a3 3 0 0 0 3-3v-6"/></svg></a></p>
</li>
</ol>
<h2 class="unnumbered" id="references">References</h2>
<p class="ref-entry" id="ref-blechschmidt2022">Ingo Blechschmidt. Exploring mathematical objects from custom-tailored mathematical universes. In Gianluigi Oliveri, Claudio Ternullo, and Stefano Boscolo, editors, <em>Objects, Structures, and Logics: FilMat Studies in the Philosophy of Mathematics</em>, pages 63–95. Springer, 2022. doi:<a href="https://doi.org/10.1007/978-3-030-84706-7_4">10.1007/978-3-030-84706-7_4</a>. arXiv:<a href="https://arxiv.org/abs/2204.00948">2204.00948</a>.</p>

<p class="ref-entry" id="ref-carlson2026">Kevin Carlson and Evan Patterson. Presheaves on lax double functors; or, Instances of models of double theories. <em>Applied Categorical Structures</em>, 34(30), 2026. doi:<a href="https://doi.org/10.1007/s10485-026-09866-y">10.1007/s10485-026-09866-y</a>. arXiv:<a href="https://arxiv.org/abs/2510.08861">2510.08861</a>.</p>

<p class="ref-entry" id="ref-lambert2025">Michael Lambert and Evan Patterson. Representing knowledge and querying data using double-functorial semantics. <em>Electronic Proceedings in Theoretical Computer Science</em>, 429:174–189, 2025. doi:<a href="https://doi.org/10.4204/EPTCS.429.9">10.4204/EPTCS.429.9</a>. arXiv:<a href="https://arxiv.org/abs/2403.19884">2403.19884</a>.</p>

<p class="ref-entry" id="ref-patterson2022">Evan Patterson, Owen Lynch, and James Fairbanks. Categorical data structures for technical computing. <em>Compositionality</em>, 4(5), 2022. doi:<a href="https://doi.org/10.32408/compositionality-4-5">10.32408/compositionality-4-5</a>. arXiv:<a href="https://arxiv.org/abs/2106.04703">2106.04703</a>.</p>

<p class="ref-entry" id="ref-schultz2017">Patrick Schultz, David I. Spivak, Christina Vasilakopoulou, and Ryan Wisnesky. Algebraic databases. <em>Theory and Applications of Categories</em>, 32(16):547–619, 2017. arXiv:<a href="https://arxiv.org/abs/1602.03501">1602.03501</a>.</p>

<p class="ref-entry" id="ref-spivak2012ologs">David I. Spivak and Robert E. Kent. Ologs: a categorical framework for knowledge representation. <em>PLoS ONE</em>, 7(1):e24274, 2012. doi:<a href="https://doi.org/10.1371/journal.pone.0024274">10.1371/journal.pone.0024274</a>. arXiv:<a href="https://arxiv.org/abs/1102.1889">1102.1889</a>.</p>

<p class="ref-entry" id="ref-spivak2012">David I. Spivak. Functorial data migration. <em>Information and Computation</em>, 217:31–51, 2012. doi:<a href="https://doi.org/10.1016/j.ic.2012.05.001">10.1016/j.ic.2012.05.001</a>. arXiv:<a href="https://arxiv.org/abs/1009.1166">1009.1166</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title><![CDATA[Why Supply Chains Are an Ideal Testbed for Safeguarded AI]]></title>
      <link>https://aria-sgai-notebook.inkandswitch.com/scm/</link>
      <guid isPermaLink="true">https://aria-sgai-notebook.inkandswitch.com/scm/</guid>
      <pubDate>Tue, 07 Apr 2026 00:00:00 GMT</pubDate>
      <description><![CDATA[Supply chains pair interrelated decisions with high-consequence outcomes, which is what makes them an ideal proving ground for Safeguarded AI — a world model that predicts an action’s consequences, an explicit specification of acceptable outcomes, and a verifier that can establish the system satisfies it.]]></description>
      <content:encoded><![CDATA[<p>Supply chains are inherently complex systems, where simple ‘optimisations’ may have significant downstream consequences, requiring a high level of assurance. A planner might reroute a shipment, switch supplier, adjust a production schedule, or release inventory—but each action can create knock-on effects several tiers away that may be hard for a human to accurately predict, or even reasonably be aware of. In biopharmaceutical supply chains, these consequences extend beyond cost and service levels to regulatory compliance, product quality, and product safety – ultimately resulting in an impact on patient health as well as that of the business.</p>
<p>Imagine a pandemic-scale surge in vaccine demand which coincides with labour shortages at a factory and constrained cold-chain capacity. Planners must decide which sites produce which medicines, how scarce inventory and freight are allocated, and which shipments are rerouted—without leaving one region short or displacing another critical product. A locally sensible choice can create failures several tiers downstream. Even in routine operations, the recent <a href="https://hash.ai/">HASH</a>/<a href="https://aria.org.uk/">ARIA</a> <a href="https://aria.org.uk/media/fapdst2k/hash-sails-public-report.pdf">Safeguarded AI report on biopharmaceutical supply chains</a> cites an estimated €4 billion in annual vaccine cold-chain waste in Europe and more than $35 billion in annual losses from temperature excursions across the global pharmaceutical industry.</p>
<p>That combination of interrelated decisions and high-consequence outcomes makes many <a href="https://hash.ai/apps/supply-chain">supply chain applications</a> ideal use cases for Safeguarded AI (SgAI). The report explains that safe AI decision-making requires three things: a world model capable of predicting an action’s consequences, an explicit specification of acceptable outcomes, and a verifier that can establish whether the resulting system satisfies that specification.</p>
<h2 id="why-supply-chains-need-safeguarded-ai">Why supply chains need Safeguarded AI</h2>
<h3 id="supply-chains-are-complex-but-structurally-modelable">Supply chains are complex but structurally modelable.</h3>
<p>Facilities, suppliers, transport lanes, inventories, production processes, inspections, and orders form a network of concurrent events and constrained resources. Uncertainty enters through demand, lead times, equipment failures, quality events, and disruptions. The system is difficult to reason about informally, yet its important entities and interactions can still be represented <a href="https://topos.institute/blog/2024-10-31-declarative-models-and-collaborative-modeling/">declaratively</a>.</p>
<h3 id="many-safety-requirements-are-concrete">Many safety requirements are concrete.</h3>
<p>Consider a temperature-sensitive vaccine shipment that is delayed while its container temperature trends towards its validated limit. The world model might track the batch’s location, temperature history, remaining shelf life, predicted weather, route-time distributions, destination demand, and available replacement stock.</p>
<p>The optimisation variables are the actions available to the controller: continue the journey, pause it in cold storage, select another route or transport mode, or reallocate replacement inventory. The objective might be to minimise expected waste, delay, cost, and stock-out risk. The safety specification is more categorical: never deliver a batch that has failed or still requires quality assessment; never choose an intervention that increases excursion risk; never divert stock from a higher-priority destination to a lower-priority one; and never violate approved handling or transport constraints.</p>
<p>This separation between what should be optimised and what must never happen (certified up to probability <span class="katex"><span class="katex-mathml"><math xmlns="http://www.w3.org/1998/Math/MathML"><semantics><mrow><mi>ε</mi></mrow><annotation encoding="application/x-tex">\varepsilon</annotation></semantics></math></span><span class="katex-html" aria-hidden="true"><span class="katex-base"><span class="katex-strut" style="height:0.4306em;"></span><span class="mord mathnormal">ε</span></span></span></span>) is central to SgAI.</p>
<h3 id="rare-long-tail-edge-cases-matter-enormously">Rare, long-tail edge cases matter enormously.</h3>
<p>Those specifications become most valuable when failures compound. A distribution plan that is safe under ordinary demand may become unsafe when a supplier failure, port closure, equipment outage, and demand spike occur together—as they might during the next pandemic-scale emergency. Historical testing can show that a policy usually performs well, but it cannot contain every consequential combination of events. SgAI instead asks whether a controller remains within its safety specification across the behaviours represented by the world model—including unlikely ones.</p>
<h2 id="modeling-supply-chains-with-petri-nets">Modeling supply chains with Petri Nets</h2>
<p><a href="https://en.wikipedia.org/wiki/Petri_net">Petri nets</a> are a natural candidate for building such models. Places can represent states or resources, transitions represent events or decisions, and tokens represent batches, orders, vehicles, or capacity. Coloured, timed, and stochastic extensions add product data, process durations, and uncertainty. The result is both executable and analysable: candidate policies can be simulated, while reachability, invariants, and related verification techniques identify forbidden states, deadlocks, or resource violations.</p>
<p>Stochastically and Dynamically Coloured Petri Nets (SDCPNs) go further by allowing token attributes to evolve as continuous stochastic processes. Semantically speaking, SDCPNs are equivalent to General Stochastic Hybrid Systems, which are expressive enough to enable the modeling of almost all cyber-physical systems (<a href="https://cdn.intechopen.com/pdfs/9186/InTech-Hybrid_state_petri_nets_which_have_the_analysis_power_of_stochastic_hybrid_systems_and_the_formal_verification_power_of_automata.pdf">Everdij &amp; Blom, 2010</a>). For example, in a pharmaceutical supply chain context, SDCPNs enable us to model the moving of a cold-chain batch discretely between supplier, vehicle, warehouse, and quarantine states – while its temperature exposure, remaining shelf life, or degradation risk evolves continuously – providing a compositional model of both operational logic and physical uncertainty.</p>
<p>But supply chains are more than a convenient demonstration domain. They offer consequential decisions, explicit safety boundaries, rich uncertainty, and measurable outcomes—all within systems that can be formally modelled. This is a sweet spot for SgAI to demonstrate its value: enabling AI to search for better decisions while mathematical assurance determines which decisions are safe enough to act on.</p>
<p>As part of the SgAI programme, the open-source <a href="https://petrinaut.org/">Petrinaut</a> library for working with SDCPNs has been developed, and now powers this real-world supply chain modeling and optimisation capability in the <a href="https://hash.ai/features">HASH platform</a>.</p>
<hr class="thin">
<p><em>This post was written by <a href="https://vilkinsons.com/">Dei Vilkinsons</a> from <a href="https://hash.ai/">HASH</a>, who lead TA1.3 of the Safeguarded AI programme. HASH’s work on the programme seeks to enable the accurate and complete modeling and specification of cyber-physical environments, and as part of TA2 they are leading efforts to apply the programme’s technology in a variety of real-world production contexts.</em></p>]]></content:encoded>
    </item>
  </channel>
</rss>